【划分型DP】数字游戏

在cv上看到一个题解思路清晰,特地搬来这里存一下。


题目描述 Description

丁丁最近沉迷于一个数字游戏之中。这个游戏看似简单,但丁丁在研究了许多天之后却发觉原来在简单的规则下想要赢得这个游戏并不那么容易。游戏是这样的,在你面前有一圈整数(一共n个),你要按顺序将其分为m个部分,各部分内的数字相加,相加所得的m个结果对10取模后再相乘,最终得到一个数k。游戏的要求是使你所得的k最大或者最小。

例如,对于下面这圈数字(n=4m=2):

                                  2

                   4                           -1

                                 3

当要求最小值时,((2-1) mod 10)×((4+3) mod 10)=1×7=7,要求最大值时,为((2+4+3) mod 10)×(-1 mod 10)=9×9=81。特别值得注意的是,无论是负数还是正数,对10取模的结果均为非负值。

丁丁请你编写程序帮他赢得这个游戏。

输入描述 Input Description

输入文件第一行有两个整数,n1≤n≤50)和m1≤m≤9)。以下n行每行有个整数,其绝对值不大于104,按顺序给出圈中的数字,首尾相接。

输出描述 Output Description

输出文件有两行,各包含一个非负整数。第一行是你程序得到的最小值,第二行是最大值。

样例输入 Sample Input

4 2

4

3

-1

2

样例输出 Sample Output

7

81

数据范围及提示 Data Size & Hint

en









由于是环,需要断环成链。采用序列倍增的方法,即原序列是1 2 3,新序列是1 2 3 1 2 3,枚举dp起点。

O(N^2)预处理,求出第i到j之间数字和对10求余的结果。

注意:正确的求余方法是((n%10)+10)%10!


枚举起点s,前i个数分成j组,其中最后一组含有第k+1到i的数字

状态转移方程:

d[i][j]=min(d[i][j],d[k][j-1]*SumMod10[s+k+1][s+i]);

p[i][j]=max(p[i][j],p[k][j-1]*SumMod10[s+k+1][s+i]);

(0<=s<N;1<=i<=N;1<=j<=min(i,M);j-1<=k<=i-1)


注意:当M=1是,只有一种划分方案,无需状态转移,否则会得出错误的答案(这里卡了好久)!


最后注意一下初始化条件,d[][]=INF,p[][]=0,d[0][0]=p[0][0]=1.


#include<iostream>

#include<cstring>

#include<climits>

#define MAXN 53

#define MAXM 12

#define INF 1000000

using namespace std;



int N,M;

int n[MAXN*2];



int SumMod10[MAXN*2][MAXN*2];



int d[MAXN][MAXM];

int p[MAXN][MAXM];



int MIN=INT_MAX;

int MAX=INT_MIN;



int main()

{

    cin>>N>>M;

    

    for(int i=1;i<=N;i++)

    {

        cin>>n[i];

        n[i+N]=n[i];

    }

    

    for(int i=1;i<=N*2;i++)

    {

        SumMod10[i][i]=n[i];

        for(int j=i+1;j<=N*2;j++)

            SumMod10[i][j]=SumMod10[i][j-1]+n[j];

    }

    

    for(int i=1;i<=N*2;i++)

        for(int j=i;j<=N*2;j++)

        {

            SumMod10[i][j]=((SumMod10[i][j]%10)+10)%10;

        }

    

    if(M==1)

    {

        MIN=MAX=SumMod10[1][N];

    }

    else

        for(int s=0;s<N;s++)

        {

            for(int i=0;i<=N;i++)

            {

                for(int j=0;j<=M;j++)

                {

                    d[i][j]=INF;

                    p[i][j]=0;

                }

            }

            d[0][0]=p[0][0]=1;

            

            for(int i=1;i<=N;i++)

            {

                for(int j=1;j<=min(i,M);j++)

                {

                    for(int k=j-1;k<=i-1;k++)

                    {

                        d[i][j]=min(d[i][j],d[k][j-1]*SumMod10[s+k+1][s+i]);

                        p[i][j]=max(p[i][j],p[k][j-1]*SumMod10[s+k+1][s+i]);

                    }

                }

            }

            MIN=min(MIN,d[N][M]); 

            MAX=max(MAX,p[N][M]);

        }

    

    cout<<MIN<<endl<<MAX<<endl;

    

    return 0;

}


划分型DP需要注意的地方

若dp[i][j]表示为前i个分成j份时:


1.要考虑dp[0][0]=1;

2.k=1时无需状态转移,表示只有一种划分方案,需要if语句提前处理边界;

3.预处理时dp[][]=INF.

4.模板(答案为dp[N][M]时)

for(int i=1;i<=N;i++)

           

                for(int j=1;j<=min(i,M);j++)

                

                    for(int k=j-1;k<=i-1;k++)

                    

                        d[i][j]=min(d[i][j],d[k][j-1](?)f(x));

                        

                    

                

            






  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值