斯坦纳树学习笔记
例1、\(\texttt{P6192 【模板】最小斯坦纳树}\)
题目描述
给定一张 \(n\) 个点, \(m\) 条边的带权无向连通图,和 \(k\) 个关键点构成的集合 \(S=\{u_1,\cdots,u_k\}\) ,求让这些点连通所需边权和的最小值。
显然最优解是一棵树(否则任意去掉环上一条边仍然连通),称为最小斯坦纳树。
数据范围
- \(1\le n\le 100,1\le m\le 500,1\le k\le 10,1\le w\le 10^6\) 。
时间限制 \(\texttt{1s}\) ,空间限制 \(\texttt{250MB}\) 。
分析
无根树是没法转移的,但是任意钦定一个根并不会影响答案。
考虑状压 \(\texttt{DP}\) ,dp[s][i] 表示包含 \(s\) 中关键点,树根为 \(i\) 的最小代价。
你也可以理解成连通 \(\{u_j\mid s\And 2^j\neq 0\}\cup\{i\}\) 的最小代价。
- \(deg_i=1\) :枚举与 \(i\) 相邻的点 \(j\) , \(dp_{s,i}\gets dp_{s,j}+w(i,j)\) 。
- \(deg_i\gt 1\) :我们可以看成多棵以 \(i\) 为根且 \(deg_i=1\) 的树通过节点 \(i\) "粘" 在一起,枚举第一棵树覆盖的关键点集合 \(t\) , \(dp_{s,i}\gets dp_{s,i}+dp_{s/t,i}\) 。
升序枚举 \(s\) ,先枚举子集处理第二类转移,第一类转移可以看成源点 \(i\) 有初始代价 \(dp_{s,i}\) ,通过 \(dijkstra\) 算法对整张图进行松弛即可。
状态设计时 \(s\) 在前 \(i\) 在后,第二类转移先枚举 \(t\) 再枚举 \(i\) ,对缓存最友好。
初值 \(dp_{2^j,u_j}=0\) ,答案为 \(\min\limits_{1\le i\le n}dp_{2^k-1,i}\) 。
时间复杂度 \(\mathcal O(3^kn+2^k(n+m)\log (n+m))\) 。
#include<bits/stdc++.h>
#define fi first
#define se second
#define mp make_pair
#define pii pair<int,int>
using namespace std;
int k,m,n,u,v,w,res=1e9;
int dp[1<<10][105];
bool vis[105];
vector<pii> g[105];
void dijkstra(int s)
{
priority_queue<pii,vector<pii>,greater<pii>> q;
for(int i=1;i<=n;i++) vis[i]=0,q.push(mp(dp[s][i],i));
while(!q.empty())
{
int u=q.top().se; q.pop();
if(vis[u]) continue; vis[u]=1;
for(auto [v,w]:g[u])
if(dp[s][v]>dp[s][u]+w)
dp[s][v]=dp[s][u]+w,q.push(mp(dp[s][v],v));
}
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;i++) scanf("%d%d%d",&u,&v,&w),g[u].push_back(mp(v,w)),g[v].push_back(mp(u,w));
memset(dp,0x3f,sizeof(dp));
for(int i=0;i<k;i++) scanf("%d",&u),dp[1<<i][u]=0;
for(int s=1;s<1<<k;s++)
{
for(int t=s;t;t=(t-1)&s) for(int i=1;i<=n;i++) dp[s][i]=min(dp[s][i],dp[t][i]+dp[s^t][i]);
dijkstra(s);
}
for(int i=1;i<=n;i++) res=min(res,dp[(1<<k)-1][i]);
printf("%d\n",res);
return 0;
}
例2、\(\texttt{P4294 [WC2008] 游览计划}\)
题目描述
一张 \(n\times m\) 的网格图,有 \(k\) 个关键点(权值为零),其余为非关键点(权值为正)。
每个点仅与上下左右四个点相邻,求让这 \(k\) 个关键点连通所需最小点权和,输出方案。
数据范围
- \(1\le n,m\le 10,0\le k\le 10,0\le w\le 2^{16}\) 。
时间限制 \(\texttt{1s}\) ,空间限制 \(\texttt{128MB}\) 。
分析
标准的最小斯坦纳树求的是边权和,但本题是点权。
第一类转移不变,第二类转移 \(i\) 的权值会算两次,改为 \(dp_{s,i}\gets dp_{t,i}+dp_{s/t,i}-w_i\) 即可。
为了输出方案,我们还要记录每个状态的前驱。
- 对第一类转移,记录 \((s,j)\) ,回溯时将 \(i\) 打标记然后递归 \((s,j)\) 。
- 对第二类转移,记录 \((t,i)\) ,回溯时递归 \((s,i)\) 和 \((s/t,i)\) 。
时间复杂度 \(\mathcal O(3^knm+2^knm\log(nm))\) 。
#include<bits/stdc++.h>
#define fi first
#define se second
#define mp make_pair
#define pii pair<int,int>
using namespace std;
const int maxn=105;
int k,m,n,res;
vector<int> g[maxn];
int val[maxn];
int dp[1<<10][maxn];
pii pre[1<<10][maxn];
bool vis[maxn];
int dx[4]={-1,1,0,0};
int dy[4]={0,0,-1,1};
int get_id(int x,int y)
{
return (x-1)*m+y;
}
void dijkstra(int s)
{
priority_queue<pii,vector<pii>,greater<pii>> q;
for(int i=1;i<=n*m;i++) vis[i]=0,q.push(mp(dp[s][i],i));
while(!q.empty())
{
int u=q.top().se; q.pop();
if(vis[u]) continue; vis[u]=1;
for(auto v:g[u])
if(dp[s][v]>dp[s][u]+val[v])
dp[s][v]=dp[s][u]+val[v],pre[s][v]=mp(s,u),q.push({dp[s][v],v});
}
}
void dfs(int s,int u)
{
if(!u) return ;
vis[u]=1;
if(pre[s][u].se!=u) dfs(pre[s][u].fi,pre[s][u].se);
else dfs(pre[s][u].fi,u),dfs(s^pre[s][u].fi,u);
}
int main()
{
scanf("%d%d",&n,&m);
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=n*m;i++)
{
scanf("%d",&val[i]);
if(!val[i]) dp[1<<(k++)][i]=0;
}
for(int x=1;x<=n;x++)
for(int y=1;y<=m;y++)
for(int k=0;k<=3;k++)
{
int nx=x+dx[k],ny=y+dy[k];
if(nx>=1&&nx<=n&&ny>=1&&ny<=m) g[get_id(x,y)].push_back(get_id(nx,ny));
}
for(int s=1;s<1<<k;s++)
{
for(int t=s;t;t=(t-1)&s)
for(int i=1;i<=n*m;i++)
if(dp[s^t][i]+dp[t][i]-val[i]<dp[s][i])
dp[s][i]=dp[s^t][i]+dp[t][i]-val[i],pre[s][i]=mp(t,i);
dijkstra(s);
}
int all=(1<<k)-1;
for(int i=1;i<=n*m;i++) if(dp[all][i]<dp[all][res]) res=i;
printf("%d\n",k?dp[all][res]:0);
memset(vis,false,sizeof(vis));
dfs(all,res);
for(int i=1;i<=n*m;i++)
{
if(!val[i]) putchar('x');
else if(vis[i]) putchar('o');
else putchar('_');
if(i%m==0) putchar('\n');
}
return 0;
}
例3、\(\texttt{P3264 [JLOI2015] 管道连接}\)
题目描述
给定一棵 \(n\) 个点, \(m\) 条边的带权无向连通图。
\(k\) 个关键点,每个关键点有各自的颜色,要求颜色相同的关键点两两连通,求最小边权和。
数据范围
- \(1\le n\le 1000,0\le m\le 3000,0\le w_i\le 2\cdot 10^4\) 。
- \(1\le col_i\le k\le 10\) 。
时间限制 \(\texttt{1s}\) ,空间限制 \(\texttt{125MB}\) 。
分析
本题求的是最小斯坦纳森林。
先用模板题的 \(\texttt{dp}\) 求出 \(w_s=\min\limits_{1\le i\le n}dp_{s,i}\) 表示让集合 \(s\) 内的关键点连通所需最小代价。
然后对颜色进行二次 \(\texttt{dp}\) , \(f_s\) 表示让集合 \(s\) 内的颜色符合要求所需最小代价。
枚举 \(s\) 的子集 \(t\) ,记 \(t\) 中颜色对应关键点集状压结果的并集为 \(all\) ,则 \(f_s\gets f_{s/t}+w_{all}\) 。
时间复杂度 \(\mathcal O(3^kn+2^k(n+m)\log(n+m))\) 。
#include<bits/stdc++.h>
#define fi first
#define se second
#define mp make_pair
#define pii pair<int,int>
using namespace std;
const int maxn=1005;
int k,m,n;
vector<pii> g[maxn];
int dp[1<<10][maxn];
int f[1<<10],h[15],w[1<<10];
bool vis[maxn];
void dijkstra(int s)
{
priority_queue<pii,vector<pii>,greater<pii>> q;
for(int i=1;i<=n;i++) vis[i]=0,q.push(mp(dp[s][i],i));
while(!q.empty())
{
int u=q.top().se; q.pop();
if(vis[u]) continue; vis[u]=1;
for(auto [v,w]:g[u])
if(dp[s][v]>dp[s][u]+w)
dp[s][v]=dp[s][u]+w,q.push(mp(dp[s][v],v));
}
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int u,v,w;m--;) scanf("%d%d%d",&u,&v,&w),g[u].push_back(mp(v,w)),g[v].push_back(mp(u,w));
memset(dp,0x3f,sizeof(dp));
for(int i=0,c,d;i<k;i++) scanf("%d%d",&c,&d),h[c]|=1<<i,dp[1<<i][d]=0;
for(int s=1;s<1<<k;s++)
{
for(int t=s;t;t=(t-1)&s) for(int i=1;i<=n;i++) dp[s][i]=min(dp[s][i],dp[t][i]+dp[s^t][i]);
dijkstra(s);
}
memset(f,0x3f,sizeof(f));
f[0]=w[0]=0;
for(int s=1;s<1<<k;s++) w[s]=*min_element(dp[s]+1,dp[s]+n+1);
for(int s=1;s<1<<k;s++)
for(int t=s;t;t=(t-1)&s)
{
int all=0;
for(int i=1;i<=k;i++) if(t>>(i-1)&1) all|=h[i];
f[s]=min(f[s],f[s^t]+w[all]);
}
printf("%d\n",f[(1<<k)-1]);
return 0;
}
例4、\(\texttt{ABC395G Minimum Steiner Tree 2}\)
任务描述
一张 \(n\) 个点的无向完全图,第 \(i\) 个点和第 \(j\) 个点之间边权为 \(c_{i,j}\) 。
\(q\) 次查询,每次给定 \(s,t\) ,求 \(\{1,\cdots,k\}\cup\{s,t\}\) 的最小斯坦纳树边权和。
数据范围
- \(3\le n\le 80,1\le k\le\min(n-2,8)\) 。
- \(0\le c_{i,j}\le 10^9,c_{i,i}=0,c_{i,j}=c_{j,i}\) 。
- \(1\le q\le 5000,k+1\le s,t\le n\) 。
时间限制 \(\texttt{4s}\) ,空间限制 \(\texttt{1GB}\) 。
分析
本题考察对斯坦纳树的理解。
注意到 \(q\ge\frac{n(n-1)}2\) ,这启示我们预处理每组 \((s,t)\) 的答案然后 \(\mathcal O(1)\) 回答。
将 \(s,t\) 视为关键点,暴力跑 \(n^2\) 次斯坦纳树,时间复杂度 \(\mathcal O(n^2(3^{k+2}n+2^{k+2}n^2\log n))\) ,显然吃不消。
回顾 \(\texttt{DP}\) 数组的定义: dp[s][i] 表示覆盖集合 \(s\) 的关键点和第 \(i\) 个点的最小代价。
注意到第二维刚好是我们要枚举的内容,因此只需将 \(s\) 视为关键点,最后 dp[*][t] 即为所求。
时间复杂度 \(\mathcal O(n(3^{k+1}n+2^{k+1}n\log n))\) 。
#include<bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#define mp make_pair
#define pii pair<ll,int>
using namespace std;
int k,n,q,x,y;
ll dp[1<<9][85],res[85][85];
bool vis[85];
vector<pii> g[85];
void dijkstra(int s)
{
priority_queue<pii,vector<pii>,greater<pii> > q;
for(int i=1;i<=n;i++) vis[i]=0,q.push(mp(dp[s][i],i));
while(!q.empty())
{
int u=q.top().se; q.pop();
if(vis[u]) continue; vis[u]=1;
for(auto [v,w]:g[u])
if(dp[s][v]>dp[s][u]+w)
dp[s][v]=dp[s][u]+w,q.push(mp(dp[s][v],v));
}
}
int main()
{
scanf("%d%d",&n,&k),k++;
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) scanf("%d",&x),g[i].push_back(mp(j,x));
for(int x=k;x<=n;x++)
{
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=k;i++) dp[1<<(i-1)][i<k?i:x]=0;
for(int s=1;s<1<<k;s++)
{
for(int t=s;t;t=(t-1)&s) for(int i=1;i<=n;i++) dp[s][i]=min(dp[s][i],dp[t][i]+dp[s^t][i]);
dijkstra(s);
}
for(int y=k;y<=n;y++) res[x][y]=dp[(1<<k)-1][y];
}
for(scanf("%d",&q);q--;) scanf("%d%d",&x,&y),printf("%lld\n",res[x][y]);
return 0;
}
例5、\(\texttt{P3638 [APIO2013] 机器人}\)
题目描述
一张 \(w\times h\) 的网格图有 \(n\) 个机器人,每个格子有如下 \(5\) 种可能:
- 一个 \([1,n]\) 中的正整数:表示对应编号的机器人。
x:障碍物。.:空地。A:逆时针转向器。C:顺时针转向器。
两个编号相邻(差为 \(1\) )且位于同一格的机器人可以合并,合并后的机器人拥有合并前的所有编号,因此任意时刻每个机器人拥有的编号是 \([1,n]\) 的一个子区间。
每一步你可以选择上下左右四个方向之一,并朝该方向推动机器人:
- 机器人会一直运动,直到前方为障碍物或网格边界。
- 碰到
A立刻左转,碰到C立刻右转。 - 停止运动后,机器人会检查自身所在格是否有可以合并的机器人,如果有则立刻合并。
注:
- 除
x外每个格子允许同时存在多个(不能合并的)机器人。 - 机器人只有停止运动后才能合并。即使在运动过程中遇到了可合并的机器人,只要前方不是障碍物或者网格边界,机器人也会穿过该格而不是合并。
求合并出编号为 \([1,n]\) 的机器人所需最小步数,如果无解输出 -1 。
数据范围
- \(1\le n\le 9,1\le w,h\le 500\) 。
时间限制 \(\texttt{1.5s}\) ,空间限制 \(\texttt{256MB}\) 。
分析
将模板题中的状压 \(\texttt{DP}\) 换成区间 \(\texttt{DP}\) ,dp[l][r][i] 表示编号为 \([l,r]\) 的机器人出现在位置 \(i\) 的最小代价。
预处理从 \(i\) 开始往四个方向推到的位置 \(i_{1\sim 4}\),这一步可以用记忆化搜索做到 \(\mathcal O(wh)\) 。
进搜索时需要先打标记,否则如果有环路的话会死循环,具体看代码。
原题数据很弱没有环路,所以能过,但是 uoj 上有相应的 hack 数据。
第一类转移 \(dp_{l,r,i_{1\sim 4}}\gets dp_{l,r,i}+1\) 。
第二类转移 \(dp_{l,r,i}\gets dp_{l,j,i}+dp_{j+1,r,i}\) 。
时间复杂度 \(\mathcal O(n^2wh\log wh+n^3wh)\) 。
然后被卡常了。
将初值不为 inf 的点按初值降序排序,然后跑 spfa ,洛谷上可以通过,但是 uoj 上过不了 hack 数据。
注意到边权为 \(1\) ,因此可以用 bfs 求最短路。升序枚举最短路长度 \(d\) ,那么第 \(d\) 层的点扩展出来的点只会放在第 \(d+1\) 层。
\(d\) 的上界可以自适应实现,初始为所有 \(dp_{l,r,i}\) 的最大值,此后枚举到第 \(d\) 层为空即可。
此外初始 vector 的数量要大于 \(d\) 的上界,实测开 \(2\cdot 10^6\) 层可以通过。
时间复杂度 \(\mathcal O(n^3wh)\) ,但显然 \(\mathcal O(n^2wh)\) 的 bfs 才是常数瓶颈。
#include<bits/stdc++.h>
using namespace std;
const int maxn=2.5e5+5,inf=0x3f3f3f3f;
int k,m,n;
char ch[505][505];
int dp[10][10][maxn],to[505][505][4];
bool vis[maxn];
int dx[4]={-1,0,1,0};
int dy[4]={0,-1,0,1};
vector<int> g[maxn],h[8*maxn];
int id(int x,int y)
{
return (x-1)*m+y;
}
int dfs(int x,int y,int k)
{
if(to[x][y][k]) return to[x][y][k];
to[x][y][k]=-1;//搜索前先打标记,否则环路会死循环
int nk=(k+(isupper(ch[x][y])?ch[x][y]-'A'+1:0))%4,nx=x+dx[nk],ny=y+dy[nk];
return to[x][y][k]=ch[nx][ny]=='x'?id(x,y):dfs(nx,ny,nk);
}
void bfs(int l,int r)
{
int mx=0;
for(int i=1;i<=n*m;i++) if(dp[l][r][i]!=inf) h[dp[l][r][i]].push_back(i),mx=max(mx,dp[l][r][i]);
memset(vis,0,sizeof(vis));
for(int d=0;d<=mx||!h[d].empty();d++)
{
for(auto u:h[d])
{
if(vis[u]) continue; vis[u]=1;
for(auto v:g[u]) if(dp[l][r][v]>d+1) h[dp[l][r][v]=d+1].push_back(v);
}
h[d].clear();
}
}
int main()
{
scanf("%d%d%d",&k,&m,&n);
for(int i=1;i<=n;i++) scanf("%s",ch[i]+1);
for(int i=1;i<=n;i++) ch[i][0]=ch[i][m+1]='x';
for(int j=1;j<=m;j++) ch[0][j]=ch[n+1][j]='x';
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(ch[i][j]!='x') for(int k=0;k<4;k++) if(~dfs(i,j,k)) g[id(i,j)].push_back(dfs(i,j,k));
if(isdigit(ch[i][j])) dp[ch[i][j]-'0'][ch[i][j]-'0'][id(i,j)]=0;
}
for(int l=k;l>=1;l--)
for(int r=l;r<=k;r++)
{
for(int j=l;j<r;j++) for(int i=1;i<=n*m;i++) dp[l][r][i]=min(dp[l][r][i],dp[l][j][i]+dp[j+1][r][i]);
bfs(l,r);
}
int res=*min_element(dp[1][k]+1,dp[1][k]+n*m+1);
printf("%d\n",res!=inf?res:-1);
return 0;
}
例6、\(\texttt{P7450 [THUSC 2017] 巧克力}\)
题目描述
\(T\) 组数据,给定一张 \(n\times m\) 的巧克力网格图,美格有两个属性:美味值 \(a_{i,j}\) 和种类 \(c_{i,j}\) 。
求一个格子数最少的四连通块,要求不能包含 \(c_{i,j}=-1\) 的格子,且 \(c_{i,j}\) 至少有 \(k\) 种取值,要求格子数量最小。
在此基础上,再要求 \(a_{i,j}\) 中位数最小。求最少格子数和中位数最小值,无解输出 -1 -1 ,这里中位数定义为从小到大第 \(\lfloor\frac{n+1}2\rfloor\) 个数。
数据范围
- \(1\le T\le 5,1\le n\cdot m\le 233,1\le k\le 5\) 。
- \(c_{i,j}\in\{-1\}\cup [1,\cdots,n\cdot m],0\le a_{i,j}\le 10^6\) 。
时间限制 \(\texttt{5s}\) ,空间限制 \(\texttt{500MB}\) 。
分析
假设总共只有 \(k\) 种颜色,第一问退化成最小斯坦纳树板子。
\(dp_{s,i}\) 表示覆盖颜色状压结果为 \(s\) ,根节点 \(i\) 的最小点数。
- 第一类转移: \(dp_{s,i}\gets dp_{s,j}+1\) ,要求 \(i\) 和 \(j\) 相邻。
- 第二类转移: \(dp_{s,i}\gets dp_{t,i}+dp_{s/t,i}-1\) 。
"至少 \(k\) 种颜色" 的限制非常讨厌,考虑随机化,将每种颜色随机映射到 \([1,k]\) 中的一个整数。
对于最优解中 \(c_{i,j}\) 互异的这 \(k\) 个格子,映射后它们仍然互异的概率为 \(\frac{k!}{k^k}=0.0384\) 。
虽然单次找到最优解的概率很低,但是重复 \(150\) 次后找到最优解的概率 \(1-(1-0.0384)^{150}\approx 0.997\) ,可以接受。
第二问是容易的,中位数最小,一眼二分答案。
注意值域 \(10^6\) 但总共只有 \(\le 233\) 个数,离散化可以让二分次数 \(20\to 8\) 。
将 \(\texttt{dp}\) 数组改用 pair 存储,第一维点数,第二维权值和,转移同理。
时间复杂度 \(\mathcal O(T\cdot 150\cdot(3^knm+2^knm\log(nm)))\) 。
#include<bits/stdc++.h>
#define fi first
#define se second
#define mp make_pair
#define pii pair<int,int>
using namespace std;
const int maxn=250,inf=1e9;
int k,m,n,t,cnt;
pii res;
int a[maxn],c[maxn],h[maxn],p[maxn];
pii val[maxn],dp[1<<5][maxn];
bool vis[maxn];
vector<int> g[maxn];
mt19937 rnd(time(0));
pii operator+(pii x,pii y) {return mp(x.fi+y.fi,x.se+y.se);}
pii operator-(pii x,pii y) {return mp(x.fi-y.fi,x.se-y.se);}
int id(int x,int y) {return (x-1)*m+y;}
void dijkstra(int s)
{
priority_queue<pair<pii,int>,vector<pair<pii,int>>,greater<pair<pii,int>>> q;
for(int i=1;i<=n*m;i++) vis[i]=0,q.push(mp(dp[s][i],i));
while(!q.empty())
{
int u=q.top().se; q.pop();
if(vis[u]) continue; vis[u]=1;
for(auto v:g[u]) if(dp[s][v]>dp[s][u]+val[v]) dp[s][v]=dp[s][u]+val[v],q.push(mp(dp[s][v],v));
}
}
pii work()
{
int l=0,r=cnt,x=inf;
for(int i=1;i<=n*m;i++) p[i]=rnd()%k;
while(r-l>1)
{
int mid=(l+r)>>1;
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=n*m;i++) dp[1<<p[c[i]]][i]=val[i]=mp(1,a[i]<=mid?-1:1);
for(int s=1;s<1<<k;s++)
{
for(int t=s;t;t=(t-1)&s) for(int i=1;i<=n*m;i++) dp[s][i]=min(dp[s][i],dp[t][i]+dp[s^t][i]-val[i]);
dijkstra(s);
}
pii tmp=*min_element(dp[(1<<k)-1]+1,dp[(1<<k)-1]+n*m+1);
if(x==inf) x=tmp.fi;
if(x>res.fi) return mp(inf,inf);
tmp.se<=0?r=mid:l=mid;
}
return mp(x,h[r]);
}
int main()
{
for(scanf("%d",&t);t--;)
{
scanf("%d%d%d",&n,&m,&k),res=mp(inf,inf);
for(int i=1;i<=n*m;i++) scanf("%d",&c[i]);
for(int i=1;i<=n*m;i++) scanf("%d",&a[i]),h[i]=a[i];
sort(h+1,h+n*m+1);
cnt=unique(h+1,h+n*m+1)-h-1;
for(int i=1;i<=n*m;i++) a[i]=lower_bound(h+1,h+cnt+1,a[i])-h;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
int u=id(i,j); g[u].clear();
if(c[u]==-1) continue;
if(i>1&&c[id(i-1,j)]!=-1) g[u].push_back(id(i-1,j));
if(i<n&&c[id(i+1,j)]!=-1) g[u].push_back(id(i+1,j));
if(j>1&&c[id(i,j-1)]!=-1) g[u].push_back(id(i,j-1));
if(j<m&&c[id(i,j+1)]!=-1) g[u].push_back(id(i,j+1));
}
for(int x=1;x<=150;x++) res=min(res,work());
if(res.fi==inf) printf("-1 -1\n");
else printf("%d %d\n",res.fi,res.se);
}
return 0;
}
本文来自博客园,作者:peiwenjun,转载请注明原文链接:https://www.cnblogs.com/peiwenjun/p/19510501
浙公网安备 33010602011771号