[POJ2440]DNA(dp)

来源:互联网 发布:慕课网python教程下载 编辑:程序博客网 时间:2024/06/16 21:31

题目描述

传送门
题意:给定一个长度L,求长度为L的01串不包含101和111的有多少个。

题解

算法一
这题在openjudge上的范围是106,一看就是线性dp的范围。
106怎么做呢?
设f(i,a,b)表示前i位,最后两位为a,b(a,b=0/1)的方案数。那么f(i,b,a)=(f(i,b,a)+f(i-1,c,b))%Mod;其中保证cba!=101并且cba!=111。
这个dp是很显然的。因为要保证每一次在最后一位放的时候和前面没有冲突,那么就要记录后两位。
时间复杂度O(n23)

算法二
但是在POJ上的范围是108,尼玛这连数组都开不下好吧。
是时候来一波奇技淫巧了。
考虑当长度不变时,方案数是不变的。将两段合成一段的时候,只需要将它们各自的方案数相乘就可以了。
那么如果我们把长度L分成L块,那么每一块的长度都是104级别的。
设m=L,t=n/m;
设f(i,a,b,c,d)表示前i位,其中最前面两位为ab,最后两位为cd(a,b,c,d=0/1)的方案数,那么我们可以在O(L)内求出f(t,a,b,c,d)。
设q(i,a,b)表示前i个块,最后两位为ab(a,b=0/1)的方案数,那么q(i,e,l)=(q(i,e,l)+q(i-1,a,b)*f(m,c,d,e,l))%Mod;其中保证abc!=101,abc!=111,bcd!=101,bcd!=111。同样是保证不冲突。
求出了q(t,a,b)之后,块有可能不是整个的,也就是说在最后一个块的后面有可能有一些没算过的位置。但是数量同样是L级别的,可以直接暴力。
那么我们终于求出了正确的解!时间复杂度O(L26)

算法三
当我打完愚蠢的多维dp了之后看题解= =
递推式:f(n)=f(n-1)+f(n-3)+f(n-4)
其实想一想就能明白它的正确性:
f(n-1)实际上是往最后一位加了一个0。
最后一位只有一个数,只有0/1两种情况。
最后加一个0是一定不会引起冲突的对吧?但是最后加一个1就不一定了,如果前面是10的话就变成了101就会变成不合法的了。所以将f(n-1)加一遍。
f(n-3)实际上是往最后3位上加了一个011。
最后三位实际上有这么多种情况:
001 010 100 011 101 110 111
可以发现第2、3、6种情况在f(n-1)里就已经包括了;第5、7种情况是不合法的;第2种情况如果它前面是1的话就会变成1010有可能变成不合法的情况所以淘汰掉。于是最后三位就只剩下了一种011的情况,所以只将f(n-3)加一遍。
f(n-4)实际上是往最后四位加了一个0001。
和上面的方法相同,列举一下就会发现除了0001,其余所有的情况要么不合法,要么已经被之前的情况包括了。
而f(n-5)就没有意义了,因为它所有的情况不是被前面的包括了就是不合法。

这就是这个递推式的由来。
好劲啊!
而且f特别像fibnacci的递推公式对吧?那么就是说如果存在i>j,并且fi=fj,fi+1=fj+1,fi+3=fj+3,那么一定存在循环节。所以三项的值域理论上是Mod3,根据抽屉原理,最多Mod3一定会出现循环节,但是实际上循环节会小得多。所以我们只需要求出循环的部分就可以了。这种方法可以完爆dp!

代码

这里是暴力dp。

#include<iostream>#include<cstring>#include<cstdio>using namespace std;#define Mod 2005int n;long long f[1000005][2][2],ans;void clear(){    memset(f,0,sizeof(f)); ans=0;}int main(){    while (~scanf("%d",&n))    {        clear();        if (n==1)        {            puts("2");            continue;        }        f[2][0][0]=f[2][0][1]=f[2][1][0]=f[2][1][1]=1;        for (int i=3;i<=n;++i)            for (int a=0;a<=1;++a)                for (int b=0;b<=1;++b)                    for (int c=0;c<=1;++c)                        if (!(c==1&&b==0&&a==1)&&!(c==1&&b==1&&a==1))                            f[i][b][a]=(f[i][b][a]+f[i-1][c][b])%Mod;        ans=(f[n][0][0]+f[n][0][1]+f[n][1][0]+f[n][1][1])%Mod;        printf("%d\n",ans);    }}

