两道比较好的dp题 codeofrces 1196 D2 1256 E

这两天做到两道比较好的dp题,感觉很多dp题都这样,就是很容易想到一个O(n*n)的dp,但是O(n*n)的会超时,所以就想怎么优化,有时候可以降成O(n*log n)的,有时候是思维题,只要想到了就可以降成O(n)的dp,就是最普通的那种dp。感觉都比较类似,在这里总结一下。

codeforces 1196 D2

题意:给你一个长度为n的字符串a和一个整数k,问最少改变多少个字符能使:这个字符串中存在一个长度为k的子串是字符串是“RGBRGBRGB......”的字串。k<=n<=2e5

首先,O(n*n)的dp很好想:

令mp['R']=1,mp['G']=2,mp['B']=3。

dp[i][j][k]表示前 i 个字符中构成一个长度为k的子串(并且这个子串是以i为结尾的,即a[i-k+1]到a[i],并且mp[a[i]]==k)所需要改变的最小次数。

状态转移方程:dp[i][j][k]=min(dp[i][j][k],dp[i-1][j-1][k-1]+(mp[a[i]]==k?0:1))      如果k等1的话,k-1改成3。

不过这显然会T(D1那个数据范围小,可以这么AC)

 

然后我们想怎样优化,我们可以不记录长度为1到k的子串,我们只需要记录长度为k的即可,这样也能进行状态转移。

令dp[i][j]表示前i个字符中构成一个长度为k的子串(并且这个子串是以i为结尾的,即a[i-k+1]到a[i],并且mp[a[i]]==j)所需要改变的最小次数。状态转移方程:dp[i][j]=min(dp[i][j],dp[i-1][j-1]+(mp[a[i]]==k?0:1)-f)       如果j等1的话,j-1改成3

其中f的值为0或者1,当mp[a[i-k]]==j-(k%3)时f=0,否则f为1。

详见代码:

#include
#define mem(a,b) memset((a),b,sizeof(a))
#define de cout< mp;
void init()
{
    for(int p=1;p<=3;p++)
    {
        int num=0;
        int pp=p;
        for(int i=kk;i>=1;i--)
        {
            if(mp[a[i]]!=pp)
                num++;
            pp--;
            if(pp<1) pp=3;
        }
        dp[kk][p]=num;
    }
}
int main()
{
    mp['R']=1;
    mp['G']=2;
    mp['B']=3;
    int T;scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&kk);
        scanf("%s",a+1);
        if(n==1||kk==1)    {printf("0\n");continue;}
        for(int i=0;i<=n;i++)
            for(int j=1;j<=3;j++)
                dp[i][j]=inf;
        init();
        int f,K;
        for(int i=kk+1;i<=n;i++)
        {
            for(int j=1;j<=3;j++)
            {
                f=0;
                K=kk%3;
                K=j-K;
                if(K<1)
                    K+=3;
                if(mp[a[i-kk]]!=K)
                    f=1;
                if(mp[a[i]]==j)
                {
                    if(j==1)
                        dp[i][j]=min(dp[i][j],dp[i-1][3]-f);
                    else
                        dp[i][j]=min(dp[i][j],dp[i-1][j-1]-f);
                }
                else
                {
                    if(j==1)
                        dp[i][j]=min(dp[i][j],dp[i-1][3]+1-f);
                    else
                        dp[i][j]=min(dp[i][j],dp[i-1][j-1]+1-f);
                }
            }
        }
        int ans=inf;
        for(int i=kk;i<=n;i++)
            for(int j=1;j<=3;j++)
                ans=min(ans,dp[i][j]);
        printf("%d\n",ans);
    }
    return 0;
}

 codeforces 1256 E

题意:给你n个数,让你给这n个数分组(分多少组都行,但每个组的人数必须大于等于3),每个组的组值为组里的数的最大值减最小值的差,问怎样分组才能使这些组的组值的和最小。n<=2e5

先把这n个数排个序,因为肯定是希望把值相近的数分到一起嘛

首先,O(n*n)的dp还是很好想:

令dp[i]表示对前i个人分组,组值的和最小为dp[i]。dp[i]=min(dp[i],dp[j]+a[i]-a[i-j+1])        3<=i-j  &&  j>=3

但是我们没有必要让j循环这么多次。

如果我们把6个人分成一组,那么一定不是最优的,因为我们可以把这六个人拆成三三的两组,这样一定不比6个人一组差,所以一个组的人数只有3种情况:三人组、四人组、五人组。

令dp[i]表示对前i个人分组,组值的和最小为dp[i]。dp[i]=min(dp[i],dp[i-3]+a[i]-a[i-2],dp[i-4]+a[i]-a[i-3],dp[i-5]+a[i]-a[i-4])

#include
#define mem(a,b) memset((a),b,sizeof(a))
#define de cout<=8)
            dp[i]=min(min(dp[i-3]+a[i].v-a[i-2].v,dp[i-4]+a[i].v-a[i-3].v),dp[i-5]+a[i].v-a[i-4].v);
        else if(i>=7)
            dp[i]=min(dp[i-3]+a[i].v-a[i-2].v,dp[i-4]+a[i].v-a[i-3].v);
        else
            dp[i]=dp[i-3]+a[i].v-a[i-2].v;
    }
    printf("%I64d ",dp[n]);
    ans=0;
    for(int i=n;i>=3;i--)
    {
        if(i>=5&&dp[i]==dp[i-5]+a[i].v-a[i-4].v)
            a[i].idd=++ans,a[i-1].idd=ans,a[i-2].idd=ans,a[i-3].idd=ans,a[i-4].idd=ans,i-=4;
        else if(i>=4&&dp[i]==dp[i-4]+a[i].v-a[i-3].v)
            a[i].idd=++ans,a[i-1].idd=ans,a[i-2].idd=ans,a[i-3].idd=ans,i-=3;
        else if(i>=3&&dp[i]==dp[i-3]+a[i].v-a[i-2].v)
            a[i].idd=++ans,a[i-1].idd=ans,a[i-2].idd=ans,i-=2;
    }
    printf("%d\n",ans);
    sort(a+1,a+n+1,cmp2);
    printf("%d",a[1].idd);
    for(int i=2;i<=n;i++)
        printf(" %d",a[i].idd);
    puts("");
    return 0;
}

 

 

 

 

 

 

你可能感兴趣的:(CF,HDU,POJ,题目)