题解 CF343E-Pumping Stations

\(\Large\natural\) CF343E Pumping Stations / 原题链接

解法

前置知识点:最小割树。如果没有学过,可以去看看模板题。

首先求出最小割树。然后,我们设 \(c_{i,j}\) 代表点 \(i\) 到点 \(j\) 的最小割。那么问题就转化为了:

你有一个完全图,点 \(i\) 到点 \(j\) 边权为 \(c_{i,j}\),且边权是正数。\(c_{i,j}=c_{j,i}\) (所以是无向完全图)。现在你要找到一条路径(起点、终点不限),使得所有点都刚好经过一次。要找出总边权最大的这条路径。

这个事实上是可以用贪心去做的。当到达一个点时,在所有「终点的点没有走过」的边中,找到权值最大的那一条,然后走。

然后枚举一下起点,每个都按照上述贪心走一遍,找到总边权最大的那条路径就好了。

贪心的正确性应该由完全图的性质可得。

代码

解法中的完全图不用真的建出来,循环一下就可以了。

码风毒瘤见谅。

#include<bits/stdc++.h>
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define per(i,x,y) for(int i=x;i>=y;--i)
#define mar(o,fst,e) for(int E=fst[o];E;E=e[E].nxt)
#define v e[E].to
#define vz ez[E].to
using namespace std;
const int n7=206,m7=2012,inf=INT_MAX-100;
struct dino{int to,nxt,w,w0;}e[m7],ez[m7];
int n,m,T,fst[n7],fstd[n7],ecnt0,ecnt=1,dep[n7],que[n7],ds,dt,ans;
int fstz[n7],dept[n7],fc[n7][13],mni[n7][13],dot[n7],dotz[n7];
int mc[n7][n7],ansx,ansn;bool u[n7];

int rd(){
	int shu=0;char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))shu=(shu<<1)+(shu<<3)+ch-'0',ch=getchar();
	return shu;
}

void Dedge(int sta,int edn,int w,dino *eh,int *fsth){
	ecnt++;
	eh[ecnt]=(dino){edn,fsth[sta],w,w};
	fsth[sta]=ecnt;
}

void edge(int sta,int edn,int w,dino *eh,int *fsth){
	Dedge(sta,edn,w,eh,fsth),Dedge(edn,sta,w,eh,fsth);
}

bool bfs(){
	memset(dep,0,sizeof dep);
	int head=1,tail=1;que[1]=ds;
	dep[ds]=1,fstd[ds]=fst[ds];
	while(head<=tail){
		int o=que[head];
		mar(o,fst,e){
			if(dep[v]||e[E].w==0)continue;
			dep[v]=dep[o]+1,fstd[v]=fst[v];
			if(v==dt)return 1;
			tail++,que[tail]=v;
		}
		head++;
	}
	return 0;
}

int dfs(int o,int val){
	if(o==dt)return val;
	int tot=val;
	mar(o,fstd,e){
		fstd[o]=E;
		if(!tot){dep[o]=inf;break;}
		if(dep[v]!=dep[o]+1||e[E].w==0)continue;
		int out=dfs(v,min(tot,e[E].w));
		e[E].w-=out,e[E^1].w+=out;
		tot-=out;
	}
	return val-tot;
}

int dinic(int p,int q){
	ds=p,dt=q;
	rep(E,2,ecnt0)e[E].w=e[E].w0;
	int tot=0;
	while(bfs())tot+=dfs(ds,inf);
	return tot;
}

void plant(int l,int r){
	if(l==r)return;
	edge(dot[l],dot[r],dinic(dot[l],dot[r]),ez,fstz);
	int zuo=l-1,you=r+1;
	rep(i,l,r){
		if(dep[ dot[i] ])zuo++,dotz[zuo]=dot[i];
		else you--,dotz[you]=dot[i];
	}
	rep(i,l,r)dot[i]=dotz[i];
	plant(l,zuo),plant(you,r);
}

void dfst(int o){
	rep(i,1,11)fc[o][i]=fc[ fc[o][i-1] ][i-1];
	rep(i,1,11)mni[o][i]=min(mni[o][i-1],mni[ fc[o][i-1] ][i-1]);
	mar(o,fstz,ez){
		if(dept[vz])continue;
		dept[vz]=dept[o]+1;
		fc[vz][0]=o,mni[vz][0]=ez[E].w;
		dfst(vz);
	}
}

int Dlca(int p,int q){
	if(dept[p]<dept[q])p^=q^=p^=q;
	int fin=inf;
	per(i,11,0){
		if(dept[ fc[p][i] ]<dept[q])continue;
		fin=min(fin,mni[p][i]);
		p=fc[p][i];
	}
	if(p==q)return fin;
	per(i,11,0){
		if(fc[p][i]==fc[q][i])continue;
		fin=min(fin,min(mni[p][i],mni[q][i]));
		p=fc[p][i],q=fc[q][i];
	}
	fin=min(fin,min(mni[p][0],mni[q][0]));
	return fin;
}

int solve(int o,bool sys){
	memset(u,0,sizeof u);
	int tot=0;u[o]=1;
	if(sys)printf("%d ",o);
	rep(i,1,n-1){//有n个点,起点已经确定,还有n-1个点要走
		int maxx=0,maxn;
		rep(j,1,n){
			if(u[j])continue;//终点走过了的边就跳过
			if(mc[o][j]>maxx)maxx=mc[o][j],maxn=j;//找到边权最大的
		}
		tot+=maxx,o=maxn,u[maxn]=1;//替换当前点
		if(sys)printf("%d ",o);
	}
	return tot;
}

int main(){
	n=rd(),m=rd();
	rep(i,1,n)dot[i]=i;
	rep(i,1,m){
		int sta=rd(),edn=rd(),w=rd();
		edge(sta,edn,w,e,fst);
	}
	ecnt0=ecnt,ecnt=0;
	plant(1,n);
	dept[1]=1,dfst(1);
    //前面都是最小割树的板子
	rep(i,1,n)rep(j,i+1,n){
		mc[i][j]=mc[j][i]=Dlca(i,j);
	} 
    //mc[i][j]就是点i,j的最小割
	rep(i,1,n){//枚举起点
		int now=solve(i,0);
		if(now>ansx)ansx=now,ansn=i;//如果更优,就选它
	}
    //输出
	printf("%d\n",ansx);
	solve(ansn,1);
	return 0;
}
posted @ 2021-01-03 22:08  BlankAo  阅读(143)  评论(0编辑  收藏  举报