您的位置:首页 > 其它

BZOJ.2595.[WC2008]游览计划(DP 斯坦纳树)

2018-07-18 22:56 357 查看
题目链接

f[i][s]表示以i为根节点,当前关键点的连通状态为s(每个点是否已与i连通)时的最优解。i是枚举得到的根节点,有了根节点就容易DP了。

那么i为根节点时,其状态s的更新为 \(f[i][s]=min\{f[i][s']+f[i][\complement_{s}s']-cost[i]\},s'\in s\)(枚举子集s'后,显然只需要s'的补集。减cost[i]是因为两种状态都包含,cost[i]算重了)

如果我们想合并入当前连通块一个新的非关键点v并以v为根,那么 \(f[v][s]=min\{f[k][s]+cost[v]\},k,v相邻\)

第一个更新可以按顺序,第二个更新没有明显顺序,但是如果固定状态s,很像SPFA,可类似转移。

输出方案,可以每次转移记录转移前的点与状态s,因为可能是转移点也可能是用子集更新。最后随便找一个关键点开始DFS即可。

复杂度 \(O(n\times 3^k+cE\times 2^k)\)

c为SPFA复杂度中的常数,E为边的数量,但几乎达不到全部边的数量,甚至非常小。\(3^k\)来自于子集的转移\(\sum_{i=1}^nC_n^i\times 2^i\),用二项式展开求一下和。

//2080kb    148ms
#include <queue>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#define gc() getchar()
#define En(i,j) (i*m+j)//Encode
#define De(w) mp(w/m,w%m)//Decode
#define mp std::make_pair
#define pr std::pair<int,int>
const int N=102,INF=1e9,to[5]={1,0,-1,0,1};

int n,m,cost
,f[(1<<10)+1]
;//换下了顺序 注意!
pr pre[(1<<10)+1]
;
bool inq
,vis
;
std::queue<int> q;

inline int read()
{
int now=0;register char c=gc();
for(;!isdigit(c);c=gc());
for(;isdigit(c);now=now*10+c-'0',c=gc());
return now;
}
void SPFA(int *f,pr *pre,int s)
{
while(!q.empty())
{
int now=q.front();
q.pop(), inq[now]=0;
for(int x=now/m,y=now%m,xn,yn,nxt,i=0; i<4; ++i)
if((xn=x+to[i])>=0&&xn<n&&(yn=y+to[i+1])>=0&&yn<m && f[nxt=En(xn,yn)]>f[now]+cost[nxt])
{
f[nxt]=f[now]+cost[nxt], pre[nxt]=mp(now,s);
if(!inq[nxt]) inq[nxt]=1, q.push(nxt);
}
}
}
void DFS(int p,int s)
{
if(!pre[s][p].second) return;//pre.second即 无转移了
vis[p]=1;
if(pre[s][p].first==p) DFS(p,s^pre[s][p].second);
DFS(pre[s][p].first,pre[s][p].second);
}

int main()
{
n=read(), m=read(); int K=0, rt=0;
memset(f,0x3f,sizeof f);
for(int tot=0,i=0; i<n; ++i)
for(int j=0; j<m; ++j,++tot)
{
cost[tot]=read();
if(!cost[tot]) f[1<<(K++)][tot]=0, rt=tot;
}
for(int s=1; s<(1<<K); ++s)
{
for(int i=0; i<n*m; ++i)
{
for(int sub=(s-1)&s; sub; sub=(sub-1)&s)
if(f[s][i]>f[sub][i]+f[sub^s][i]-cost[i])
f[s][i]=f[sub][i]+f[sub^s][i]-cost[i], pre[s][i]=mp(i,sub);
if(f[s][i]<INF) q.push(i), inq[i]=1;//多起点,inq[]还是不能省啊
}
SPFA(f[s],pre[s],s);
}
printf("%d\n",f[(1<<K)-1][rt]);
DFS(rt,(1<<K)-1);
for(int i=0,tot=0; i<n; ++i,putchar('\n'))
for(int j=0; j<m; ++j,++tot)
if(!cost[tot]) putchar('x');
else putchar(vis[tot]?'o':'_');

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: