ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

洛谷 P5331 - [SNOI2019]通信(CDQ 分治优化建图+费用流)

2021-04-18 17:05:45  阅读:203  来源: 互联网

标签:P5331 哨站 洛谷 int mid ec 建图 flw dis


题面传送门

首先熟悉网络流的同学应该能一眼看出此题的建模方法:

  • 将每个点拆成两个点 \(in_i,out_i\),连一条 \(S\to in_i\),容量为 \(1\) 费用为 \(0\) 的边
  • 连一条 \(in_i\to T\) 容量为 \(1\) 费用为 \(W\) 的边,表示哨站 \(i\) 连向控制中心
  • 连一条 \(out_i\to T\) 容量为 \(1\) 费用为 \(0\) 的边,表示每个哨站最多被后面一个哨站连接
  • 对每对 \(i,j(i>j)\) 连一条 \(in_i\to out_j\) 容量为 \(1\) 费用为 \(|a_i-a_j|\) 的边,表示哨站 \(i\) 连向哨站 \(j\)

然后跑最小费用最大流即可,最大流保证每个哨站都要么连向控制中心,要么连向了前面某个哨站,要么连向了控制中心,最小费用保证费用最小。

然后你兴高采烈地开始码,码好了,测过了样例,交上去……T 了?

不难发现在这个做法中边数最高可达到 \(n^2=10^6\),这显然是费用流所承受不了的。因此考虑优化建边。不过按照传统的线段树优化建图的方法是不太可行的,因为这里既涉及到下标的大小关系 \(i>j\),又涉及到值的大小关系(因为边权中带一个绝对值),也就是说这玩意儿实际上可以视作一个二维偏序,考虑求解 \(k\) 维偏序的时候用到的一个技巧——cdq 分治。每次递归到区间 \([l,r]\) 时候,记 \(mid=\lfloor\dfrac{l+r}{2}\rfloor\),我们就从 \([mid+1,r]\) 向 \([l,mid]\) 连边,我们将 \(a_l,a_{l+1},a_{l+2},\cdots,a_r\) 从小到大排序并去重,假设为 \(b_1,b_2,\cdots,b_m\),我们对每个 \(b_i\) 新建一个虚点 \(pt_i\),然后在 \(pt_i\) 与 \(pt_{i+1}\) 之间连费用为 \(b_{i+1}-b_i\) 的双向边,然后对 \(i\in[l,mid]\) 找出满足 \(b_j=a_i\) 的 \(j\) 然后连 \(pt_j\to out_i\),\(i\in[mid+1,r]\) 也同理,只不过是从 \(in_i\) 向 \(pt_j\) 连边。不难发现这种建图方法与暴力是等价的,边数也降到了 \(n\log n\) 级别,可以通过此题。

这是蒟蒻第一次遇到这种建图方法哦,不喜勿喷~

const int MAXN=1e3;
const int MAXV=2e4;
const int MAXE=1e5*2;
const int INF=0x3f3f3f3f;
int n,W,S=1,T=2,ncnt=2,a[MAXN+5],p1[MAXN+5],p2[MAXN+5];
int hd[MAXV+5],to[MAXE+5],nxt[MAXE+5],cap[MAXE+5],cst[MAXE+5],ec=1;
void adde(int u,int v,int f,int c){
	to[++ec]=v;cap[ec]=f;cst[ec]=c;nxt[ec]=hd[u];hd[u]=ec;
	to[++ec]=u;cap[ec]=0;cst[ec]=-c;nxt[ec]=hd[v];hd[v]=ec;
} int flw[MAXV+5],pre[MAXV+5],lste[MAXV+5];ll dis[MAXV+5];
bool inq[MAXV+5];
bool getdis(){
	memset(dis,63,sizeof(dis));memset(flw,0,sizeof(flw));
	dis[S]=0;flw[S]=INF;queue<int> q;q.push(S);inq[S]=1;
	while(!q.empty()){
		int x=q.front();q.pop();inq[x]=0;
		for(int e=hd[x];e;e=nxt[e]){
			int y=to[e],z=cap[e],w=cst[e];
			if(z&&dis[y]>dis[x]+w){
				dis[y]=dis[x]+w;flw[y]=min(flw[x],z);
				pre[y]=x;lste[y]=e;
				if(!inq[y]){inq[y]=1;q.push(y);}
			}
		}
	} return dis[T]<0x3f3f3f3f3f3f3f3fll;
}
pair<int,ll> mcmf(){
	int mxfl=0;ll mncst=0;
	while(getdis()){
		mxfl+=flw[T];mncst+=flw[T]*dis[T];
		for(int i=T;i^S;i=pre[i]){
			cap[lste[i]]-=flw[T];cap[lste[i]^1]+=flw[T];
		}
	} return mp(mxfl,mncst);
}
int b[MAXN+5];
void build(int l,int r){
	if(l==r) return;int mid=l+r>>1;
	build(l,mid);build(mid+1,r);int cnt=0;
	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++){
		adde(ncnt+i,ncnt+i+1,INF,b[i+1]-b[i]);
		adde(ncnt+i+1,ncnt+i,INF,b[i+1]-b[i]);
	}
	for(int i=l;i<=r;i++){
		int pos=lower_bound(b+1,b+cnt+1,a[i])-b;
		if(i>mid) adde(p1[i],ncnt+pos,1,0);
		else adde(ncnt+pos,p2[i],1,0);
	} ncnt+=cnt;
}
int main(){
	scanf("%d%d",&n,&W);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) p1[i]=++ncnt;
	for(int i=1;i<=n;i++) p2[i]=++ncnt;
	for(int i=1;i<=n;i++) adde(S,p1[i],1,0),adde(p2[i],T,1,0),adde(p1[i],T,1,W);
	build(1,n);printf("%lld\n",mcmf().se);
	return 0;
}

标签:P5331,哨站,洛谷,int,mid,ec,建图,flw,dis
来源: https://www.cnblogs.com/ET2006/p/luogu-P5331.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有