P5331[SNOI2019]通信【费用流+优化建图】

题面


SOL

首先确定用费用流求解。
对于每一个点的贡献,只和向谁连边有关,和后面的点无关。
所以把每个点拆成 入点,出点。

1.将S和入点连边, c a p = 1 , w = 0 cap=1,w=0 cap=1,w=0
2.入点和T连一边, c a p = 1 , w = W cap=1,w=W cap=1,w=W
3.出点和T连边: c a p = 1 , w = 0 cap=1,w=0 cap=1,w=0
4.入点和前 i − 1 i-1 i1个点的出点连边, c a p = 1 , w = ∣ a [ j ] − a [ i ] ∣ cap=1,w=|a[j]-a[i]| cap=1,w=a[j]a[i]

但是第4种有 n 2 n^2 n2条边,费用流跑不过。。

优化建图: 如果不考虑前后,显然可以构造一条权值递增的链,每一个点向链连边,链向每一个点连边。链间费用为两端点的权值之差,链间连双向边。这样点与点之间的路径可以表示成 : i 的 入 点 流 向 链 中 a [ i ] , 在 链 上 流 向 a [ j ] , 链 上 a [ j ] 流 向 j 的 出 点 。 i的入点流向链中a[i],在链上流向a[j],链上a[j]流向j的出点。 ia[i],a[j]a[j]j

如何加上下标维度的限制? c d q 分 治 cdq分治 cdq。 构造 l o g n logn logn条链,处理区间 m i d mid mid以左和以右的进出。


CODE

#include<bits/stdc++.h>
using namespace std;
#define sf scanf
#define pf printf
#define ll long long
#define cs const
#define db double
#define ri register int
#define gc getchar()
#define in red()
inline int red(){
	int num=0,f=1;char c=gc;
	for(;!isdigit(c);c=gc)if(c=='-')f=-1;
	for(;isdigit(c);c=gc)num=(num<<1)+(num<<3)+(c^48);
	return num*f;
}
cs int N=1e5+10,M=3e6+10,inf=1e9;
cs ll INF=1e18;
int sum,a[N],tmp[N],n,W,S,T;
namespace FLOW{
	int head[N],c[M],w[M],nxt[M],to[M],cnt=1,vis[N];
	ll dis[M],ans;
	inline void adde(int u,int v,int cap,int vl){
		nxt[++cnt]=head[u];head[u]=cnt;to[cnt]=v;w[cnt]=vl;c[cnt]=cap;
		nxt[++cnt]=head[v];head[v]=cnt;to[cnt]=u;w[cnt]=-vl;c[cnt]=0;
	}
	inline bool spfa(){
	memset(vis,0,sizeof (int)*(sum+5));fill(dis+1,dis+sum+1,INF);
		queue<int>q;
		q.push(S);dis[S]=0;
		while(!q.empty()){
			int u=q.front();q.pop();
			vis[u]=0;
			for(ri i=head[u];i;i=nxt[i]){
				int v=to[i];
				if(c[i]&&dis[v]>dis[u]+w[i]){
					dis[v]=dis[u]+w[i];
					if(!vis[v])q.push(v),vis[v]=1;
				}
			}
		}
		return dis[T]^INF;
	}	
	inline int dfs(int u,int tot){
		if(u==T||!tot)return tot;
		vis[u]=1;
		int res=0,now;
		for(ri i=head[u];i;i=nxt[i]){
			int v=to[i];
			if(!vis[v]&&c[i]&&dis[v]==dis[u]+w[i]){
				now=dfs(v,min(c[i],tot-res));
				if(now)c[i]-=now,c[i^1]+=now,res+=now,ans+=now*w[i];
			}
			if(res==tot)break;
		}
		if(!res)dis[u]=INF;
		return vis[u]=0,res;
	}
	inline void run(){while(spfa()){memset(vis,0,sizeof (int)*(sum+5));dfs(S,inf);}}
}
using FLOW::adde;

inline void cdq(int l,int r){
	if(l==r)return;
	int mid=(l+r)>>1,top=0;
	cdq(l,mid);cdq(mid+1,r);
	for(ri i=l;i<=r;++i)tmp[++top]=a[i];
	sort(tmp+1,tmp+top+1);top=unique(tmp+1,tmp+top+1)-tmp-1;
	for(ri i=1;i<top;++i)adde(sum+i+1,sum+i,inf,tmp[i+1]-tmp[i]),adde(sum+i,sum+i+1,inf,tmp[i+1]-tmp[i]);
	for(ri i=l;i<=r;++i){
		int k=lower_bound(tmp+1,tmp+top+1,a[i])-tmp;
		if(i<=mid)adde(sum+k,n+i,1,0);
		else adde(i,sum+k,1,0);
	}
	sum+=top;
}

signed main(){
	n=in;W=in;
	S=(n<<1)+1;T=(n<<1)+2;sum=T;
	for(ri i=1;i<=n;++i)a[i]=in,adde(S,i,1,0),adde(i,T,1,W),adde(n+i,T,1,0);
	cdq(1,n);FLOW::run();
	cout<<FLOW::ans<<'\n';

	return 0;
}














评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值