1. 程式人生 > 實用技巧 >CF1288F Red-Blue Graph

CF1288F Red-Blue Graph

題面

CF1288F Red-Blue Graph

有一張二分圖,左邊有 \(n_1\) 個點,右邊有 \(n_2\) 個點,\(m\) 條邊。每個點可能有一種顏色 R 或者 B,也可能沒有,也就是 U。現在要給一些邊染色,把邊染成 R 要花費 \(r\) 的代價,把邊染成 B 要花費 \(b\) 的代價,要求對於每個顏色為 R 的點,與之相鄰的邊中 R 的邊嚴格多於 B 的邊;對於每個顏色為 B 的點,與之相鄰的邊中 B 的邊嚴格多於 R 的邊。求花費最小的方案,輸出任意一種,無解輸出 \(-1\)

資料範圍:\(1 \le n_1,n_2,m,r,b\le 200\)


蒟蒻語

蒟蒻昨天打了一場 \(\rm VP\),然後今天補了一個下午的題。中途的時候,zaky 說要打蒟蒻打過的 \(\rm VP\),於是怹去打了。等蒟蒻補完題後蒟蒻看到 zaky 站了起來,說:“朕 AK 了”。

這題有一個不需要上下界只需要最小費用最大流的騷操作,蒟蒻覺得挺妙的於是寫篇題解。


蒟蒻語

先令原圖每個右邊的節點 \(u\) 編號為 \(u+n_1\),有色點個數為 \(cnt\)

首先將一條二分圖上的邊 \((u,v)\) 轉化為網路流上的邊:

\((u,v,flow=1,cost=r)\),如果流了表示選了紅色;

\((v,u,flow=1,cost=b)\),如果流了表示選了藍色;

都沒流表示無色。

對於一個左邊的節點 \(u\),如果顏色為 R,連 \((s,u,flow=1,cost=-\infty),(s,u,flow=\infty,cost=0)\)

對於一個右邊的節點 \(u\),如果顏色為 R,連 \((u,t,flow=1,cost=-\infty),(u,t,flow=\infty,cost=0)\)

這樣就可以引誘網路流演算法先滿足紅點的限制。

對於一個左邊的節點 \(u\),如果顏色為 B,連 \((u,t,flow=1,cost=-\infty),(u,t,flow=\infty,cost=0)\)

對於一個右邊的節點 \(u\),如果顏色為 B,連 \((s,u,flow=1,cost=-\infty),(s,u,flow=\infty,cost=0)\)

這樣就可以引誘網路流演算法先滿足藍點的限制。

對於一個節點 \(u\),如果顏色為 U,連 \((s,u,flow=\infty,cost=0),(u,t,flow=\infty,cost=0)\)

這樣可以使它不優先考慮,同時可以選擇輔助滿足另外兩種顏色的條件。

然後跑最小費用最大流

如果滿足了所有條件,那麼答案肯定包含 \(cnt\)\(-\infty\),所以要給跑出來的總 \(cost\) 加上 \(cnt\cdot \infty\)。如果 \(cost\ge \infty\),那麼肯定有條件沒有滿足,輸出 \(-1\)

這樣直接交會 WA#\(19\),因為有個小問題:雖然這是個最小費用流,但是它畢竟還是個最大流,所以它把所有負權路徑跑完後就開始跑正權路徑了。

如果可以滿足條件,那麼跑負權路徑就夠了,正權路徑相當於選了多餘的有色邊,這是不必要的。

可以發現如果用 \(\rm EK\) 演算法,滿足條件和跑多餘邊肯定是分開計算的,而且肯定是先負權再正權,所有可以在分界點上 break,不再跑網路流。

這個演算法的正確性還基於染色邊的正權代價使得有色邊儘量地少。


程式碼

#include <bits/stdc++.h>
using namespace std;

//Start
typedef long long ll;
typedef double db;
#define mp(a,b) make_pair((a),(b))
#define x first
#define y second
#define be(a) (a).begin()
#define en(a) (a).end()
#define sz(a) int((a).size())
#define pb(a) push_back(a)
#define R(i,a,b) for(int i=(a),I=(b);i<I;i++)
#define L(i,a,b) for(int i=(b)-1,I=(a)-1;i>I;i--)
const int iinf=0x3f3f3f3f;
const ll linf=0x3f3f3f3f3f3f3f3f;

//Mcmf
const int T=402;
int tn,s,t;
vector<int> e[T],to,fw,co;
int  adde(int u,int v,int w,int c){
//	cout<<u<<" - "<<v<<" ("<<w<<" , "<<c<<")\n";
	e[u].pb(sz(to)),to.pb(v),fw.pb(w),co.pb(+c);
	e[v].pb(sz(to)),to.pb(u),fw.pb(0),co.pb(-c);
	return sz(to)-2;
}
int dep[T],pre[T]; bool vis[T];
bool spfa(){
	fill(vis,vis+tn,false);
	fill(dep,dep+tn,iinf);
	fill(pre,pre+tn,-1);
	queue<int> q; q.push(s),vis[s]=true,dep[s]=0;
	while(sz(q)){
		int u=q.front(); q.pop(),vis[u]=false;
		for(int v:e[u])if(fw[v]&&dep[to[v]]>dep[u]+co[v]){
			dep[to[v]]=dep[u]+co[v],pre[to[v]]=v;
			if(!vis[to[v]]) vis[to[v]]=true,q.push(to[v]);
		}
	}
	return dep[t]<iinf;
}
int flow,cost;
void mcmf(){
	while(spfa()){
		if(dep[t]>=0) break; // 題解的精華
		int f=iinf;
		for(int i=t;i!=s;i=to[1^pre[i]]) f=min(f,fw[pre[i]]);
		for(int i=t;i!=s;i=to[1^pre[i]]) fw[pre[i]]-=f,fw[pre[i]^1]+=f;
		cost+=dep[t]*f,flow+=f;
	}	
}

//Data
const int N=200;
int n1,n2,m,r,b,re[N],be[N],cnt;
string sa,sb;

//Main
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n1>>n2>>m>>r>>b>>sa>>sb;
	tn=(t=(s=n1+n2)+1)+1; 
	R(i,0,m){
		int u,v; cin>>u>>v,--u,--v;
		re[i]=adde(u,v+n1,1,r),be[i]=adde(v+n1,u,1,b);
	}
	int mx=4e6;
	R(i,0,n1){
		if(sa[i]=='R') adde(s,i,1,-mx),cnt++;
		else adde(i,t,mx,0);
		if(sa[i]=='B') adde(i,t,1,-mx),cnt++;
		else adde(s,i,mx,0);
	}
	R(i,0,n2){
		if(sb[i]=='R') adde(i+n1,t,1,-mx),cnt++;
		else adde(s,i+n1,mx,0);
		if(sb[i]=='B') adde(s,i+n1,1,-mx),cnt++;
		else adde(i+n1,t,mx,0);
	}
	mcmf(),cost+=cnt*mx;
	if(cost>=mx) cout<<-1<<'\n',exit(0);
	cout<<cost<<'\n';
	R(i,0,m){
		if(fw[re[i]^1]) cout<<'R';
		else if(fw[be[i]^1]) cout<<'B';
		else cout<<'U';
	}
	cout<<'\n';
	return 0;
}

祝大家學習愉快!