cunzai_zsy0531

关注我

P5331 [SNOI2019]通信 题解

Post time: 2021-04-07 17:08:10

题目链接

cdq 分治优化建图+最小费用最大流。

首先考虑暴力怎么做。暴力是不是,加边加边加鞭

看到题面中“每个哨站只能被后面的至多一个哨站连接。”想到使用流量来限制,那么本题中的最小代价就可以用最小费用来求。显然是一个最小费用最大流的模型。

考虑建图。把每个点拆成两个点 \(i\)\(i+n\)

  1. \(S\to i\),流量为 \(1\),费用为 \(0\)
  2. \(i\to T\),流量为 \(1\),费用为 \(W\)。表示这个点直接连向基站的费用。
  3. \(i+n\to T\),流量为 \(1\),费用为 \(0\)
  4. \(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;
}
posted @ 2022-04-21 21:16  cunzai_zsy0531  阅读(46)  评论(0编辑  收藏  举报