[图论记录]P7916 [CSP-S 2021] 交通规划(民间数据)
command_block
2021-10-29 19:54:19
**题意** : 给出一个 $n\times m$ 的网格图,边有边权。
有 $T$ 次询问,每次询问给出 $k_i$ 个**网格边界上**的点,给每个点建立一个新点,并连边,也给出边权。
建立新图后,需要将点黑白染色,使得一端黑一端白的边权和最小,回答这个最小值。
$n,m\leq 500,\sum k\leq 50$ ,时限$\texttt{3s}$。
------------
尽管维持了形式上的不超纲,以最小割为核心模型,网络流为大众暴力的本题,是命题的偶然,还是预示着新时代的开始呢?
抛开这些试题的限制不谈,题目本身是很好的。学到许多。
- **最小割模型**
这个非常显然,熟练的选手可以发现,将“黑色”看做 $S$ 集,将“白色”看做 $T$ 集,则“黑白之间的边”恰好对应一种割。
- $k=2$
此时图是平面图。**平面图的有源汇最小割可以转为对偶图上的最短路。**
观察下列图例:
![](https://cdn.luogu.com.cn/upload/image_hosting/z2fmxwq2.png)
左侧是一张平面图(加粗)与其对偶图(未加粗)。所谓对偶图就是将平面图的边围成的每个“区域”看做一个点,对于相邻区域之间的分界边,给这两个区域连边,边权为分界边的权值。
右侧是平面图上的割,以及对偶图上的对应边集(黑色)。不难发现,平面图上的一种割对应对偶图上的一条路径。
怎样的路径才分割 $S,T$ 两点?我们可以从 $S,T$ 向外发射射线,将无限大的区域分成两个部分,横跨两部分的一条路径即对应 $S,T$ 之间的一种割。
求个最短路就能得到最小割。
- $k>2$
此时不太能用单源单汇最短路描述问题了。观察下图中割的分布:
![](https://cdn.luogu.com.cn/upload/image_hosting/r16p91r2.png)
我们按顺时针(逆时针也可)顺序,将网格周围相邻的红蓝点之前区域,交替地染成红色或蓝色。(也可以当做红先则红,蓝先则蓝)
不难发现,一组合法的割形如若干条连接红色区域和蓝色区域的路径,且每个区域恰好被连接一次。
先跑 $k$ 次 Dijkstra 求出这些区域两两的最短路。
发现匹配是不可能相交的,破环为链然后 $O(k^3)$ 区间 DP 即可求出最优匹配。
复杂度 $O(nmk\log nm+k^3)$ 。
```cpp
#include<algorithm>
#include<cstdio>
#include<queue>
#define Pr pair<int,int>
#define mp make_pair
#define fir first
#define sec second
#define MaxG 255000
#define MaxN 505
#define MaxK 105
using namespace std;
const int INF=1000000000;
struct Line{int t,l,nxt;}l[MaxG<<2];
int fir[MaxG],tl;
void adl(int u,int v,int len){
l[++tl]=(Line){v,len,fir[u]};fir[u]=tl;
l[++tl]=(Line){u,len,fir[v]};fir[v]=tl;
}
priority_queue<Pr> q;
int mx,dis[MaxG];
void Dijk(int s)
{
for (int i=1;i<=mx;i++)dis[i]=INF;
q.push(mp(dis[s]=0,s));
while(!q.empty()){
Pr now=q.top();q.pop();
if (dis[now.sec]!=-now.fir)continue;
int u=now.sec;
for (int i=fir[u],v;i;i=l[i].nxt)
if (dis[v=l[i].t]>dis[u]+l[i].l){
dis[v]=dis[u]+l[i].l;
q.push(mp(-dis[v],v));
}
}
}
struct Data{int id,col;}p[MaxK];
bool cmp(const Data &A,const Data &B)
{return A.id<B.id;}
int n,m,down[MaxN][MaxN],right[MaxN][MaxN]
,k,tn,s[MaxK],cost[MaxK][MaxK],dp[MaxK][MaxK];
#define pos(x,y) ((x)*(m+1)+(y)+1)
int pos2(int p){
if (p<=m)return pos(0,p);
if (p<=m+n)return pos(p-m,m);
if (p<=m+n+m)return pos(n,m+n+m-p);
return pos(2*(n+m)-p,0);
}
void solve()
{
scanf("%d",&k);
for (int i=0;i<=n;i++)down[i][0]=down[i][m]=0;
for (int j=0;j<=m;j++)right[0][j]=right[n][j]=0;
for (int i=1,x;i<=k;i++){
scanf("%d%d%d",&x,&p[i].id,&p[i].col);
int j=p[i].id;
if (j<=m)right[0][j-1]=x;
else if (p[i].id<=n+m)down[j-m-1][m]=x;
else if (p[i].id<=2*m+n)right[n][m+m+n-j]=x;
else down[2*(n+m)-j][0]=x;
}
for (int i=0;i<n;i++)
for (int j=0;j<=m;j++)
adl(pos(i,j),pos(i+1,j),down[i][j]);
for (int i=0;i<=n;i++)
for (int j=0;j<m;j++)
adl(pos(i,j),pos(i,j+1),right[i][j]);
sort(p+1,p+k+1,cmp);
p[k+1]=p[1];tn=0;
for (int i=1;i<=k;i++)
if (p[i].col!=p[i+1].col)
s[++tn]=pos2(p[i].id);
if (!tn){puts("0");return ;}
mx=pos(n,m);
for (int i=1;i<=tn;i++){
Dijk(s[i]);
for (int j=1;j<=tn;j++)
cost[i][j]=cost[i][j+tn]=cost[i+tn][j+tn]=dis[s[j]];
}
for (int len=2;len<=tn;len+=2)
for (int l=1;l+len-1<=tn+tn;l++){
int r=l+len-1;
dp[l][r]=INF;
for (int k=l+1;k<=r;k+=2)
dp[l][r]=min(dp[l][r],dp[l+1][k-1]+dp[k+1][r]+cost[l][k]);
}
int ans=INF;
for (int l=1;l<=tn;l++)
ans=min(ans,dp[l][l+tn-1]);
printf("%d\n",ans);
tl=0;for (int i=1;i<=mx;i++)fir[i]=0;
}
int main()
{
int T;scanf("%d%d%d",&n,&m,&T);
for (int i=1;i<n;i++)
for (int j=0;j<m;j++)
scanf("%d",&right[i][j]);
for (int i=0;i<n;i++)
for (int j=1;j<m;j++)
scanf("%d",&down[i][j]);
while(T--)solve();
return 0;
}
```