题目描述
从未来过绍兴的小D有幸参加了Winter Camp 2008,他被这座历史名城的秀丽风景所吸引,强烈要求游览绍兴及其周边的所有景点。
主办者将绍兴划分为N行M列(N×M)个分块,如下图(8×8):
景点含于方块内,且一个方块至多有一个景点。无景点的方块视为路。
为了保证安全与便利,主办方依据路况和治安状况,在非景点的一些方块内安排不同数量的志愿者;在景点内聘请导游(导游不是志愿者)。在选择旅游方案时,保证任意两个景点之间,存在一条路径,在这条路径所经过的每一个方块都有志愿者或者该方块为景点。既能满足选手们游览的需要,又能够让志愿者的总数最少。
例如,在上面的例子中,在每个没有景点的方块中填入一个数字,表示控制该方块最少需要的志愿者数目:
图中用深色标出的方块区域就是一种可行的志愿者安排方案,一共需要20名志愿者。由图可见,两个相邻的景点是直接(有景点内的路)连通的(如沈园和八字桥)。
现在,希望你能够帮助主办方找到一种最好的安排方案。
输入输出格式
输入格式:
第一行有两个整数,N和M,描述方块的数目。
接下来N行,每行有M个非负整数,如果该整数为0,则该方块为一个景点;
否则表示控制该方块至少需要的志愿者数目。相邻的整数用(若干个)空格隔开,
行首行末也可能有多余的空格。
输出格式:
由N+1行组成。第一行为一个整数,表示你所给出的方案中安排的志愿者总数目。
接下来N行,每行M个字符,描述方案中相应方块的情况:
‘_’(下划线)表示该方块没有安排志愿者;
‘o’(小写英文字母o)表示该方块安排了志愿者;
‘x’(小写英文字母x)表示该方块是一个景点;
注:请注意输出格式要求,如果缺少某一行或者某一行的字符数目和要求不一致(任何一行中,多余的空格都不允许出现),都可能导致该测试点不得分。
输入输出样例
输入样例#1:
4 4
0 1 1 0
2 5 5 1
1 5 5 1
0 1 1 0
输出样例#1:
6
xoox
___o
___o
xoox
说明
所有的 10 组数据中 N, M ,以及景点数 K 的范围规定如下
输入文件中的所有整数均不小于 0 且不超过 2^16
分析:在看图论是看到的一种神奇算法。其实是求n个点中,使得k个关键点相互联通的生成树(只要求这k个点连通即可)。不能用最小生成树做的原因是,假设只有x,y两个关键点,他们的最短路不一定是在最小生成
树上。如果跑出两两关键点最短路,再搞最小生成树,就会出现重边的情况,也是不可行的。所有用到一种dp的想法。
设f[s][i]为关键点连通状态为s,以i为根的生成树的最小代价。有
第一种转移:
f[s][i]=min(f[sub][i]+f[s-sub][j],f[s][i])
(其中sub为s的一个子集,s-sub为改子集相对与s的补集)
此处表示合并两棵子树,如果是点权(比如本题),就要减去i的点权(根节点算了两次)
第二种:
f[s|g[j]][j]=min(f[s|g[j]][j],f[s][i]+val[j])
(如果j为关键点,我们给关键点编号,第k个关键点的g[j]=2^(k-1),如果i不是关键点,那么g[j]=0)
此处表示把以i为根的子树进行通过边(i,j)延伸,变为以j为根的子树,假如j为关键点,那么状态s就会发生变化。可以用spfa转移。
两种变换相互独立,可以先第一种转移,后第二种转移,也可以反过来。
如果以s划分层,那么第一种相当与从下面的层转移到上面的层,第二种就是同层转移。也就是说,如果
s|g[j]<>s,那么这个状态就到后面的层再同层转移了。
代码:
// luogu-judger-enable-o2
#include <iostream>
#include <cstdio>
#include <cmath>
#include <queue>
#include <algorithm>
const int maxn=12;
const int inf=0x3f3f3f3f;
const int dx[4]={0,1,0,-1};
const int dy[4]={1,0,-1,0};
using namespace std;
char ans[maxn][maxn];
int a[maxn][maxn],f[1<<maxn][maxn][maxn],g[maxn][maxn];
bool vis[maxn][maxn];
int n,m,k,c;
struct node{
int x1,y1,s1;
int x2,y2,s2;
}next[1<<maxn][maxn][maxn];
struct rec{
int x,y;
};
queue <rec> q;
void make()
{
for (int i=1;i<=n;i++)
{
for (int j=1;j<=m;j++)
{
for (int s=0;s<(1<<maxn);s++) f[s][i][j]=inf;
}
}
}
void spfa(int s)
{
while (!q.empty())
{
rec t=q.front();
q.pop();
int i=t.x,j=t.y;
for (int l=0;l<4;l++)
{
int x=i+dx[l],y=j+dy[l];
if ((x>0) && (x<=n) && (y>0) && (y<=m))
{
if (f[s][i][j]+a[x][y]<f[s|g[x][y]][x][y])
{
f[s|g[x][y]][x][y]=f[s][i][j]+a[x][y];
next[s|g[x][y]][x][y].x1=i;
next[s|g[x][y]][x][y].y1=j;
next[s|g[x][y]][x][y].s1=s;
next[s|g[x][y]][x][y].x2=0;
next[s|g[x][y]][x][y].y2=0;
next[s|g[x][y]][x][y].s2=0;
if ((s|g[x][y]==s) && (!vis[x][y]))
{
vis[x][y]=1;
rec t=(rec){x,y};
q.push(t);
}
}
}
}
vis[i][j]=0;
}
}
void dfs(int s,int x,int y)
{
if ((!s) && (!x) && (!y)) return;
if (ans[x][y]!='x') ans[x][y]='o';
dfs(next[s][x][y].s1,next[s][x][y].x1,next[s][x][y].y1);
dfs(next[s][x][y].s2,next[s][x][y].x2,next[s][x][y].y2);
}
int main()
{
scanf("%d%d",&n,&m);
make();
for (int i=1;i<=n;i++)
{
for (int j=1;j<=m;j++)
{
scanf("%d",&a[i][j]);
if (!a[i][j])
{
ans[i][j]='x';
f[1<<k][i][j]=0;
g[i][j]=1<<k;
k++;
}
else
{
f[0][i][j]=a[i][j];
ans[i][j]='_';
}
}
}
for (int s=1;s<(1<<k);s++)
{
for (int i=1;i<=n;i++)
{
for (int j=1;j<=m;j++)
{
for (int sub=s;sub;sub=(sub-1)&s)
{
if (f[sub][i][j]+f[s-sub][i][j]-a[i][j]<f[s][i][j])
{
f[s][i][j]=f[sub][i][j]+f[s-sub][i][j]-a[i][j];
next[s][i][j].x1=i;
next[s][i][j].y1=j;
next[s][i][j].s1=sub;
next[s][i][j].x2=i;
next[s][i][j].y2=j;
next[s][i][j].s2=s-sub;
}
}
vis[i][j]=0;
if (f[s][i][j]!=inf)
{
rec t=(rec){i,j};
q.push(t);
vis[i][j]=1;
}
}
}
spfa(s);
}
c=inf;
int s,x,y;
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
{
if (f[(1<<k)-1][i][j]<c)
{
c=f[(1<<k)-1][i][j];
s=(1<<k)-1;
x=i;
y=j;
}
}
printf("%d\n",c);
dfs(s,x,y);
for (int i=1;i<=n;i++)
{
for (int j=1;j<=m;j++) printf("%c",ans[i][j]);
printf("\n");
}
}