[动态规划+数学结论]HOJ_12301_Eleven Lover

题目来源:HNU   http://acm.hnu.cn/online/?action=problem&type=show&id=12301&courseid=214

题目大意:给定一个数字(0-9)序列,没有前导0,要求你求出所有这样的连续子序列,即该子序列为11的倍数(0不算),序列长度可以达到80000

分析:要求解的实际上是两个子问题,1.如何判断一个序列能整除11,2.如何求出所有的这种序列。很容易想到,枚举起点终点,然后做大数取模,如果能整除11,那么记录下来,这样做的复杂度能达到O(n^3),显然会超时。那么,这里需要用到一个结论:在Radix进制下的数%(Radix+1)==0的充要条件是,该数的奇数位之和减去偶数位之和(以下记为隔位差)能够整除(Radix+1),同样还有另外一个结论,Radix进制下得数%(Radix-1)==0的充要条件是,该数的所有位之和能够整除(Radix-1)。这样的话,实际上如果要枚举起点终点的方法,还是不能够降低复杂度,这里要利用该结论用DP来解决。

 

构造状态为dp(i,j)表示以第i位结尾的所有子串的隔位差%11==j的个数,那么状态空间是11*80000,现在考虑如何进行转移,这里要感谢瑞瑞给的一个证明:对于序列ai-a1(假设i为偶数),记其隔位差为s,即s = segma(aj){0<j<i&&j is odd}-segma(ak){0<k<=i&&k is even},那么序列ai+1-a1的隔位差为ai+1-s,这里给出一个证明的样例,假设当前序列为abcd,那么其隔位差为(a+c)-(b+d),那么考虑到abcdf,其隔位差为(f+c+a)-(b+d) = f - [(a+c)-(b+d)] = f-s,即证。

再结合模的性质

那么可以得出状态转移方程为

  dp(0,a1%11) = 1

  dp(i,(ai%11-j)mod11) += dp(i-1,j){0<i<L,0<=j<11}  &&  if(ai) dp(i,ai%11)++ ,其中mod的作用是取模,返回值为0-10。最后的结果就是segma(dp(i,0)){0<=i<L}

考虑到dp(i,j)只与dp(i-1,k)有关系,那么可以用滚动数组优化。

 

代码:

 

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int MAXN = 80011; 
char seq[MAXN];
int dp[2][11];
int mod11(int x)
{
    x%=11;
    if(x<0)return x+11;
    return x;
}
int main()
{
    int L,ans;
    while(gets(seq),seq[0]!='0')
    {
        memset(dp,0,sizeof(dp));ans = 0;L = strlen(seq);
        dp[0][seq[0]-'0']=1;
        for(int i=1;i<L;i++)
        {
            int s = seq[i]-'0';
            for(int j=0;j<11;j++)
            {
                dp[1][mod11(s%11-j)]+=dp[0][j];
            }
            if(s)dp[1][s%11]++;
            ans+=dp[1][0];
            memcpy(&dp[0][0],&dp[1][0],11*sizeof(int));
            memset(dp[1],0,11*sizeof(int));
        }
        /*for(int i=0;i<L;i++)
        {
            for(int j=0;j<11;j++)
            cout<<dp[i][j]<<" ";
            cout<<endl;
        }*/
        printf("%d\n",ans);
    }
    return 0;
}


 

 

总结:在状态转移时,开始老是以ai为最后一位去算隔位差,结果导致不知道是+ai还是-ai,很纠结,最后瑞瑞说他是以ai为第一位开始算隔位差的,于是,问题得到解决,还是得多想想啊。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值