IOI2021集训队作业 114DJ Son of Pipe Stream

一个双向网络中有两种液体,这两种液体之间可以混合或分离。在一个容量为\(c\)的管道中,两者流量\((f,w)\)满足限制\(fv+w\le c\),并且方向相同。分别有\(f\)\(w\)\(1\)\(2\),以及两者共同的汇点\(3\)。设\((F,W)\)为从源点到汇点的流量,最大化\(F^aW^{1-a}\)

\(n\le 305\)


显然可以先忽略\(v\),最后再除以它。

显然如果有条边两种液体方向相反肯定是不优的。

分别从\(1\)\(2\)出发,求出各自的最大流\(F_{max},W_{max}\),再从连向\(1\)\(2\)的超级源出来,求出总的最大流\(S\)。有结论:对于解\((F,W)\),只要满足\(F\le F_{max},W\le W_{max},F+W\le S\),则一定有解。

谈谈我的理解:假设从超级源连出两条边,不断调整两条边的流量,使得流量总和为\(S\)

现在假如到\(1\)的边少了\(\epsilon\),到\(2\)的边多了\(\epsilon\)。那么从\(2\)开始的新的增广路一定和从\(1\)开始的原增广路有交,并且不能再找出一条从\(1\)开始的增广路使得可以补回\(1\)出发的\(\epsilon\)

试题准备的题解的解释:有解\((F_{max},S-F_{max})\)\((S-W_{max},W_{max})\)(可以看做增广时优先增广其中一边),可以找到\(t\in[0,1]\),得到\((F,W)=t(F_{max},S-F_{max})+(1-t)(S-W_{max},W_{max})\)

用点高中数学的知识求出最优时\(F\)是多少,然后还原方案。还原的时候可以先限制流量\((F,W)\)跑一遍,得到每条边的合流量,把这个合流量变成容量,且把边定向,限制流量\((F,0)\)跑一遍,就可以知道\(f\)的流量。


using namespace std;
#include <bits/stdc++.h>
#define N 305
#define INF 1000000000
int n,m;
double v,a;
struct edge{int u,v,c;} ed[N*N];
struct EDGE{
	int to;
	double c;
	EDGE *las;
} e[N*N];
int ne;
EDGE *last[N];
void link(int u,int v,double c){
	e[ne]={v,c,last[u]};
	last[u]=e+ne++;
}
#define rev(ei) (e+(int((ei)-e)^1))
int T;
EDGE *cur[N];
int dis[N],gap[N],BZ;
double dfs(int x,double s){
	if (x==T)
		return s;
	double have=0;
	for (EDGE *&ei=cur[x];ei;ei=ei->las)
		if (ei->c && dis[ei->to]+1==dis[x]){
			double t=dfs(ei->to,min(ei->c,s-have));
			ei->c-=t,rev(ei)->c+=t,have+=t;
			if (have==s)
				return s;
		}
	cur[x]=last[x];
	if (!--gap[dis[x]])
		BZ=0;
	++dis[x];
	++gap[dis[x]];
	return have;
}
void build(double c1=INF,double c2=INF){
	ne=0;
	memset(last,0,sizeof(EDGE*)*(n+2));	
	for (int i=0;i<m;++i){
		link(ed[i].u,ed[i].v,ed[i].c);
		link(ed[i].v,ed[i].u,ed[i].c);
	}
	link(n+1,1,c1),link(1,n+1,0);
	link(n+1,2,c2),link(2,n+1,0);
	T=3;
}
double flow(int S){
	memset(gap,0,sizeof gap);
	memset(dis,0,sizeof dis);
	gap[0]=n+1;
	memset(cur,0,sizeof cur);
	BZ=1;
	double res=0;
	while (BZ)
		res+=dfs(S,INF);
	return res;
}
double doit(int S,double c1=INF,double c2=INF){
	build(c1,c2);
	return flow(S);
}
pair<double,double> ans[N*N];
int main(){
//	freopen("in.txt","r",stdin);
	scanf("%d%d%lf%lf",&n,&m,&v,&a);
	for (int i=0;i<m;++i){
		int u,v,c;
		scanf("%d%d%d",&u,&v,&c);
		ed[i]={u,v,c};
	}
	double Fmx=doit(1);
	double Wmx=doit(2);
	double S=doit(n+1);
	double Fmn=S-Wmx,tmp=a*S,F,W;
	if (Fmn<=tmp && tmp<=Fmx)
		F=tmp;
	else if (tmp<Fmn)
		F=Fmn;
	else
		F=Fmx;
	W=S-F;
	doit(n+1,F,W);
	for (int i=0;i<m;++i){
		double f=ed[i].c-e[i*2].c;
		if (f>0)
			e[i*2].c=f,e[i*2+1].c=0;
		else
			e[i*2].c=0,e[i*2+1].c=-f;
		ans[i].second=f;
	}
	e[m*2].c=F,e[m*2+1].c=0;
	e[(m+1)*2].c=e[(m+1)*2+1].c=0;
	flow(n+1);
	for (int i=0;i<m;++i){
		double f;
		if (ans[i].second>0)
			ans[i].first=e[i*2+1].c;
		else
			ans[i].first=-e[i*2].c;
		ans[i].second-=ans[i].first;
	}
	for (int i=0;i<m;++i)
		printf("%lf %lf\n",ans[i].first/v,ans[i].second);
	double val=pow(F/v,a)*pow(W,1-a);
	printf("%lf\n",val);
	return 0;
}
posted @ 2021-03-03 19:25  jz_597  阅读(113)  评论(0编辑  收藏  举报