【TCO2016】ColorfulPath(平面图最短路转对偶图最小割)

传送门


跑最大流的时候加法爆int了,WA了七次。

题意简述:

给一个DAG,记边从 a i a_i ai b i b_i bi,边权 c i c_i ci,要从 0 0 0走到 n n n,求最短路。
保证 a i &lt; b i a_i&lt;b_i ai<bi,而且不存在 a i &lt; a j &lt; b i &lt; b j a_i&lt;a_j&lt;b_i&lt;b_j ai<aj<bi<bj
每个点有一个颜色,每种颜色在路径上要么不经过,要么全部经过。

题解:

大概就是有一个DAG,点从0到n标号且排成一排,两条边的区间要么一个包含另一个,要么不交。然后求最短路,每个点有一个颜色,要求每种颜色的点要么全部经过,要么全部不经过。

注意到边的覆盖关系可以表示为一棵树。

由于边没有相交,显然是一个平面图,我们知道平面图最短路等价于期对偶图的最小割。

颜色的奇怪的限制显然不好用最短路,我们用最小割来做。

转对偶图的建图很简单,考虑一下怎么建立出关于颜色的限制。

发现在最小割中显然就是要所有颜色同时属于S集或者T集。

对于每个颜色维护一个集合,集合中的点相互连INF的边,这样就能够保证同时在S集或T集了。

首先划分区间,叶子直接向汇点连INF的边,同时处理一下颜色限制。

否则这条边需要加入左右颜色的集合,然后父亲向它连容量为它的权值的边,然后它向父亲连边INF。

该图的最小割即为答案。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

int S,T=1,tot=1;
cs int N=5e3+7,INF=0x3f3f3f3f;
struct edge{
	int to,cap,rev;
	edge(int _to,int _cap,int _rev):to(_to),cap(_cap),rev(_rev){}
};
std::vector<edge> G[N];
typedef std::vector<edge>::iterator iter;
iter cur[N];

inline void adde(int u,int v,int uv,int vu=0){
	G[u].push_back(edge(v,uv,G[v].size()));
	G[v].push_back(edge(u,vu,G[u].size()-1));
}
	
int lev[N],gap[N];
inline void BFS(){
	memset(lev,0,sizeof lev);
	memset(gap,0,sizeof gap);
	gap[1]=lev[T]=1;std::queue<int> q;q.push(T);
	while(!q.empty()){
		int u=q.front();q.pop();cur[u]=G[u].begin();
		for(edge &e:G[u])if(lev[e.to]==0)++gap[lev[e.to]=lev[u]+1],q.push(e.to);
	}
}

bool finished;
inline int dfs(int u,int flow){
	if(u==T)return flow;
	int ans=0;
	for(iter &e=cur[u];e!=G[u].end();++e){
		if(e->cap&&lev[e->to]+1==lev[u]){
			int delta=dfs(e->to,std::min(flow-ans,e->cap));
			if(delta){
				e->cap-=delta;
				G[e->to][e->rev].cap+=delta;
				if((ans+=delta)==flow)return flow;
			}
		}
	}
	cur[u]=G[u].begin();
	if(!--gap[lev[u]++])finished=true;
	++gap[lev[u]];
	return ans;
}

inline int Flow(){
	int ans=0;
	BFS();finished=false;
	while(!finished&&ans<INF)ans+=dfs(S,INF);
	return ans;
}

class ColorfulPath{
	private:
		static cs int N=5e3+7;
		std::vector<int> G[N],w[N];
		std::vector<int> col[N];int c[N];
		void build(int l,int r,int fa){
			for(int re u=l;u<r;){
				int v=0,p=0,m=G[u].size();
				if(u!=l)col[c[u]].push_back(fa);
				for(int re i=0;i<m;++i)if(G[u][i]>v){
					v=G[u][i];
					p=i;
				}
				if(!v){
					adde(fa,T,INF);
					for(int re i=l+1;i<r;++i)
					col[c[i]].push_back(fa);
					return ;
				}
				else {
					G[u][p]=0;++tot;
					adde(fa,tot,w[u][p],INF);
					build(u,v,tot);
					u=v;
				}
			}
		}
	public:
		ColorfulPath(){}
		int shortestPath(std::vector<int> a,std::vector<int> b,
				std::vector<int> cost,std::vector<int> color){
			int n=color.size()+1,m=a.size();
			for(int re i=0;i<m;++i){
				G[a[i]].push_back(b[i]);
				w[a[i]].push_back(cost[i]);
			}
			for(int re i=1;i<n;++i){
				c[i]=color[i-1];
			}
			build(0,n,S);
			for(int re i=1;i<=1000;++i){
				int m=col[i].size();
				for(int re j=1;j<m;++j)
				adde(col[i][j-1],col[i][j],INF,INF);
			}
			int ans=Flow();
			return ans>=INF?-1:ans;
		}
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值