group:状压dp,轮廓线
神仙题。但是难得的傻孩子cbx没有喊题解,所以也就难得的自己想出来了一个如此神仙的题。
如果是自己想的,说它神仙是不是有点不合适啊。。?
反正的确不好像。关键就在于这个标签。颓完标签就差不多会了。
%%%cbx那么快就想出来了。(2个小时?)
废话多了。
先考虑暴力。对于16的数据范围当然要考虑状压,状态表示每一个位置是否要放兵。
我们只需要考虑左边对右边,上边对下边的贡献,最后把答案×2即可。
然后枚举每一层的状态,逐层转移即可。
复杂度是$O((2^{C})^2 \times C \times R)$,9e12左右
我想到一个没什么用的优化,既然你已经知道了本层的士兵数量,那么那些状态里不合法的就不用枚举了。
预处理一下,复杂度是$O((C_C^{C/2})^2 \times C \times R)$,极端情况3e11左右
但是不要想了,一分也不会多的。
1 #include<cstdio> 2 #include<cstring> 3 #include<vector> 4 #include<iostream> 5 using namespace std; 6 int r,c,num[129],dp[2][1048577],re[1048577][17],loc[1048577][17],scnt[1048577],ANS; 7 char s[129][18]; 8 vector<int>v[17]; 9 int cal(int ro,int lst,int tst){ 10 int ans=0; 11 for(int i=1;i<scnt[tst];++i)if(loc[tst][i]+1==loc[tst][i+1]&&s[ro][i]==s[ro][i+1])ans++; 12 for(int i=1;i<=c;++i)if(lst&1<<i-1&&tst&1<<i-1&&s[ro-1][re[lst][i]]==s[ro][re[tst][i]])ans++; 13 return ans; 14 } 15 int main(){ 16 scanf("%d%d",&r,&c); 17 for(int i=1;i<=r;++i)scanf("%s",s[i]+1),num[i]=strlen(s[i]+1); 18 for(int i=0;i<1<<c;++i){ 19 int cnt=0,j=i,alm=0; 20 while(j)j^=j&-j,cnt++; 21 scnt[i]=cnt; 22 v[cnt].push_back(i); 23 for(int k=1;k<=c;++k)re[i][k]=re[i][k-1]+(i&1<<k-1?1:0); 24 for(int k=1;k<=c;++k)if(i&1<<k-1)loc[i][++alm]=k; 25 }//printf("%d\n",v[1][0]); 26 for(int i=1;i<=r;++i){ 27 memset(dp[i&1],0,sizeof dp[i&1]); 28 for(int j=0;j<v[num[i-1]].size();++j)for(int k=0;k<v[num[i]].size();++k) 29 dp[i&1][v[num[i]][k]]=max(dp[i&1][v[num[i]][k]],dp[i&1^1][v[num[i-1]][j]]+cal(i,v[num[i-1]][j],v[num[i]][k])); 30 } 31 for(int i=0;i<=v[num[r]].size();++i)ANS=max(ANS,dp[r&1][v[num[r]][i]]); 32 printf("%d\n",ANS<<1);//printf("%d\n",cal(2,1,1)); 33 }
复杂度的瓶颈明显就在于$C_{16}^8$或者$2^{16}$的平方上,状压肯定是少不了的但是平方不能有。
也就是必须一次只枚举一个状态进行转移。
找这题的特殊性质,如果依次考虑每个格子,那么dp值是否增加只与左边一位和上边的一位有关。
所以你枚举上面的一整层是多余的。
我们只要知道这一位自身,左边和上面是谁就好了,其余位置并不在意。
而这一位填完之后,上面的那一位就作废了,取而代之的是这一位。。。
所以我们的状态表示的就是当前轮廓线上的每一位有没有放数。。。
具体实现还是比较简单的。需要修改二进制下的某一位,判断二进制下某一位左右各有几个1(知道是第几个就可以判断它到底是谁了)
一个打成函数,一个预处理。
复杂度$O(2^C \times C \times R)$
注意干掉不合法的状态(一行完毕之后发现它填数的个数不够或者是多了)
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 using namespace std; 5 char s[129][18];int n,m,dp[2][65538],cntl[65538][18],cntr[65538][18],ans,l[129]; 6 int chg(int st,int p,int w){ 7 if(!p)return st>>1<<1|w; 8 int r=st&(1<<p)-1; 9 st>>=p+1;st<<=1;st|=w;st<<=p;//printf("%d\n",st); 10 return st|r; 11 } 12 int main(){ 13 scanf("%d%d",&n,&m); 14 for(int i=1;i<=n;++i)scanf("%s",s[i]+1),l[i]=strlen(s[i]+1); 15 for(int i=0;i<1<<m;++i){ 16 for(int j=2;j<=m+1;++j)cntl[i][j]=cntl[i][j-1]+(i&1<<j-2?1:0); 17 for(int j=m-1;j;--j)cntr[i][j]=cntr[i][j+1]+(i&1<<j?1:0); 18 // for(int j=1;j<=m;++j)printf("%d %d %d %d\n",i,j,cntl[i][j],cntr[i][j]); 19 }//return 0; 20 int nw=1,ls=0;memset(dp[nw],0xa0,sizeof dp[nw]);dp[nw][0]=0; 21 for(int i=1;i<=n;++i){ 22 for(int j=1;j<=m;++j){ 23 nw^=1;ls^=1;memset(dp[nw],0xa0,sizeof dp[nw]); 24 for(int st=0;st<1<<m;++st){ 25 char sl=s[i][cntl[st][j]],su=s[i-1][l[i-1]-cntr[st][j]],sT=s[i][cntl[st][j]+1];//printf("%d %d %d %c %c %c\n",i,j,st,sT,sl,su); 26 if(!(st&1<<j-2))sl=0;if(!(st&1<<j-1))su=0; 27 if(sT)dp[nw][chg(st,j-1,1)]=max(dp[nw][chg(st,j-1,1)],dp[ls][st]+(sl==sT)+(sT==su)); 28 dp[nw][chg(st,j-1,0)]=max(dp[nw][chg(st,j-1,0)],dp[ls][st]); 29 } 30 // for(int s=0;s<1<<m;++s)printf("%d %d %d %d\n",i,j,s,dp[nw][s]); 31 } 32 for(int st=0;st<1<<m;++st)if(cntl[st][m+1]!=l[i])dp[nw][st]=0xa0a0a0a0; 33 // int j=m;for(int s=0;s<1<<m;++s)printf("%d %d %d %d\n",i,j,s,dp[nw][s]); 34 } 35 for(int i=0;i<1<<m;++i)ans=max(ans,dp[nw][i]); 36 printf("%d\n",ans*2);//printf("%d\n",chg(1,3,1)); 37 }
没有cbx说的那么好写好调。
他给出的小的容易出锅的样例:
2 1 A A
2 2 A A
3 3 AB AA BA(这个是我出锅的)