BZOJ2595:[Wc2008]游览计划——题解(插头dp)
http://www.lydsy.com/JudgeOnline/problem.php?id=2595
Description
Input
第一行有两个整数,N和 M,描述方块的数目。
接下来 N行, 每行有 M 个非负整数, 如果该整数为 0, 则该方块为一个景点;
否则表示控制该方块至少需要的志愿者数目。 相邻的整数用 (若干个) 空格隔开,
行首行末也可能有多余的空格。Output
由 N + 1行组成。第一行为一个整数,表示你所给出的方案
中安排的志愿者总数目。
接下来 N行,每行M 个字符,描述方案中相应方块的情况:
z ‘_’(下划线)表示该方块没有安排志愿者;
z ‘o’(小写英文字母o)表示该方块安排了志愿者;
z ‘x’(小写英文字母x)表示该方块是一个景点;
注:请注意输出格式要求,如果缺少某一行或者某一行的字符数目和要求不
一致(任何一行中,多余的空格都不允许出现) ,都可能导致该测试点不得分。Sample Input
4 4
0 1 1 0
2 5 5 1
1 5 5 1
0 1 1 0
Sample Output
6
xoox
___o
___o
xoox
参(抄)考(了):http://www.sigongzi.org/index.php/archives/jiyuliantongxingdezhuangtaiyasuodongtaiguihua.html
……原本也不想抄的啊,不然括号表达法还得学独立插头……
打眼一看就是一个插头dp,于是我们选用最小表示法求解。插头dp详见这里。
但是我们最开始学的插头dp只能解决哈密顿回路啊根本没有这道题这么复杂这可怎么办?
别慌,我们慢慢理:
1.显然景点是必须要取的(插头不能为“0”)。
2.显然路径必须是互相联通的线段。
对于1,我们强制不让这个格子不放插头即可。
对于2,我们考虑如果我们当前格子的上插头在轮廓线再没有与之相连通的插头,且同时我们已经扫完了所有景点,此时我们不选当前格子的话我们就将分开成两条路径了,所以必须要取;但是如果是左插头的话如果不取的话是可能符合2的。
然后正常插头dp即可。
(写这道题写了一天的我……)
(如果你插头dpA了这道题可以试一下这个数据):
4 3
0 1 1
10 10 1
10 10 0
10 10 1
ans:
3
xoo
__o
__x
___
#include<cstdio> #include<cstring> using namespace std; const int INF=2147483647; const int mod=999983; const int N=11; const int M=1000005; struct node{//哈希表 int nxt; int state,ans,pos,pre;//状态,答案,编号,前一个状态 bool choose;//是否选择该方块 }edge[M]; int head[mod+1],cnt; int lcnt,rcnt; void insert(int now,int num,int ppos,int last,bool flag){ int u=now%mod; for(int i=head[u];i;i=edge[i].nxt){ if(edge[i].state==now){ if(num<edge[i].ans){ edge[i].ans=num; edge[i].pos=ppos; edge[i].pre=last; edge[i].choose=flag; } return; } } edge[++cnt].nxt=head[u]; head[u]=cnt; edge[cnt].state=now; edge[cnt].ans=num; edge[cnt].pos=ppos; edge[cnt].pre=last; edge[cnt].choose=flag; return; } bool g[N][N]; int w[N],cntt; int n,m,lastedge,e1,e2; int mp[N][N]; int mapp[N]; inline void decode(int now){ for(int i=m;i>=1;i--){ w[i]=now&7; now>>=3; } return; } inline int encode(){ int x=0,tot=0; memset(mapp,-1,sizeof(mapp)); mapp[0]=0; for(int i=1;i<=m;i++){ if(mapp[w[i]]==-1)mapp[w[i]]=++tot; w[i]=mapp[w[i]]; x=x<<3|w[i]; } return x; } inline void init(){ memset(head,0,sizeof(head)); lcnt=rcnt+1;rcnt=cnt; return; } inline void getans(){ for(int k=lcnt;k<=rcnt;k++){ int now=edge[k].state; int num=edge[k].ans; decode(now); bool flag=1; for(int l=1;l<=m&&flag;l++){ if(w[l]>1)flag=0; } if(num<cntt&&flag){ cntt=num; lastedge=k; } } return; } void plugdp(){ insert(0,0,101,0,0); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ init(); if(i>e1||(i==e1&&j>e2))getans();//当前状态可能构成一种方案 for(int k=lcnt;k<=rcnt;k++){ int now=edge[k].state; int num=edge[k].ans; decode(now); int is_right=w[j-1];//这个格子左面格子的右插头 int is_down=w[j];//这个格子上面格子的下插头 bool flag=0;//判断是否可以放插头,1可以不放 if(!mp[i][j])flag=0;//景点必须放 else if(!is_down)flag=1;//下插头不需要再匹配,故可以不放。 else{ flag=0; for(int l=1;l<=m&&!flag;l++){ if(l!=j&&w[l]==is_down)flag=1; //上插头已经与轮廓线的一端匹配,可以在那一端延伸。 } //没有匹配的插头,必须要接上 } if(flag){//不放插头 w[j]=0;//下插头没有用了 insert(encode(),num,(i-1)*m+j,k,0); w[j]=is_down; } //放插头 if(!is_right&&!is_down)w[j]=7;//一个新插头 else if(!is_down&&is_right)w[j]=is_right;//右插头延续过来 else if(is_right!=is_down&&is_right&&is_down){ for(int l=1;l<=j;l++){//不太好说,但画个图应该就理解了 if(w[l]==is_right)w[l]=is_down; } } //如果下插头和右插头匹配那么就把它们连起来 insert(encode(),num+mp[i][j],(i-1)*m+j,k,1); } } } init(); getans(); return; } void getans(int k){ if(!k)return; int r=edge[k].pos; int l=(r-1)/m+1;r=(r-1)%m+1; g[l][r]=edge[k].choose; getans(edge[k].pre); return; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ scanf("%d",&mp[i][j]); if(!mp[i][j])e1=i,e2=j; } } cntt=INF; plugdp(); printf("%d\n",cntt); getans(lastedge); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(!mp[i][j])putchar('x'); else if(g[i][j])putchar('o'); else putchar('_'); } putchar('\n'); } return 0; }