简单dp
定义了一个函数f。f(‘’)=’’,设s为一个字符串数组,则f(s[1],s[2])=s’(为一个字符串),s’必须满足s[1]是他的前缀,而s[2]是他的后缀,并且s’的长度必须最短。
F(s[1],s[2],s[3],s[4]…,s[n])=f(f(s[1],s[2],s[3],s[4]…,s[n-1]),s[n])
现在有一个长度为n的字符串序列s,s序列的大小不超过200000,每一个字符串的长度不超过20,而且长度都一样,且均为0\1串。
必须找出两个没有重复元素的子序列a和b,使得序列s的每一个元素都属于其中某一个子序列,并且使得f(a)+f(b)的总长度最小。
很直观的想法是记f[i][j]为到第i个字符串,另一子序列最后一个是第j个字符串的最小值,很直观地可以得到一个方程,但是显然的,光是状态就达到了n^2的级别,因此必须转换思路,观察方程,其实我们多记录了一件事,那就是j+1~i都属于i这个序列,考虑一下我们只将f[i]设为第i个字符串和第i+1个字符串不属于同一个序列,则将转移变为o(n)虽然时间复杂度没变,但是前一个方程时间复杂度已经是下界(状态数),而现在的方程则可以在转移上做手脚,观察方程
F[i]=min{f[j]+s[j+1][i]+w[j][i+1]};s表示区间的函数值,wij表示第i个和第j个的花费,很明显s可以用前缀和拆开,关键在于w这个玩意儿没法用数据结构优化,因为每一个不同的j他的w都会不同,但是w有一个局限就是取值不超过len,我们将一个字符串的f值储存为len个w的值也就确定了,时间复杂度o(n*len)
#include <cstdio> #include <cstdlib> #include <cstring> #include <algorithm> const int oo=1073741819; using namespace std; int f[2000000],g[21][1048577],n,len,a[2000000],s[2000000],c[2000000],ans; char ch[300000][21]; int check(int x,int y) { int ne,na,j,k=len; for (ne=x&1,na=(y>>(len-1))&1,x>>=1,j=1;j<=len;ne=ne | ((x&1)<<j),na=(na<<1)+((y>>(len-j-1))&1),j++,x>>=1) if (ne==na) k=min(k,len-j); return k; } void init() { int sum,cos,i,ne,j,little; scanf("%d\n",&n); for (i=1;i<=n;i++) scanf("%s\n",ch[i]+1); len=strlen(ch[1]+1); for (i=1;i<=n;i++) { ne=0; for (j=1;j<=len;j++) ne=(ne<<1)+(ch[i][j]-'0'); c[i]=ne; } c[0]=0; for (i=1;i<=n;i++) a[i]=check(c[i-1],c[i]); a[1]=len; for (i=1;i<=n;i++) s[i]=s[i-1]+a[i]; memset(g,61,sizeof(g)); little=len; for (i=1;i<=n-1;i++) { sum=little; for (ne=c[i+1],j=len;j;j--,ne>>=1) { cos=g[j][ne]+(len-j); if (cos<sum) sum=cos; } f[i]=sum+s[i]; for (cos=c[i]>>1,ne=c[i]&1,j=1;j<=len;j++,ne=ne | ((cos&1)<<(j-1)),cos>>=1) if (f[i]-s[i+1]<g[j][ne]) g[j][ne]=f[i]-s[i+1]; if (f[i]-s[i+1]+len<little) little=f[i]-s[i+1]+len; } ans=s[n]; for (i=1;i<=n-1;i++) ans=min(ans,f[i]+s[n]-s[i+1]); printf("%d\n",ans); // for (i=1;i<=n;i++) printf("%d ",a[i]); } int main() { freopen("83E.in","r",stdin); freopen("83E.out","w",stdout); init(); return 0; }