Codeforces Round #642 (Div. 3) E-F题解

传送门
A,B,C,D懒得补了,写一下E,F的题解吧

E

题意

 给一个$01$串,一次操作可以把一个0变成1,或者把1变成0.求最小的操作次数使得$01$串中相邻得两个1之间间隔为$k$。

题解

  • 显然可以$dp$
  • $dp_{i,0/1}$表示第$i$位为$0/1$时,前$i$位满足条件需要的最小操作次数,$cnt_i$为前$i$位的1的个数
  • 当第$i$位取1时,则$dp_{i,1}=min(dp_{i-k,1}+cnt_{i-1}-cnt_{i-k},cnt_{i-1})+s_i!=1$
  • 当第$i$位取0时,$dp_{i,0}=min(dp_{i-1,0},dp_{i-1,1})+s_i!=0$
  • 注意边界的情况
查看代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6+5;
const int mod = 1e9+7;
ll qpow(ll a,ll b){ll res=1;for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
int dp[maxn][2];
char s[maxn];
int cnt[maxn];
int main()
{
#ifndef ONLINE_JUDGE
    freopen("simple.in", "r", stdin);
    freopen("simple.out", "w", stdout);
#endif
    int t;
    scanf("%d",&t);
    while(t--){
        int n,k;
        scanf("%d%d",&n,&k);
        scanf("%s",s+1);
        for(int i = 1;i <= n;++i){
            cnt[i]=cnt[i-1]+s[i]-'0';
            dp[i][0]=dp[i][1]=1e9;
        }
        for(int i = 1;i <= n;++i){
            if(s[i]=='1'){
                if(i<=k){
                    dp[i][1]=cnt[i-1];
                    dp[i][0]=min(dp[i-1][1],dp[i-1][0])+1;
                }
                else {
                    dp[i][1]=dp[i-k][1]+cnt[i-1]-cnt[i-k];
                    dp[i][1]=min(dp[i][1],cnt[i-1]);
                    dp[i][0]=min(dp[i-1][1],dp[i-1][0])+1;
                }
            }
            else {
                if(i<=k){
                    dp[i][1]=dp[i-1][0]+1;
                    dp[i][0]=min(dp[i-1][1],dp[i-1][0]);
                }
                else {
                    dp[i][1]=dp[i-k][1]+cnt[i]-cnt[i-k]+1;
                    dp[i][1]=min(dp[i][1],cnt[i-1]+1);
                    dp[i][0]=min(dp[i-1][1],dp[i-1][0]);
                }
            }
        }
        printf("%d\n",min(dp[n][0],dp[n][1]));
    }
    return 0;
}

F

题意

 给定一个$n*m$的矩阵,起点位$(1,1)$终点为$(n,m)$,每次可以向右或向下走1格,并且只能走比当前位置的权值大1的位置。在开始走之前你可以进行任意次操作,每次操作可以把任意位置的权值减少1,求走到终点需要的最小操作次数

题解

  • 如果确定了起点的权值为$b_{1,1}$那么从起点$(1,1)$走到$(i,j)$时,$(i,j)$的权值一定为$b_{1,1}+i+j-2$。
  • 设$dp_{i,j}$为到达$(i,j)$需要的最小操作次数,$dp_{i,j}=min(dp_{i-1,j},dp_{i,j-1})+res$,$res$为$(i,j)$原来的值与$b_{i,j}+i+j-2$的差,若$res<0$则代表当前枚举的起点权值无法走到$(i,j)$这个位置,就不对这个点进行更新
  • 考虑如何枚举起点的权值
  • 显然在起点到终点走过的路径中一定存在一个点的值没有发生变化。所以直接$O(n*m)$枚举这个点然后$O(n*m)$dp一下就好了,复杂度$O(n^2*m^2)$
查看代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 105;
const int mod = 1e9+7;
ll qpow(ll a,ll b){ll res=1;for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
ll dp[maxn][maxn],a[maxn][maxn],b[maxn][maxn];
int main()
{
#ifndef ONLINE_JUDGE
    freopen("simple.in", "r", stdin);
    freopen("simple.out", "w", stdout);
#endif
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i = 1;i <= n;++i){
            for(int j = 1;j <= m;++j)scanf("%lld",&a[i][j]);
        }
        ll ans = 1e18;
        for(int i = 1;i <= n;++i){
            for(int j = 1;j <= m;++j){
                ll tmp = a[i][j]-i-j+2;
                if(tmp<=a[1][1]){
                    bool ok = true;
                    memset(dp,0x3f,sizeof(dp));
                    dp[0][1]=0;
                    ll p;
                    for(int k = 1;k <= n;++k){
                        for(int l = 1;l <= m;++l){
                            p = tmp+k+l-2;
                            if(p>a[k][l]){
                                continue;
                            }  
                            dp[k][l]=min(dp[k-1][l],dp[k][l-1])+a[k][l]-p;
                        }
                    }
                    ans=min(ans,dp[n][m]);
                }
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
}
posted @ 2020-05-16 14:14  tryatry  阅读(273)  评论(0编辑  收藏  举报