• UOJ71 【WC2015】k小割


    本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作。

    本文作者:ljh2000 
    作者博客:http://www.cnblogs.com/ljh2000-jump/
    转载请注明出处,侵权必究,保留最终解释权!

     

    Description

     

    Input

    Output

     

    Sample Input

    3 3 1 3 100
    1 2 3
    2 3 4
    1 3 5

    Sample Output

    8
    9
    12
    -1

     

    正解:搜索+堆+网络流(最小割)

    解题报告:

      这道题写了我好久,怒写7KB...

      强行把三个程序组合才A掉...

      有用的博客:http://africamonkey.blog.uoj.ac/blog/108

            http://www.cnblogs.com/New-Godess/p/4348890.html

     

      Task1:

        注意到前两个测试点n、m很小,直接搜索;

     

      Task2:

        7到14这8个数据具有共同特点:s有边连向所有非t节点,所有非 s结点有边连向t;

        那么我们考虑对于除了S、T之外的所有点来说,都连向S、T,那么只要任意断掉一条即可成为割,而两条都断掉那自然也是合法的。

        我们可以把题目转化为每个集合有三个元素:a,b,a+b(不妨设a<=b),需要从中选取一个,一共有n-2个集合,问全局选取的总和最小的前k个。

        显然,每个集合都取a时,对于全局而言就是最小割了,下面我们考虑如何通过这个最优值转化出较优值,显然接下来我们需要使得每个集合的选择发生改变,

        不妨作差,把每个集合重新定义为大小为2,元素为{b-a,b}的集合,将其按先后两 个关键字排序,则每次只需加上我当前的值即可。

        这样我们不难得到一个算法:每次把当前的集合做三种变换:

        1、把当前的升级

        2、把当前的降级,后一个升级

        3、直接往后取,把下一个集合的升级。用堆维护即可;

     

      Task3:

        对于n、m不很大的情况,我们考虑从割的角度解决这个问题,假设我们得到了最小割,想得到次小割,显然我们是把最小割中的某一条边换成另一条边,或者加入一条权值最小的新的边。

        如果直接暴力执行上述操作的话会T,仔细思考就会发现问题关键在于如何优化换边的步骤,其实我们无需每次枚举换哪条边,我们可以考虑最小割中的每条边的两个端点到S、T的割的值,取个             min之后就能得到把这条边换掉之后的增长的代价,从而我们得到了一个简单的思路,只需对于每条边求一遍两个端点到S、T的最小割再取min即可,做法大致就是这样。

        但是有很多很多的细节,难以赘述,看代码吧,有详细注释。

     

    //It is made by ljh2000
    #include <iostream>
    #include <cstdlib>
    #include <cstring>
    #include <cstdio>
    #include <cmath>
    #include <algorithm>
    #include <queue>
    using namespace std;
    typedef long long LL;
    const int MAXN = 150011;
    const int MAXM = 300011;
    int n,m,S,T,k;
    int AA[MAXM],BB[MAXM],CC[MAXM];
    int ecnt,first[MAXN],w[MAXM],to[MAXM],next[MAXM];//have direction!!!
    inline int getint(){
        int w=0,q=0; char c=getchar(); while((c<'0'||c>'9') && c!='-') c=getchar();
        if(c=='-') q=1,c=getchar(); while (c>='0'&&c<='9') w=w*10+c-'0',c=getchar(); return q?-w:w;
    }
    
    //Task1:search
    namespace Search{
    	int cnt,father[MAXN],Tim,vis[MAXN];
    	bool stop[MAXM];
    	LL ans[1200011];
    	inline int find(int x){ if(father[x]!=x) father[x]=find(father[x]); return father[x]; }
    	inline bool check(int x){
    		if(x==T) return true; vis[x]=Tim;
    		for(int i=first[x];i;i=next[i]) {
    			if(stop[i]) continue ; int v=to[i];
    			if(vis[v]==Tim) continue;
    			if(check(v)) return true;
    		}		
    		return false;
    	}
    	inline void dfs(int x,LL val){
    		if(x==m+1) { Tim++;	if(!check(S)) ans[++cnt]=val; return ;}
    		stop[x]=1; dfs(x+1,val+w[x]);
    		stop[x]=0; dfs(x+1,val);
    	}
    	inline void work(){ 
    		int x,y,z;
    		for(int i=1;i<=m;i++) {	x=AA[i]; y=BB[i]; z=CC[i]; next[++ecnt]=first[x]; first[x]=ecnt; to[ecnt]=y; w[ecnt]=z; }
    		dfs(1,0); sort(ans+1,ans+cnt+1);
    		if(cnt>=k) for(int i=1;i<=k;i++) printf("%lld
    ",ans[i]);
    		else { for(int i=1;i<=cnt;i++) printf("%lld
    ",ans[i]); printf("-1"); }
    	}
    }
    
    //Task3:S links nodes,nodes link T
    //nlogn+klogn
    namespace StoT{	
    	LL ans;
    	struct node{ LL val; int pos,id; inline bool operator < (const node &a)const { return a.val<val; }}tmp,Top;
    	struct seq{ int x,y; }a[MAXN],b[MAXN];
    	inline bool cmp(seq q,seq qq){ if(q.x==qq.x) return q.y<qq.y; return q.x<qq.x; }
    	priority_queue<node>Q;
    	inline void work(){
    		int x,y; int cnt=0;
    		for(int i=1;i<=m;i++) {
    			x=AA[i]; y=BB[i];
    			if(x==S) b[y].x=CC[i];
    			else b[x].y=CC[i];
    		}
    		for(int i=1;i<=n;i++) {
    			if(i==S||i==T) continue; 
    			a[++cnt]=b[i];/*!!!*/
    			if(a[cnt].x>a[cnt].y) swap(a[cnt].x,a[cnt].y);
    			x=a[cnt].x;	ans+=a[cnt].x; a[cnt].x=a[cnt].y-a[cnt].x; a[cnt].y=x;
    		}
    		printf("%lld
    ",ans); k--;
    		sort(a+1,a+cnt+1,cmp);
    		tmp.val=ans+a[1].x; tmp.pos=1; tmp.id=1; Q.push(tmp);
    		while(k>0&&(!Q.empty())) {
    			Top=Q.top(); Q.pop();
    			printf("%lld
    ",Top.val);
    			//choice 1:把当前的升级
    			if(Top.id<2) {
    				tmp.val=Top.val+a[Top.pos].y;
    				tmp.pos=Top.pos;
    				tmp.id=2;
    				Q.push(tmp);
    			}
    			//choice 2:把当前的降级,后一个升级
    			if(Top.id==1 && Top.pos<cnt/*!!!*/) {//只需考虑当前取了第一个元素的情况,当前取了第二个元素的话降级再把后一个升级,等价于直接往后取
    				tmp.val=Top.val-a[Top.pos].x;
    				tmp.val+=a[Top.pos+1].x;
    				tmp.pos=Top.pos+1;
    				tmp.id=1;
    				Q.push(tmp);
    			}
    			//choice 3:直接往后取,把下一个集合的升级
    			if(Top.pos<cnt/*!!!*/) {
    				tmp.val=Top.val+a[Top.pos+1].x;/*!!!*/
    				tmp.pos=Top.pos+1;
    				tmp.id=1;
    				Q.push(tmp);
    			}
    			k--;
    		}
    		if(k>0) printf("-1");
    	}
    }
    
    //Task2:n、m很小,无别的特殊条件
    //考虑和Task3的类似做法,我们如何将最小割中的边转化为次小割。
    //显然我们考虑最小割中权值最小的边,有两种可能:1、强制不选这条边,即将其删除,再跑一次最小割;2、强制选这条边,再选一条权值最小的边
    namespace network_flow{
    	const int N=52,M=3005;
    	const int inf = (1<<28);
    	int X[M],Y[M],W[M],ecnt=1,first[N],next[M],to[M],dui[M*10],head,tail,deep[N];
    	int ds[N],dt[N];//预处理出每个点到S和T的最小割
    	bool havs[N],havt[N];//是否已经做过最小割
    	bool in[M/*!!!*/];//是否在最小割边集中
    	bool vis[N];	
    	struct Graph{
    		int w[M];
    		inline void link(int x,int y,int z){
    			next[++ecnt]=first[x]; first[x]=ecnt; to[ecnt]=y; w[ecnt]=z;
    			next[++ecnt]=first[y]; first[y]=ecnt; to[ecnt]=x; w[ecnt]=0;
    		}
    		inline bool bfs(int s,int t){
    			int head=tail=0; dui[++tail]=s; int u;
    			for(int i=1;i<=n;i++) deep[i]=-1; deep[s]=1;
    			while(head<tail) {
    				head++; u=dui[head];
    				for(int i=first[u];i;i=next[i]) {
    					if(w[i]==0) continue; int v=to[i];
    					if(deep[v]==-1) { deep[v]=deep[u]+1; dui[++tail]=v; }
    				}
    			}
    			if(deep[t]==-1) return false;
    			return true;
    		}
    		inline LL dinic(int x,int remain,int t){
    			if(x==t||remain==0) return remain; LL flow=0,f;
    			for(int i=first[x];i;i=next[i]) {
    				if(w[i]==0) continue; int v=to[i]; if(deep[v]!=deep[x]+1) continue;
    				f=dinic(v,min(remain,w[i]),t);
    				if(f==0) deep[v]=-1; else { flow+=f,remain-=f; w[i]-=f; w[i^1]+=f; if(remain==0) return flow; }
    			}
    			return flow;
    		}
    		inline LL dinic(int s,int t){
    			LL tot=0; while(bfs(s,t)) { tot+=dinic(s,inf,t); if(tot>inf) return tot; }
    			return tot;
    		}
    		inline void DFS(int x){
    			vis[x]=1;
    			for(int i=first[x];i;i=next[i]) if(w[i]&&(!vis[to[i]])) DFS(to[i]);
    		}
    		inline void get_cut(){
    			memset(vis,0,sizeof(vis)); memset(in,0,sizeof(in));
    			DFS(S);
    			for(int i=1;i<=m;i++) if(vis[X[i]] && (!vis[Y[i]])) in[i]=1;//!!!割边的判断
    		}
    	}G,lin,lin2;
    	struct heap_node{
    		LL val; int id,ww;//选出的边的编号
    		bool must[M],stop[M];
    		inline bool operator < (const heap_node &a) const{ 	return a.val<val; }
    		inline void build(){//找出最小割中最小的那条边,要么强制其不选再跑一遍最小割;或者强制选再选一条最短的边
    			val=0; lin=G;//!!!还原
    			for(int i=1;i<=m;i++) if(must[i]) val+=W[i],lin.w[i<<1]=0,lin.w[i<<1|1]=0; else if(stop[i]) lin.w[i<<1]=inf,lin.w[i<<1|1]=0;
    			val+=lin.dinic(S,T);
    			lin.get_cut();
    			memset(havs,0,sizeof(havs)); memset(havt,0,sizeof(havt));
    			ww=inf; id=0;
    			for(int i=1;i<=m;i++) {
    				if(must[i]||stop[i]) continue;
    				if(in[i]) {//此处为快速找到次小割的方法,无需真的去再做一遍dinic,只需看一下到S、T的最小割,取min之后即变化值
    					if(!havs[X[i]]) havs[X[i]]=1,lin2=lin,ds[X[i]/**/]=lin2.dinic(S,X[i]);//为了不破坏原来的残量网络,新建一个再跑
    					if(!havt[Y[i]]) havt[Y[i]]=1,lin2=lin,dt[Y[i]/**/]=lin2.dinic(Y[i],T);
    					if(ds[X[i]]/*!!!是点而不是边的最小割*/<ww) ww=ds[X[i]],id=i;
    					if(dt[Y[i]]/*!!!*/<ww) ww=dt[Y[i]],id=i;
    					/*if(ww==0) {
    						id++;
    						id--;
    					}*/
    				}
    				else if(W[i]<ww) ww=W[i],id=i;
    			}
    			val+=ww;
    		}
    	}a,b;
    	priority_queue<heap_node>Q;
    	inline void work(){
    		for(int i=1;i<=m;i++) {
    			X[i]=AA[i]; Y[i]=BB[i]; W[i]=CC[i];
    			G.link(X[i],Y[i],W[i]);
    		}
    		k--; lin=G; printf("%lld
    ",lin.dinic(S,T));
    		a.build(); if(a.val<inf) Q.push(a);
    		while(k>0 && (!Q.empty())) {
    			a=b=Q.top(); Q.pop(); printf("%lld
    ",a.val);
    			//强制选
    			a.must[a.id]=1; a.build(); if(a.val<inf) Q.push(a);
    			//强制不选
    			b.stop[b.id]=1; b.build(); if(b.val<inf) Q.push(b);
    			k--;
    		}
    		if(k>0) printf("-1");
    	}
    }
    
    inline void work(){
    	n=getint(); m=getint(); S=getint(); T=getint(); k=getint(); int mx=0;
    	for(int i=1;i<=m;i++) AA[i]=getint(),BB[i]=getint(),CC[i]=getint(),mx=max(mx,CC[i]);
    	if(n<=10 && m<=20) Search::work();
    	else if(n<=50 && m<=1500 && k<=100 && mx<=65536) network_flow::work();
    	else StoT::work();
    }
    
    int main()
    {
        work();
        return 0;
    }
    

      

  • 相关阅读:
    Webpack 打包 14. html压缩
    webpack优化环境配置 18.HMR
    Webpack 打包 10. 压缩CSS
    Wenpack 打包 15. 生产环境配置(提取JS中的CSS、压缩CSS、压缩html、eslint语法检查...)
    Webpack 打包 13. 压缩JS
    webpack优化环境配置 17.优化配置介绍
    Webpack 打包 11. eslint 语法检查
    AtCoder Beginner Contest 258
    AtCoder Beginner Contest 259
    AtCoder Beginner Contest 261
  • 原文地址:https://www.cnblogs.com/ljh2000-jump/p/6268767.html
Copyright © 2020-2023  润新知