SMU Summer 2024 Contest Round 5

[ABC230F] Predilection - 洛谷 偏爱...

思路:本次比赛最顶级的题目!务必肾科礼节..

首先思考在数列中"选择两个相邻的数,删去他们,并在原位置放入他们的和的实质是什么?"
答案就是删除该数列前缀和中相应的一个数字.
例如:数列arr:1,2,3,4 ; 那么有前缀和pre:1,3,6,10.
如果删去数字3,那么就是在前缀和中删去数字6. 其他保持不变.
而且!数列 和 前缀和 是可以互相推导,并且一一对应的. 那么求数列序列不同的个数,就是求前缀和序列不同的个数.
至此,完成了这道题较为简单的部分(但是我完全没想到..)
另一个问题,状态转移方程怎么写?  本题最顶级的地方..肾科礼节..
定义dp[i]为以第pre[i]个数字结尾,可以有多少个不同的序列.
以pre[i]为结尾的不同序列的个数为"先前不同的序列个数"和"先前不同序列拼上pre[i]的个数"和"该数字单独一个"那么转移方程为:
①dp[i]=dp[i-1]*2+1--pre[i]是第一次出现.
②dp[i]=dp[i-1]*2-dp[last-1]--pre[i]不是第一次出现,并且上一次出现的位置为last.
为什么是这样呢?为什么*2?为什么-dp[last-1]?
看这个例子:pre为:1,2,3,3; 有如下:
dp[1]=1 : 1
dp[2]=3 : 1 ' 1,2 ' 2
dp[3]=7 : 1 ' 1,2 ' 2 ' 1,3 ' 1,2,3 ' 2,3 ' 3
分别由"先前不同的序列"和"先前不同序列拼上pre[i]"和单独的"pre[i]"组成dp[3],由此可见dp[i]=dp[i-1]*2+1的由来
dp[4],这里3重复出现了,如果还是像上面那样直接转移,"先前不同的序列"和"先前不同序列拼上pre[i]"和单独的"pre[i]"组成dp[4]的话,会出现重复部分如下:
1 ' 1,2 ' 2 ||| 1,3 ' 1,2,3 ' 2,3 ' 3 ||| 1,3 ' 1,2,3 ' 2,3 ' 1,3,3 ' 1,2,3,3 ' 2,3,3 ' 3,3 '
在这个例子dp[4]的转移中,||| 1,3 ' 1,2,3 ' 2,3 ||| 这一部分的计数会重复
即"先前不同序列拼上pre[i]"这部分贡献,会有部分的重复,重复的部分就是dp[last-1]的个数,因为这部分已经在上一次3出现的时候拼过了.
要注意!!最后一个pre[n]是不可以删去的,所以dp[n]=dp[n-1]+1,即前面全部序列都要拼上pre[n],和只有pre[n]自己的情况.
至此,结束。
可以当做是在序列中删去k个数字,求有多少种不同序列的dp模板!
const int mod=998244353;
int n;
int pre[200005];
int dp[200005];
unordered_map last;
[ABC230F] Predilection
https://www.luogu.com.cn/problem/AT_abc230_f
很顶级的一个题....肾科礼节..
首先思考在数列中"选择两个相邻的数,删去他们,并在原位置放入他们的和的实质是什么?"
答案就是删除该数列前缀和中相应的一个数字.
例如:数列arr:1,2,3,4 ; 那么有前缀和pre:1,3,6,10.
如果删去数字3,那么就是在前缀和中删去数字6. 其他保持不变.
而且!数列 和 前缀和 是可以互相推导,并且一一对应的. 那么求数列序列不同的个数,就是求前缀和序列不同的个数.
至此,完成了这道题较为简单的部分(但是我完全没想到..)
另一个问题,状态转移方程怎么写?  本题最顶级的地方..肾科礼节..
定义dp[i]为以第pre[i]个数字结尾,可以有多少个不同的序列.
以pre[i]为结尾的不同序列的个数为"先前不同的序列个数"和"先前不同序列拼上pre[i]的个数"和"该数字单独一个"那么转移方程为:
①dp[i]=dp[i-1]*2+1--pre[i]是第一次出现.
②dp[i]=dp[i-1]*2-dp[last-1]--pre[i]不是第一次出现,并且上一次出现的位置为last.
为什么是这样呢?为什么*2?为什么-dp[last-1]?
看这个例子:pre为:1,2,3,3; 有如下:
dp[1]=1 : 1
dp[2]=3 : 1 ' 1,2 ' 2
dp[3]=7 : 1 ' 1,2 ' 2 ' 1,3 ' 1,2,3 ' 2,3 ' 3
分别由"先前不同的序列"和"先前不同序列拼上pre[i]"和单独的"pre[i]"组成dp[3],由此可见dp[i]=dp[i-1]*2+1的由来
dp[4],这里3重复出现了,如果还是像上面那样直接转移,"先前不同的序列"和"先前不同序列拼上pre[i]"和单独的"pre[i]"组成dp[4]的话,会出现重复部分如下:
1 ' 1,2 ' 2 ||| 1,3 ' 1,2,3 ' 2,3 ' 3 ||| 1,3 ' 1,2,3 ' 2,3 ' 1,3,3 ' 1,2,3,3 ' 2,3,3 ' 3,3 '
在这个例子dp[4]的转移中,||| 1,3 ' 1,2,3 ' 2,3 ||| 这一部分的计数会重复
即"先前不同序列拼上pre[i]"这部分贡献,会有部分的重复,重复的部分就是dp[last-1]的个数,因为这部分已经在上一次3出现的时候拼过了.
要注意!!最后一个pre[n]是不可以删去的,所以dp[n]=dp[n-1]+1,即前面全部序列都要拼上pre[n],和只有pre[n]自己的情况.
至此,结束。
可以当做是在序列中删去k个数字,求有多少种不同序列的dp模板!
void solve(){           G--毫无头绪..
    cin>>n;
    for(int i=1;i<=n;i++) cin>>pre[i],pre[i]+=pre[i-1];
    dp[1]=1,last[pre[1]]=1;  init
    for(int i=2;i<=n-1;i++){
        if(last[pre[i]]==0) (dp[i]=dp[i-1]*2+1)%=mod;
        else (dp[i]=dp[i-1]*2-dp[last[pre[i]]-1]+mod)%=mod;
        last[pre[i]]=i;
    }
    因为最后一个是不可以删去的!!所以到第pre[n]的个数为,dp[n-1]全拼上pre[n],和pre[n]独立一个.共:dp[n-1]+1
    cout<<(dp[n-1]+1)%mod;
}
pre:1,2,3,4,3

