(Large atural) 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
",ansx);
solve(ansn,1);
return 0;
}