• P3308


    upd 11.2:谁是鸽王?

    突然发现最小割可行边和必须边相关结论我还不是很清楚,即使以前做过相关的题。那就在这篇题解里补一下可行边和必须边,然后介绍一下退流罢。

    最小割可行边和必须边

    对割 ((S,T))​​​​,在残量网络上 (S)​​​​ 到 (T)​​​​ 没有边。那么我们不难知道:若残量网络上 (x o y)​​​​ 存在路径,那么 (xin S,yin T)​ 必定不成立。因为若不然,不论怎么划分 (S,T)​,必定在 (x o y)​ 路上至少有一条边在 (S)​ 到 (T)​​​​ 之间。

    那么 ((x,y))​ 是可行边的必要条件显然是 (x)​ 在残量网络上不能到达 (y)​。同时不难证明充分性:直接通过从 (s)​ 开始在残量网络上 dfs 选取所有能到达的节点组成 (S)​ 的方式,此时显然是一种 (xin S,yin T)​ 的方案。

    若在残量网络上 (s o x,y o t) 都可达(由于 (s o t) 不可达,此时不难知道 (x o y) 不可达),那么必然有 (xin S,yin T),此时 ((x,y)) 是必须边。同时不难证明必要性。不难发现逻辑链:必须边 $ o $​ 可行边 ( o) ((x,y)) 满流,其中 (P o Q) 表示 (Q)(P) 的必要条件。

    退流

    在一个增广到不能再增广的网络上,我们想知道将某条边容量减小到某个值之后,残量网络是什么样的(并且不允许重新建图,那样复杂度太大了)。如果是增加容量的话,那显然可以直接加,然后直接 dinic 增广到底。那减小容量怎么做呢?

    考察增加容量后动态 dinic 的原理:其实就是在原网络的任意可行流对应的残量网络上增广,最终都可以到达最大流状态,跟据最大流最小割定理即可证明。那么我们试图花费较小的代价将减小容量后的残量网络上调整到一个合法的状态,然后 dinic 即可(dinic 之前网络几乎增广完毕)。

    设减小的边原来是 ((x,y,c,f))​​​,容量想要减小到 (c')​​​,那么考虑将流量调整到 (f'=min(f,c'))​​​​,​此时 ((y,x))​​​ 的流量是 (-f')​​​。此时虽然满足了容量限制,但是在 (x,y)​​ 处又不满足流量守恒了,我们需要在 (x)​ 处多流出去 (f-f')​,在 (y)​ 处多流入 (f'-f)​。我们可以利用 (s)​ 可以无限流出,(t)​ 可以无限流入的特点,从 (x o s,t o y)​​ 分别跑一遍 dinic 增广,并且限制多流 (f-f')​ 的流量。我们有自信证明:至少有 (f-f')​ 的流量可以流,毕竟在原先的残量网络上 (s o x,y o t)​ 已经流了至少 (f-f')​ 的流量,通过留下来的反悔边即可知道。此时我们已经得到一个可行流对应的残量网络,那么(如果立刻就要知道最大流的话)再跑一遍 (s o t)​ dinic 即可。

    总结一下:先调整 ((x,y))​ 的流量,然后跑 (mathrm{dinic}(x,s,f-f'),mathrm{dinic}(t,y,f-f'))​,最后跑一遍 (mathrm{dinic}(s,t))。我们显然可以用退流来进行删边操作,因为这等效于将容量降到 (0)

    本题题解

    比较好想。DP 出 LIS,那么所有 LIS 就是满足 (dp_{i_1}=1,dp_{i_{|i|}}=mx) 且对所有 (jin[1,|i|))(a_{i_j}<a_{i_{j+1}},dp_{i_j}+1=dp_{i_{j+1}})。这样把所有 (i_j o i_{j+1})​ 连边,(s o i_1,i_{|i|} o t)​,然后第一问显然就是最小割点集,点边转化跑最大流即可。

    考虑第二问。由于 (c_i) 互不相同,可以直接排序贪心。chk 是否选当前 (c_i) 的条件是选了 (c_i) 后依然存在至少一组最小割,这其实就是说 (c_i) 是最小割可行边,直接调用 dinic 的 bfs 函数即可。如果确实是可行边,那么要把 (c_i) 对应的边删掉,往下继续贪心,这需要用到退流技术。

    不过这里退流有两点可以写得更简单(找遍了全网的题解,基本上只有两三篇讲明白了为什么可以这样简化,其它的都直接糊弄过去了,我估计这些题解里只有 5% 的作者真正理解了退流):

    1. 退流之后不需要重新跑 dinic,因为退完之后必定是最大流状态。因为割掉的 ((x,y,f)) 是最小割可行边,将它割去之后最小割(即最大流)必定是 (F-f),其中 (F) 是原最大流。而退流算法在重新增广之前也显然恰好会使当前可行流变为 (F-(f-f')),其中 (F)​ 是原最大流。而 (f'=0)​,这就说明退流之后恰好达到了新最大流,不用再增广了。以及并不需要时刻记录最大流的流量,因为咱根本就不关心流量,只关心残量网络上连通性。
    2. 退流 dinic 可以不限制流量(就是 (+infty))(虽然也没好写多少),因为 (x o s,t o y) 的最大流恰好等于 (f)。我们早已知道最大流不低于 (f),由于这题的特殊性,最大流也不超过 (f)。证明:显然 (x)(即入点)只有 ((x,y)) 一条出边,其它都是入边。而这唯一一条出边已经在 dinic 前被删成 (0) 了,那么所有的出边都是由 (s o x) 的所有流量造成的反悔边,得证。

    怕卡常贴了个前向星的 dinic 板子,然后还是卡。看题解发现了 wll 一个有效的卡常方法:bfs 到 (t) 就直接 return;,直接快的飞起。

    code
    #include<bits/stdc++.h>
    using namespace std;
    #define int long long
    #define pb push_back
    #define mp make_pair
    #define X first
    #define Y second
    const int inf=0x3f3f3f3f3f3f3f3f;
    const int N=1410;
    int n;
    struct addedge{
    	int sz,head[N],nxt[N*N/7],to[N*N/7],flw[N*N/7];
    	addedge(){sz=1;memset(head,0,sizeof(head));}
    	void ae(int x,int y,int z){
    		nxt[++sz]=head[x],head[x]=sz,to[sz]=y,flw[sz]=z;
    	}
    }nei;
    void ae(int x,int y,int z){
    	nei.ae(x,y,z),nei.ae(y,x,0);
    }
    int dep[N+1];
    bool bfs(int s,int t){
    	memset(dep,-1,sizeof(dep));
    	dep[s]=0;
    	queue<int> q;
    	q.push(s);
    	while(q.size()){
    		int x=q.front();
    		q.pop();
    		for(int i=nei.head[x];i;i=nei.nxt[i]){
    			int y=nei.to[i],c=nei.flw[i];
    			if(c&&!~dep[y]){
    				dep[y]=dep[x]+1,q.push(y);
    				if(y==t)return true;
    			}
    		}
    	}
    	return false;
    }
    int now[N+1];
    int dfs(int x,int t,int flw){
    	if(x==t||!flw)return flw;
    	int res=0,tmp;
    	for(int &i=now[x];i;i=nei.nxt[i]){
    		int y=nei.to[i],&c=nei.flw[i];
    		if(dep[y]==dep[x]+1&&(tmp=dfs(y,t,min(flw,c)))){
    			res+=tmp,flw-=tmp;
    			c-=tmp,nei.flw[i^1]+=tmp;
    			if(!flw)break;
    		}
    	}
    	return res;
    }
    int dinic(int s,int t){
    	int res=0;
    	while(bfs(s,t)){
    		for(int i=1;i<=2*n+2;i++)now[i]=nei.head[i];
    		res+=dfs(s,t,inf);
    	}
    	return res;
    }
    int a[N],b[N],c[N];
    int dp[N];
    int ord[N];
    bool cmp(int x,int y){return c[x]<c[y];}
    void mian(){
    	scanf("%lld",&n);
    	for(int i=1;i<=n;i++)scanf("%lld",a+i);
    	for(int i=1;i<=n;i++)scanf("%lld",b+i);
    	for(int i=1;i<=n;i++)scanf("%lld",c+i); 
    	int mx=0;
    	for(int i=1;i<=n;i++){
    		dp[i]=1;
    		for(int j=1;j<i;j++)if(a[j]<a[i])dp[i]=max(dp[i],dp[j]+1);
    		mx=max(mx,dp[i]);
    	}
    	int s=2*n+1,t=2*n+2;
    	nei=addedge();
    	for(int i=1;i<=n;i++)for(int j=1;j<i;j++)if(a[j]<a[i]&&dp[j]+1==dp[i])ae(j+n,i,inf);
    	for(int i=1;i<=n;i++){
    		if(dp[i]==1)ae(s,i,inf);
    		if(dp[i]==mx)ae(i+n,t,inf);
    	}
    	for(int i=1;i<=n;i++)ae(i,i+n,b[i]),ord[i]=i;
    	cout<<dinic(s,t)<<" ";
    	vector<int> ans;
    	sort(ord+1,ord+n+1,cmp);
    	for(int i=1;i<=n;i++){
    		int x=ord[i],fd=nei.head[x];
    		if(bfs(x,x+n))continue;
    		ans.pb(x);
    		nei.flw[fd]=nei.flw[fd^1]=0;
    		dinic(x,s),dinic(t,x+n);
    	}
    	sort(ans.begin(),ans.end()); 
    	cout<<ans.size()<<"
    ";
    	for(int i=0;i<ans.size();i++)cout<<ans[i]<<" 
    "[i+1==ans.size()];
    }
    signed main(){
    //	freopen("4.in","r",stdin);
    	int t=1;
    	scanf("%lld",&t);
    	while(t--)mian();
    	return 0;
    }
    
    珍爱生命,远离抄袭!
  • 相关阅读:
    tricky c++ new(this)
    MCI使用
    LoadIcon的使用
    深入浅出Node.js (2)
    洛谷 P1464 Function
    洛谷 P1722 矩阵 II
    洛谷 P1036 选数
    洛谷 P1303 A*B Problem
    洛谷 P2694 接金币
    洛谷 P1679 神奇的四次方数
  • 原文地址:https://www.cnblogs.com/ycx-akioi/p/solution-p3308.html
Copyright © 2020-2023  润新知