[ABC211E] Red Polyomino - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:很奇妙的暴搜..把当前搜过的路封阻#. 到下一次搜索的时候再释放这些刚刚被封阻的位置..

并且dfs里面是没有{x,y}的位置坐标参数的,下一个合法的位置是通过遍历矩阵,遇到'@'旁边的'.'就是下一个合法位置。既然能从当前{i,j}点进入dfs,那么从出来的时候,意味着当前step!和{i,j}有关的方案数已经选完了!和step是相关联的!

int n,k,ans=0;
char maze[10][10];
int dx[4]={-1,1,0,0};
int dy[4]={0,0,-1,1};
这里的参数没有x,y,下一个合法的位置是通过遍历这个8*8的矩阵,遇到'@'旁边的'.'就是下一个合法位置
void dfs(int step){
    if(step==0){
        ans++;
        return;
    }
    vector> mark;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(maze[i][j]!='.') continue;
            bool check=false;
            for(int h=0;h<4;h++){
                int x=i+dx[h],y=j+dy[h];
                if(x>=1&&x<=n&&y>=1&&y<=n&&maze[x][y]=='@') check=true;
            }
            if(check){
                maze[i][j]='@';
                dfs(step-1);
                maze[i][j]='#';   当前和{i,j}点有关的方案已经选完,可以封阻--神奇的点.
                mark.emplace_back(i,j);
            }
        }
    }
    for(auto v:mark) maze[v.first][v.second]='.';  回溯--理解. 之前被封阻的点在后续方案还能选择.
}
Red Polyomino
https://www.luogu.com.cn/problem/AT_abc211_e
void solve(){       E   这个暴搜不会搜重,深刻理解.
    cin>>n>>k;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            cin>>maze[i][j];
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(maze[i][j]=='.'){
                maze[i][j]='@';
                dfs(k-1);
                maze[i][j]='#';     和{i,j}点有关的方案已经选完了,可以直接封阻.
            }
        }
    }
    cout<

[ABC213E] Stronger Takahashi - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:暴力建边,附件能打一拳的位置都是代价为1.特别要注意相邻且为'.'的代价为0. 建完边之后跑最短路即可.关键在于建边.

int n,m;
char maze[505][505];
int dx[20]={-2,-2,-2,-1,-1,-1,-1,-1,0,0,0,0,1,1,1,1,1,2,2,2};
int dy[20]={-1,0,1,-2,-1,0,1,2,-2,-1,1,2,-2,-1,0,1,2,-1,0,1};
vector> vct[250004];     250000
//priority_queue> pq;
deque dq;
int dis[250004];
bool vis[250004];
void bfs01(int s){
    for(int i=1;i<=n*m;i++) dis[i]=INT_MAX,vis[i]=0;
    dis[s]=0;
    dq.emplace_front(s);
    while(dq.size()){
        int from=dq.front();
        dq.pop_front();
        if(vis[from]) continue;
        vis[from]=1;
        for(auto v:vct[from]){
            int to=v.first,w=v.second;
            if(dis[to]>dis[from]+w){
                dis[to]=dis[from]+w;
                if(w==0) dq.emplace_front(to);
                else dq.emplace_back(to);
            }
        }
    }
}
[ABC213E] Stronger Takahashi
https://www.luogu.com.cn/problem/AT_abc213_e
void solve(){           F  可以01bfs(143ms) 建图dijkstra(184ms),虽然建边还可以优化一点,少建一点。但是无关紧要.
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>maze[i][j];
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            for(int g=0;g<20;g++){
                int x=i+dx[g],y=j+dy[g];
                if(x>=1&&x<=n&&y>=1&&y<=m) {
                    if((g==5||g==9||g==10||g==14)&&maze[x][y]=='.') vct[(i-1)*m+j].emplace_back((x-1)*m+y,0);
                    else vct[(i-1)*m+j].emplace_back((x-1)*m+y,1);
                }
            }
        }
    }
    bfs01(1);
    cout<

你可能感兴趣的:(动态规划,算法,c++)