【图论,网络流】CF1525F Goblins And Gnomes

发布时间 2023-07-13 17:38:18作者: CharlieVinnie

Problem Link

你在打怪。你有一个 \(n\) 个点 \(m\) 条边的 DAG,接下来会有 \(k\) 波怪来袭,第 \(i\) 波怪有 \(i\) 个,它们会各自选择走一条路径,要求它们所选的路径点不相交。如果某一波中所有点都被覆盖了,那你就输了。

你不能输。于是在第 \(i\) 波之前,你可以花费若干分钟备战,每分钟可以选一个点将它的入边或者出边全部删掉。给定 \(a_i,b_i\),如果你在第 \(i\) 分钟花了 \(x\) 分钟备战,则你在这一波的得分为 \(\max(0,a_i-b_ix)\)

你想拿分。你的总得分为每一波的得分之和。在不输的前提下最大化这个总得分。输出方案。

\(n\le 500,m\le n(n-1)/2,k<n\)


首先这个是最小不交链覆盖,直接拆点二分图,转化成二分图最大匹配。对于一张图,最小链覆盖的大小即为 \(n-\) 最大匹配大小。

然后考虑操作是什么,删去一个点的所有出边或所有入边?就是删掉二分图上的一个点!

问题变成了删点使得最大匹配减小。然后我们记起来一点,最大匹配 \(=\) 最小点覆盖!那么删掉最小点覆盖上一个点一定会使得最大匹配减小。因此一定有一种方案使得最大匹配减小。

怎么找到这个方案?考虑如果是左部点,合法的充要条件是删掉它之后不存在一条从 \(S\) 到它的匹配点的路径。然后又注意到一点,如果一个左部点合法,那么只有它的匹配点是指向它的,所以不存在 \(S\) 到它的匹配点的路径等价于不存在 \(S\) 到它的路径。于是我们就进行 \(n\) 轮 dfs,每轮 dfs 从 \(S\) 开始搜,找出一个 \(S\) 无法到达的左部点删掉(如果找不到就从 \(T\) 开始对称地搜,两者一定能找到其一)。找到要删的点之后记得把从它到 \(T\)(或从 \(S\) 到它)的流退掉。每删一个点只用花 \(O(n^2)\) 的时间遍历,这部分复杂度 \(O(n^3)\)

然后得分是诈骗的。直接硬上一个 \(O(n^3)\) 的 dp 即可。前面这么麻烦是因为要输出方案。

总时间复杂度 \(O(n^3)\)

点击查看代码
#include <bits/stdc++.h>
#define For(i,a,b) for(int i=a;i<=b;i++)
#define Rev(i,a,b) for(int i=a;i>=b;i--)
#define Fin(file) freopen(file,"r",stdin);
#define Fout(file) freopen(file,"w",stdout);
using namespace std;
const int N=505,M=N*N; using ll = long long;
int n,m,S,T,K,head[N],nxt[M],to[M],dir[M],tot,vis[N],st[N],tp,lis[N],lcnt,num[N],g[N][N]; ll f[N][N];
bool dfs(int u,int o,function<bool(int)> Done){
	vis[u]=1; if(Done(u)) return true;
	for(int e=head[u];e;e=nxt[e]) if(dir[e]==o&&!vis[to[e]]){
		st[++tp]=e; if(dfs(to[e],o,Done)) return true; else tp--;
	}
	return false;
}
void add(int x,int y){	
	nxt[++tot]=head[x]; head[x]=tot; to[tot]=y; dir[tot]=1;
	nxt[++tot]=head[y]; head[y]=tot; to[tot]=x; dir[tot]=0;
}
bool ck(ll& x,ll y) { return x<y?x=y,true:false; }
int main(){
	cin>>n>>m>>K; tot=1;
	For(i,1,m){
		int x,y; cin>>x>>y; x=x*2-1; y=y*2; add(x,y);
	}
	S=n*2+1,T=n*2+2; int tt=n;
	For(i,1,n*2) i&1?add(S,i):add(i,T);
	while(true){
		tp=0; For(i,1,T) vis[i]=0;
		if(!dfs(S,1,[&](int u){return u==T;})) break;
		tt--; Rev(i,tp,1) dir[st[i]]^=1,dir[st[i]^1]^=1;
	}
	int ww=tt;
	while(tt<=K){
		tp=0; For(i,1,T) if(vis[i]!=-1) vis[i]=0;
		dfs(S,1,[&](int){return false;});
		int o=0; for(int i=1;i<2*n;i+=2) if(!vis[i]) { o=i; break; }
		if(o==0){
			tp=0; For(i,1,T) if(vis[i]!=-1) vis[i]=0;; dfs(T,0,[&](int){return false;});
			for(int i=2;i<=2*n;i+=2) if(!vis[i]) { o=i; break; }
		}
		assert(o); num[tt++]=o;
		tp=0; For(i,1,T) if(vis[i]!=-1) vis[i]=0;
		if(o&1) assert(dfs(T,1,[&](int u){return u==o;})); else assert(dfs(S,0,[&](int u){return u==o;}));
		Rev(i,tp,1) dir[st[i]]^=1,dir[st[i]^1]^=1;
		vis[o]=-1;
	}
	memset(f,0xaf,sizeof(f)); f[0][0]=0;
	For(i,1,K){
		ll x,y; cin>>x>>y;
		For(j,0,n) For(k,max(j,max(0,i-ww+1)),n){
			if(ck(f[i][k],f[i-1][j]+max(0ll,x-(k-j)*y))) g[i][k]=j;
		}
	}
	ll ans=f[0][1]; int j=-1; For(i,0,n) if(ck(ans,f[K][i])) j=i;
	vector<int> Ans;
	Rev(i,K,1){
		Ans.push_back(0); int k=g[i][j];
		while(j>k) Ans.push_back(num[--tt]),j--;
	}
	reverse(Ans.begin(),Ans.end());
	cout<<Ans.size()<<'\n';
	for(int x:Ans){
		if(x==0) cout<<x<<' ';
		else if(x&1) cout<<(x+1)/2<<' ';
		else cout<<-x/2<<' ';
	}
	cout<<'\n';
	return 0;
}