108可过的多位dp。

#include<iostream>#include<cstring>#include<cstdio>#include<cmath>using namespace std;#define Mod 2005int n,m,p,t,base;int a[16]={0,2,4,6,9,15,25,40,64,104,169,273,441,714,1156,1870};int f[10005][2][2][2][2],g[2][2][2][2],h[10005][2][2],q[10005][2][2],ans;void clear(){    memset(f,0,sizeof(f)); memset(g,0,sizeof(g)); memset(h,0,sizeof(h)); memset(q,0,sizeof(q));    m=p=t=base=ans=0;}int main(){    while (~scanf("%d",&n))    {        clear();        if (n<16)        {            printf("%d\n",a[n]);            continue;        }        m=sqrt(n); p=n%m; t=n/m;        for (int a=0;a<=1;++a)            for (int b=0;b<=1;++b)                for (int c=0;c<=1;++c)                    for (int d=0;d<=1;++d)                        if (!(a==1&&b==0&&c==1)&&!(a==1&&b==1&&c==1)&&!(b==1&&c==0&&d==1)&&!(b==1&&c==1&&d==1))                            f[4][a][b][c][d]=1;        for (int i=5;i<=m;++i)            for (int a=0;a<=1;++a)                for (int b=0;b<=1;++b)                    for (int c=0;c<=1;++c)                        for (int d=0;d<=1;++d)                            for (int e=0;e<=1;++e)                                if (!(c==1&&d==0&&e==1)&&!(c==1&&d==1&&e==1))                                    f[i][a][b][d][e]=(f[i][a][b][d][e]+f[i-1][a][b][c][d])%Mod;        for (int a=0;a<=1;++a)            for (int b=0;b<=1;++b)                for (int c=0;c<=1;++c)                    for (int d=0;d<=1;++d)                    {                        g[a][b][c][d]=f[m][a][b][c][d];                        q[1][c][d]=(q[1][c][d]+f[m][a][b][c][d])%Mod;                    }        for (int i=2;i<=t;++i)            for (int a=0;a<=1;++a)                for (int b=0;b<=1;++b)                    for (int c=0;c<=1;++c)                        for (int d=0;d<=1;++d)                            for (int e=0;e<=1;++e)                                for (int l=0;l<=1;++l)                                    if (!(a==1&&b==0&&c==1)&&!(a==1&&b==1&&c==1)&&!(b==1&&c==0&&d==1)&&!(b==1&&c==1&&d==1))                                        q[i][e][l]=(q[i][e][l]+q[i-1][a][b]*g[c][d][e][l])%Mod;        if (p)        {            base=m*t;            for (int a=0;a<=1;++a)                for (int b=0;b<=1;++b)                    h[0][a][b]=q[t][a][b];            for (int i=m*t+1;i<=n;++i)                for (int a=0;a<=1;++a)                    for (int b=0;b<=1;++b)                        for (int c=0;c<=1;++c)                            if (!(a==1&&b==0&&c==1)&&!(a==1&&b==1&&c==1))                                h[i-base][b][c]=(h[i-base][b][c]+h[i-1-base][a][b])%Mod;            for (int a=0;a<=1;++a)                for (int b=0;b<=1;++b)                    ans=(ans+h[n-base][a][b])%Mod;        }        else        {            for (int a=0;a<=1;++a)                for (int b=0;b<=1;++b)                    ans=(ans+q[t][a][b])%Mod;        }        printf("%d\n",ans);    }}

傻逼dp我没有写。可能是因为看到递推式了之后感到自己太傻逼于是弃疗了。

总结

I am so stupid!!! ATP
①分块dp第一次写。自己想出来写出来还是很开心的。
②递推式非常厉害啊。这种东西应该自己好好想一想。

0 0
原创粉丝点击