P5331 [SNOI2019]通信 题解
Post time: 2021-04-07 17:08:10
cdq 分治优化建图+最小费用最大流。
首先考虑暴力怎么做。暴力是不是,加边加边加鞭
看到题面中“每个哨站只能被后面的至多一个哨站连接。”想到使用流量来限制,那么本题中的最小代价就可以用最小费用来求。显然是一个最小费用最大流的模型。
考虑建图。把每个点拆成两个点 \(i\) 和 \(i+n\)。
- \(S\to i\),流量为 \(1\),费用为 \(0\)。
- \(i\to T\),流量为 \(1\),费用为 \(W\)。表示这个点直接连向基站的费用。
- \(i+n\to T\),流量为 \(1\),费用为 \(0\)。
- \(i\to j(j<i)\),流量为 \(1\),费用为 \(|a_i-a_j|\)。表示这个点连向前面的其他点。
这样可以直接跑了,但是过不去,因为第 4 条加边的边数是 \(O(n^2)\) 级别的。考虑如何优化这个东西。
这里用到一个很神奇的 cdq 分治优化建图。考虑目前的区间 \([l,r]\),现在要把这个区间的右半边向左半边连边。先把所有点拿出来去重之后从小到大建成一条双向虚链,两个点之间的花费就是两个点权值之差。
可以发现,这样做之后,任意两个点之间的这条边都可以在这条虚链上表示。对于左半边每一个点,找到这个点在虚链上对应的点,从这个虚点向左半边的点连边;对于右半边,则相反,从右半边上的点向虚点连边。这样就通过对重复边的利用,把边数降到了 \(O(n\log n)\) 级别,可以直接跑费用流通过此题。
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int N=1e5+13,INF=0x3f3f3f3f;//数组别开小!
struct Edge{int u,v,f,w,nxt;}e[N<<4];
int n,W,h[N],a[N],tot=1,num,b[N],dist[N],s,t;
bool vis[N];
long long ans;//注意答案需要开long long
inline void add(int u,int v,int f,int w){
e[++tot]=(Edge){u,v,f,w,h[u]};h[u]=tot;
e[++tot]=(Edge){v,u,0,-w,h[v]};h[v]=tot;
}
void link(int l,int r){//cdq分治优化建图
if(l==r) return;
int mid=(l+r)>>1,cnt=0;
link(l,mid),link(mid+1,r);
for(int i=l;i<=r;++i) b[++cnt]=a[i];
sort(b+1,b+cnt+1);cnt=unique(b+1,b+cnt+1)-b-1;
for(int i=1;i<cnt;++i) add(num+i,num+i+1,INF,b[i+1]-b[i]),add(num+i+1,num+i,INF,b[i+1]-b[i]);
for(int i=l;i<=mid;++i){
int j=lower_bound(b+1,b+cnt+1,a[i])-b;
add(num+j,i+n,1,0);
}
for(int i=mid+1;i<=r;++i){
int j=lower_bound(b+1,b+cnt+1,a[i])-b;
add(i,num+j,1,0);
}
num+=cnt;//这个地方不要忘记,图中的点都只能用一次
}
queue<int> q;
inline bool spfa(){
memset(dist,0x3f,sizeof dist);
memset(vis,0,sizeof vis);
while(!q.empty()) q.pop();
q.push(s);dist[s]=0,vis[s]=1;
while(!q.empty()){
int u=q.front();q.pop();vis[u]=0;
for(int i=h[u];i;i=e[i].nxt){
int v=e[i].v,w=e[i].w,f=e[i].f;
if(f&&dist[v]>dist[u]+w){
dist[v]=dist[u]+w;
if(!vis[v]) vis[v]=1,q.push(v);
}
}
}
return dist[t]!=INF;
}
int dfs(int u,int rest){
if(u==t) return rest;
vis[u]=1;
int tmp=rest;
for(int i=h[u];i;i=e[i].nxt){
int v=e[i].v,w=e[i].w,f=e[i].f,ttmp;
if(f&&!vis[v]&&(dist[v]==dist[u]+w)&&(ttmp=dfs(v,min(rest,f)))){
rest-=ttmp;ans+=1ll*w*ttmp;
e[i].f-=ttmp,e[i^1].f+=ttmp;
if(!rest) break;
}
}
if(tmp==rest) dist[u]=INF;
vis[u]=0;
return tmp-rest;
}
inline void zkw(){//zkw费用流
int maxflow=0;
while(spfa()){
memset(vis,0,sizeof vis);
maxflow+=dfs(s,INF);
}
printf("%lld\n",ans);
}
int main(){
scanf("%d%d",&n,&W);s=n*2+1,t=s+1,num=t;
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
for(int i=1;i<=n;++i) add(s,i,1,0),add(i,t,1,W),add(i+n,t,1,0);
link(1,n);
zkw();
return 0;
}