peiwenjun's blog 没有知识的荒原

斯坦纳树学习笔记

例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;
}

posted on 2026-01-21 10:59  peiwenjun  阅读(0)  评论(0)    收藏  举报

导航