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 
#include 
#include 
#include 
#include 
#include 
#include 
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\n",ans[i]);
		else { for(int i=1;i<=cnt;i++) printf("%lld\n",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.valQ;
	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\n",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\n",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.pos0) 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(headinf) 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.valQ;
	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\n",lin.dinic(S,T));
		a.build(); if(a.val0 && (!Q.empty())) {
			a=b=Q.top(); Q.pop(); printf("%lld\n",a.val);
			//强制选
			a.must[a.id]=1; a.build(); if(a.val0) 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;
}

  

转载于:https://www.cnblogs.com/ljh2000-jump/p/6268767.html

你可能感兴趣的:(UOJ71 【WC2015】k小割)