神仙题。但是难得的傻孩子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 }
用作对拍的T40

复杂度的瓶颈明显就在于$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 }
View Code

没有cbx说的那么好写好调。

他给出的小的容易出锅的样例:

2 1 A A

2 2 A A

3 3 AB AA BA(这个是我出锅的)

02-13 15:25