题解 P2337【[SCOI2012]喵星人的入侵】
xtx1092515503 · · 题解
本文的实现要比常规插头 DP 要复杂(即分类讨论的情形更多)。但是,其思想(大概)会比常规插头DP要更加简单。一句话,本文比较无脑硬上。同时,码量在删去注释后应该比常规插头 DP 有优势(因为除了分类讨论外细节不多)。
首先先默认
一个简单的观察是必定存在一种最优状态使得路径唯一——因为障碍数量无限制,所以自然可以堵死那些敌人不会去的地方。这样便是有关路径的统计,又是网格图,考虑插头 DP。
但是我们发现插头 DP 并不好,因为并非所有信息都能用插头表示,一个典型的例子是格子中有无炮台。因此,我们考虑压缩轮廓线周围的方格而非插头。
因为一个炮台影响八连通的格子,所以一个显然的想法是压缩
然后我们考虑状态的每一位填什么、各表示什么意义:
- 这个格子放置了障碍。
- 这个格子是路径中某一段的左端点。
- 这个格子是路径中某一段的右端点。
- 这个格子在路径上,但不是端点。
- 这个格子是端点,但是此段路径的另一个端点是起点/终点。
- 这个格子放置了炮台。
- 这个格子是某一段路径的起点。(也即,其同时是1和2)。
除了状态6其它都比较好理解,唯独状态6比较奇怪。但是事实上仔细一想这是正常的,因为我们记录的是方格状态而非插头状态,如果记录的是插头那么6就可以用一个下插头+一个右插头表示,但是方格就只能老老实实新建状态表示。
因为仅仅是编号就太抽象了,所以我们将所有状态用一个形容词来修饰一下:
- 0&5,这两个状态是“绝缘
insulated”的,它不会产生任何互动(当然,5会对周边的格子有辐射,但是这不会影响格子的状态,仅仅影响答案罢了)。 - 3,这个状态是“惰性
inert”的,它不会扩展,周边也不应该出现其它非绝缘状态(不然路径就会不唯一)。 - 6,这个状态是“多动
hyperactive”的(实际上我本来想用“活泼”来修饰,但是hyperactive这个词太有韵味了,hyper-前缀除了“过于活泼”也有“虚拟”的意思,刚好表示状态6实际上是不存在的,其真实意义是1和2挤在一个格子中)。它在产生的下一轮DP中,就会立马“裂变”变回1和2。同时,“多动”也表示它自然产生条件严苛(周边只能存在绝缘的格子)。 - 4,这个状态是“寄生
parasitic”的。它自然产生条件非常严苛(和6一样),但是却可以依托某段路径产生。并且,它会把一整段1-2路径直接寄生成4路径。 - 1&2,这两个状态是“平凡
mundane”的,它没有过多的特征。
用8进制压缩7进制是老生常谈了。
附:我最近找到一种形象地给网格图上变量取名的好方法。我们可以看向键盘。
QWERTYUIOP ASDFGHJKL ZXCVBNM假如我们当前关心的格子是
J的话,就可以轻松得到其它方位的变量的命名:左侧是H,左上是Y,上方是U,右上是I。这几个位置是我们决策J时需要知道的信息。至于为什么选择
J而不选其它字符嘛,因为其它字符周边的区域中的大写字符已经被我占用了。
甚至可以出一道题目:键盘上有一些大写字符已经被占用,求最多能挖出几块这样的区域。
然后发现这也可以用轮廓线DP解决。
好那么我们现在考虑转移。
首先,当前格 J 填入非绝缘状态(即是路径的一部分)和填入5时对答案的贡献可以由 H,Y,U,I 四个状态处理得到。
然后,我们考虑转移。
首先先考虑 H,U 的状态延伸而来的情形。
-
考虑6以及4(在不寄生一条现存路径的时候)的产生(二者条件相同)。我们上文已经提到其产生条件苛刻,即须满足
H,U绝缘且当前格子非障碍。此时,若当前格子非起讫点,则可填入6。否则,即其是起讫点,则可填入4。 -
考虑填入绝缘状态的情形。其限制是当前格子非起讫点、
H不能填入多动的6(因为6在产生后会立马裂变成为1和2,因此如果当前点是绝缘状态的话就会阻止6的裂变),以及U只能填入绝缘的0&5或者惰性的3——因为非绝缘、非惰性的状态是总要延伸的,不往右边延伸就往下边延伸,而U往右边延伸的路已经被I堵死了,所以非绝缘、非惰性的U必须往下延伸,则当前格子不能绝缘。此时,无论如何都可以填入0,而当当前格子非障碍时就可填入5。
其它所有状态都是由 H,U 的状态延伸或拼接而成的。所以这时,如果格子有障碍,就可以直接 continue 了——障碍格显然不能成为延伸或拼接的去处。
- 考虑
U非绝缘非惰性,即其必须延伸的情形。- 考虑
H非绝缘,即延伸的U必然会导致两条路径交汇的情形。- 当前格子是起讫点。此时就会发生从当前格子出发有两条不同路径的情形,因此
continue。 H=3:因为此时H非端点,所以就会出现路径不唯一的状况,因此状态不合法,直接continue掉。Y非绝缘,此时就会出现四个相邻位置成团的现象,状态不合法,直接continue。U=6,这个状态不可能出现(因为6一出现就会裂变),直接continue。H=1,U=1 或 H=2,U=2,这两个状态同常规插头DP类似,合并后修改其中一条路径的另一端点即可。注意合并完后HJU都变成3(路径的非端点部分)。H=1,U=2,这时路径成环,continue。H=2,U=1,同常规插头DP一致,直接合并。注意合并完HJU都变成3。H=4,U=4,这时相当于完整的一条路径被拼接成了,直接合并,HJU变成3。H=4,U=1 或 H=1,U=4,这两个状态相似,把与其中的1对应的2赋成4、HJU变成3即可。H=4,U=2 或 H=2,U=4,这两个状态相似,把与其中的2对应的1赋成4、HJU变成3即可。H=6,这时就把它当作1与2的合体计算即可。
- 当前格子是起讫点。此时就会发生从当前格子出发有两条不同路径的情形,因此
- 考虑
H绝缘,只有U延伸的情形。- 考虑当前格子是起讫点。如果
U是1或2就修改对应另一端点为4,JU变成3;如果U也是4就直接JU变成3即可。 - 考虑当前格子非起讫点。此时就把
J赋作U,U赋作3即可。
- 考虑当前格子是起讫点。如果
- 考虑
- 剩余情形下,就是
U无法延伸的状态了。因为两个格子均不延伸的情形我们已经讨论过了,所以这里只需讨论H延伸的情形。此时要求H非绝缘,非惰性。U=3。同上,路径不唯一,continue。- 剩下的就跟
H绝缘U延伸的转移差不多了。唯一需要注意的是H可能为6,此时就当作1和2同时存在即可。
不重不漏,讨论完毕。
代码(附有详细注释):
#include<bits/stdc++.h>
using namespace std;
const int fni=0xc0c0c0c0;
int n,m,p,hs[2100000],sta[50000],cnt,res;
void pr(int x){for(int i=0;i<=m;i++)printf("%d",(x>>i*3)&7);}
int f[2][16][50000];
//0:block 1:left plug 2:right plug 3:part of the route 4:free plug 5:cannon 6:bi-direction plug
//0&5 are 'insulated',which do not act with any state.
//3 is 'inert',which cannot expand.
//6 is 'hyperactive',which has the necessity of expanding in both directions.
//4 is 'parasitic',which need a 1/2 plug to produce, otherwise it is as difficult as 6 when to be produced.
//1&2 are 'mundane',which do not have strong characteristics.
//all plugs except the 'insulated' 0&5 and the 'inert' 3, once put above, will always expand.
char s[30][30];
int num[30];
void dfs1(int pos,int msk,int fre,int tms){
if(pos==m+1){
if(tms)return;
for(int i=1;i<pos;i++){
if((num[i]==1||num[i]==2||num[i]==4)&&num[i-1]==3&&num[i+1]==3)return;
if(num[i]==6&&(num[i-1]==3||num[i+1]==3))return;
}
cnt++,hs[msk]=cnt,sta[cnt]=msk;
return;
}
num[pos]=0;dfs1(pos+1,msk,fre,tms);
num[pos]=3,dfs1(pos+1,msk+(3<<pos*3),fre,tms);
num[pos]=5;dfs1(pos+1,msk+(5<<pos*3),fre,tms);
if(!pos||num[pos-1]==0||num[pos-1]==5){
num[pos]=1,dfs1(pos+1,msk+(1<<pos*3),fre,tms+1);
num[pos]=6,dfs1(pos+1,msk+(6<<pos*3),fre,tms);
}
if(!pos||(num[pos-1]!=2&&num[pos-1]!=6&&num[pos-1]!=4))num[pos]=2,dfs1(pos+1,msk+(2<<pos*3),fre,tms-1);
if((!pos||num[pos-1]==0||num[pos-1]==5||num[pos-1]==3)&&fre<2)num[pos]=4,dfs1(pos+1,msk+(4<<pos*3),fre+1,tms);
}
int nex(int y,int k){
if(y==m-1){
int L=k>>m*3;k-=(L<<m*3);
if(L!=0&&L!=3&&L!=5)return 0;
return hs[k<<3];
}
return hs[k];
}
int P=0,Q=1;
void chmx(int j,int w,int id,int nw,int K,int bon=0){
int nid=nex(j,K);
// printf("%d:",nid),pr(K),puts("");
if(nid&&nw<=p)f[Q][nw][nid]=max(f[Q][nw][nid],f[P][w][id]+bon);
}
int nex2(int y,int K){
for(int i=y,j=0;i<=m;i++){
if(((K>>i*3)&7)==1)j++;
if(((K>>i*3)&7)==2&&!j--)return i;
}
}
int las1(int y,int K){
for(int i=y,j=0;i>=0;i--){
if(((K>>i*3)&7)==2)j++;
if(((K>>i*3)&7)==1&&!j--)return i;
}
}
int main(){
scanf("%d%d%d",&n,&m,&p);
for(int i=0;i<n;i++)scanf("%s",s[i]);
if(n<m){
for(int i=0;i<n;i++)for(int j=i+1;j<m;j++)swap(s[i][j],s[j][i]);
swap(n,m);
}
dfs1(0,0,0,0);
// printf("%d\n",cnt);
// for(int i=1;i<=cnt;i++)pr(sta[i]),puts("");
memset(f[P],fni,sizeof(f[P]));
f[P][0][hs[0]]=0;
for(int i=0;i<n;i++)for(int j=0;j<m;j++,P^=1,Q^=1){
memset(f[Q],fni,sizeof(f[Q]));
for(int w=0;w<=p;w++)for(int id=1;id<=cnt;id++){
if(f[P][w][id]<0)continue;
int k=sta[id];
// printf("(%d,%d)[%d,%d]",i,j,w,id);pr(k);printf(":%d\n",f[P][w][id]);
int H=0,Y=(k>>j*3)&7,U=(k>>(j+1)*3)&7,I=(k>>(j+2)*3)&7;
if(U==6)continue;
//impossible to have 6 above, as 'hyperactive' 6 is 'hyper', will immediately fission into 1&2.
int K=k-(Y<<j*3),KHU=K;
K-=(U<<(j+1)*3);int KH=K;
if(j)H=((k>>(j-1)*3)&7),K-=(H<<(j-1)*3);
int bon=(H==5)+(Y==5)+(U==5)+(I==5);
int nob=(H!=0&&H!=5)+(Y!=0&&Y!=5)+(U!=0&&U!=5)+(I!=0&&I!=5);
if((H==0||H==5)&&(U==0||U==5)&&s[i][j]!='#'){
//situation of producing 'hyperactive' 6 and 'parasitic' 4(when it does not parasitize)
if(s[i][j]=='S'||s[i][j]=='T')chmx(j,w,id,w,KHU+(4<<j*3),bon);
else chmx(j,w,id,w,KHU+(6<<j*3),bon);
}
if(H!=6&&(U==0||U==5||U==3)&&s[i][j]!='S'&&s[i][j]!='T'){
//situation where it is possible to put 'insulated' 0&5 in.
chmx(j,w,id,w,KHU);//putting 0.
if(s[i][j]!='#')chmx(j,w,id,w+1,KHU+(5<<j*3),nob);//putting 5.
}
if(s[i][j]=='#')continue;//the only operation under this circumstance is putting 0.
if(U!=0&&U!=5&&U!=3){//situation when U has to expand.
if(H!=0&&H!=5){//two plugs meet.
if(s[i][j]=='S'||s[i][j]=='T')continue;//there can't be two different routes around endpoints.
if(H==3)continue;//H has been on a complete route.
if(Y!=0&&Y!=5)continue;//a 'cancer' forms, which is invalid.
if(H==1&&U==1)chmx(j,w,id,w,K+(3<<(j-1)*3)+(3<<j*3)+(3<<(j+1)*3)-(1<<nex2(j,K)*3),bon);
//two 1s will 'alpha-fusion', turing a 2 into 1, and leaving three 3s.
if(H==1&&U==2)continue;//1&2 will 'circle', which is invalid.
if(H==2&&U==1)chmx(j,w,id,w,K+(3<<(j-1)*3)+(3<<j*3)+(3<<(j+1)*3),bon);
//2&1 will 'beta-fusion',only leaving three 3s.
if(H==2&&U==2)chmx(j,w,id,w,K+(3<<(j-1)*3)+(3<<j*3)+(3<<(j+1)*3)+(1<<las1(j,K)*3),bon);
//two 2s will 'gamma-fusion', turing an 1 into 2, and leaving three 3s.
if(H==6&&U==1)chmx(j,w,id,w,K+(1<<(j-1)*3)+(3<<j*3)+(3<<(j+1)*3),bon);
if(H==6&&U==2)chmx(j,w,id,w,K+(2<<(j-1)*3)+(3<<j*3)+(3<<(j+1)*3),bon);
//6 shall be seen as 1+2.
if(H==4&&U==4){chmx(j,w,id,w,K+(3<<(j-1)*3)+(3<<j*3)+(3<<(j+1)*3),bon);continue;}
//two 'parasitic' 4, forming a complete route.
if(H==4&&U==1||H==1&&U==4)chmx(j,w,id,w,K+(3<<(j-1)*3)+(3<<j*3)+(3<<(j+1)*3)+(2<<nex2(j,K)*3),bon);
if(H==4&&U==2||H==2&&U==4)chmx(j,w,id,w,K+(3<<(j-1)*3)+(3<<j*3)+(3<<(j+1)*3)+(3<<las1(j,K)*3),bon);
if(H==6&&U==4)chmx(j,w,id,w,K+(4<<(j-1)*3)+(3<<j*3)+(3<<(j+1)*3),bon);
//4 going parasitize.
continue;
}
if(s[i][j]=='S'||s[i][j]=='T'){
if(U==1)chmx(j,w,id,w,KH+(3<<j*3)+(3<<(j+1)*3)+(2<<nex2(j,K)*3),bon);
if(U==2)chmx(j,w,id,w,KH+(3<<j*3)+(3<<(j+1)*3)+(3<<las1(j,K)*3),bon);
if(U==4)chmx(j,w,id,w,KH+(3<<j*3)+(3<<(j+1)*3),bon);
continue;
}
chmx(j,w,id,w,KH+(U<<j*3)+(3<<(j+1)*3),bon);
continue;
}
if(H!=0&&H!=5&&H!=3){
if(U==3)continue;
if(s[i][j]=='S'||s[i][j]=='T'){
if(H==1)chmx(j,w,id,w,K+(3<<(j-1)*3)+(3<<j*3)+(2<<nex2(j,K)*3)+(U<<(j+1)*3),bon);
if(H==2)chmx(j,w,id,w,K+(3<<(j-1)*3)+(3<<j*3)+(3<<las1(j,K)*3)+(U<<(j+1)*3),bon);
if(H==4)chmx(j,w,id,w,K+(3<<(j-1)*3)+(3<<j*3)+(U<<(j+1)*3),bon);
if(H==6)chmx(j,w,id,w,K+(4<<(j-1)*3)+(3<<j*3)+(U<<(j+1)*3),bon);
continue;
}
if(H==1||H==2||H==4)chmx(j,w,id,w,K+(3<<(j-1)*3)+(H<<j*3)+(U<<(j+1)*3),bon);
else chmx(j,w,id,w,K+(1<<(j-1)*3)+(2<<j*3)+(U<<(j+1)*3),bon);
}
}
}
for(int j=1;j<=cnt;j++){
bool ok=true;
int k=sta[j];
for(int t=0;t<=m;t++)if(((k>>t*3)&7)!=0&&((k>>t*3)&7)!=3&&((k>>t*3)&7)!=5){ok=false;break;}
if(!ok)continue;
for(int i=0;i<=p;i++)res=max(res,f[P][i][j]);
}
printf("%d\n",res);
// for(int i=1;i<=cnt;i++)pr(sta[i]),puts("");
return 0;
}