zoukankan      html  css  js  c++  java
  • 【BZOJ2502】清理雪道(最大费用最大流)

    上下界网络流的做法大佬们都讲过了,我就来讲一个另类的解法。(不过也是网络流)

    这个做法是考场上想了很久都不会后奇思妙想想出来的(不会上下界网络流),所以没多少思路引导。

    首先原题明显可以转移成一个类似最小链覆盖的问题。

    剩下的就是我的具体做法:

    我们对原来的有向无环图进行改造:

    上面的是原图,我们把图中的每一条边 e i e_i ei 拆成两条边:一条流量为 1 1 1,费用为 1 1 1,不妨取名叫 a i a_i ai;一条流量为 ∞ infty ,费用为 0 0 0,取名叫 b i b_i bi。然后源点向每个入度为 0 0 0 的点(也就是链覆盖的起点)连边,流量 ∞ infty ,费用 0 0 0;每个出度为 0 0 0 的点(也就是链覆盖的终点)向汇点连边,流量 ∞ infty ,费用 0 0 0

    然后跑最大费用最大流。

    意思就是说每次增广时,相当于我新覆盖一条链。在增广中:

    如果某条原图中的边 e i e_i ei 没有被走过,那么为了满足最大费用,程序肯定会选择流费用为 1 1 1 的那条边 a i a_i ai,费用增加 1 1 1,代表我新清理了一条雪道,即新访问了一条原图中的边;

    如果这条边 e i e_i ei 被走过了,那么费用为 1 1 1 的那条边肯定被流满了,只能流费用为 0 0 0 的边,不贡献费用,意思就是我走过的这条边在我前几次覆盖时已经被覆盖了,也就是说我只是路过这,并不要清理这。

    然后根据最大费用最大流的特性,每次增广完后,能保证所得的解是当前的最优解,也就是说它能保证用最优方法覆盖所有边。

    那么当贡献的费用等于总边数时,整个图也就被我们覆盖完了,答案就是链的条数,也就是覆盖的次数,或者说增广的次数。

    整道题也就结束了。

    顺带说一下,其实这种跑网络流却不跑完,把网络流当成一个类似贪心的工具来使用的 trick 挺实用也挺常见的(类似数学中的设而不求),比如 BZOJ2893征服王 也可以用这个 trick 做,而且比这个麻烦一点,所以建议总结一下(

    代码如下:

    #include<bits/stdc++.h>
    
    #define N 110
    #define M 10010
    #define INF 0x7fffffff
    
    using namespace std;
    
    inline int read()
    {
    	int x=0,f=1;
    	char ch=getchar();
    	while(ch<'0'||ch>'9')
    	{
    		if(ch=='-') f=-1;
    		ch=getchar();
    	}
    	while(ch>='0'&&ch<='9')
    	{
    		x=(x<<1)+(x<<3)+(ch^'0');
    		ch=getchar();
    	}
    	return x*f;
    }
    
    int n,s,t,tot,ans;
    int rudu[N],chudu[N];
    int cnt=1,head[N],to[N*4+M*4],nxt[N*4+M*4],c[N*4+M*4],w[N*4+M*4];
    int pre[N],dis[N];
    bool inq[N];
    
    queue<int>q;
    
    void adde(int u,int v,int ci,int wi)
    {
    	to[++cnt]=v;
    	c[cnt]=ci;
    	w[cnt]=wi;
    	nxt[cnt]=head[u];
    	head[u]=cnt;
    	
    	to[++cnt]=u;
    	c[cnt]=0;
    	w[cnt]=-wi;
    	nxt[cnt]=head[v];
    	head[v]=cnt;
    }
    
    bool SPFA()
    {
    	memset(dis,128,sizeof(dis));
    	q.push(s);
    	dis[s]=0;
    	inq[s]=1;
    	while(!q.empty())
    	{
    		int u=q.front();
    		q.pop();
    		inq[u]=0;
    		for(int i=head[u];i;i=nxt[i])
    		{
    			int v=to[i];
    			if(c[i]&&dis[u]+w[i]>dis[v])
    			{
    				dis[v]=dis[u]+w[i];
    				pre[v]=i;
    				if(!inq[v])
    				{
    					inq[v]=1;
    					q.push(v);
    				}
    			}
    		}
    	}
    	return dis[t]!=dis[0];
    }
    
    void MCMF()
    {
    	int maxcost=0,maxflow=0;
    	while(SPFA())
    	{
    		int minflow=INF;
    		for(int u=t;u!=s;u=to[pre[u]^1]) 
    			minflow=min(minflow,c[pre[u]]);
    		for(int u=t;u!=s;u=to[pre[u]^1])
    		{
    			c[pre[u]]-=minflow;
    			c[pre[u]^1]+=minflow;
    			maxcost+=minflow*w[pre[u]];
    		}
    		maxflow+=minflow;
    		ans++;//统计增广次数(链的条数)
    		if(maxcost==tot) break;//如果最大费用达到总边数
    	}
    }
    
    int main()
    {
    	n=read();
    	s=1,t=1+n+1;
    	for(int i=1;i<=n;i++)
    	{
    		int m=read();
    		tot+=m;
    		for(int j=1;j<=m;j++)
    		{
    			int v=read();
    			adde(1+i,1+v,1,1);//ai
    			adde(1+i,1+v,INF,0);//bi
    			chudu[i]++,rudu[v]++;
    		}
    	}
    	for(int i=1;i<=n;i++)
    	{
    		if(!rudu[i]) adde(s,1+i,INF,0);//源点连入度为0的点
    		if(!chudu[i]) adde(1+i,t,INF,0);//出度为0的点连汇点
    	}
    	MCMF();//最大费用最大流
    	printf("%d
    ",ans);
    	return 0;
    }
    /*
    5
    1 2
    0
    2 2 4
    0
    1 4
    */
    
  • 相关阅读:
    flume1.7.0的安装与使用
    获取top10
    editplus格式化xml文档
    LOG4J.PROPERTIES配置详解
    Oracle自增列
    javascript 传递引用类型参数
    {JavaScript}栈和堆内存,作用域
    JAVA中String与StringBuffer的区别
    Java中堆和栈的区别(转)
    JAVA错误:org.apache.jasper.JasperException: java.lang.ClassCastException:org.apache.catalina.util.DefaultAnnotationProcessor cannot be cast to org.apach
  • 原文地址:https://www.cnblogs.com/ez-lcw/p/14448659.html
Copyright © 2011-2022 走看看