2021牛客暑期多校训练营 3C - Minimum grid
题意
给定一个\(n\times n\)的网格,你需要将其中的\(m\)个位置填入正整数\((\le k)\)
使得第\(i\)行整行的最大值为\(b[i]\),第\(j\)列整列的最大值为\(c[j]\),并且整个网格所有数字总和最小
求出最小总和
思路
每行最大值与每列最大值给出,换句话说这些数都需要在每行或每列出现一次
单独考虑行和列,对于某个位置\((i,j)\)且\(b[i]\neq c[j]\),则该位置对缩小答案无贡献,对应的行最大值\(b[i]\)与列最大值\(c[j]\)需要都出现一次
而对于位置\((i,j)\)且\(b[i]=c[j]\),发现如果在该位置填入了对应最大值,则行与列的条件都满足,不需要在其余位置再出现
而如果同行或者同列出现多个点满足\(b[i]=c[j_1]=c[j_2]=\cdots\)或\(c[j]=b[i_1]=b[i_2]=\cdots\),对应的每个点都应当放置一个数,所以一旦取了某个位置\((i,j)\)并且让答案减去一次最大值后,行\(i\)与列\(j\)则不能再被选择
发现这就是模板二分图最大匹配(由于与每个点连的边权值一定相同,故所有二分图匹配算法基本都能写),每个点只能被匹配一次
按照基本套路将行与列拆成两部分点,如果输入的可填数的点\((i,j)\)满足\(b[i]=c[j]\)就让行\(i\)与列\(j\)连边,如果行\(i\)被匹配就减去\(b[i]\);也可直接跑最大权完美匹配(KM不确定是否能过),权值直接放在连边上便于处理
或者像下方代码展示的一样直接跑最小费用最大流,限制每个点流量为\(1\),边权取反求最大费用
代码(MCMF)
//#include<ext/pb_ds/assoc_container.hpp>
//#include<ext/pb_ds/hash_policy.hpp>
#include<bits/stdc++.h>
#define closeSync ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define multiCase int T;cin>>T;for(int t=1;t<=T;t++)
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define perr(i,a,b) for(int i=(a);i>(b);i--)
#define all(a) (a).begin(),(a).end()
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
#define eb emplace_back
#define fi first
#define se second
using namespace std;
//using namespace __gnu_pbds;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
const double eps=1e-12;
const double PI=acos(-1.0);
const ll mod=998244353;
const int dx[8]={0,1,0,-1,1,1,-1,-1},dy[8]={1,0,-1,0,1,-1,1,-1};
void debug(){cerr<<'\n';}template<typename T,typename... Args>void debug(T x,Args... args){cerr<<"[ "<<x<< " ] , ";debug(args...);}
mt19937 mt19937random(std::chrono::system_clock::now().time_since_epoch().count());
ll getRandom(ll l,ll r){return uniform_int_distribution<ll>(l,r)(mt19937random);}
ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
ll qmul(ll a,ll b){ll r=0;while(b){if(b&1)r=(r+a)%mod;b>>=1;a=(a+a)%mod;}return r;}
ll qpow(ll a,ll n){ll r=1;while(n){if(n&1)r=(r*a)%mod;n>>=1;a=(a*a)%mod;}return r;}
ll qpow(ll a,ll n,ll p){ll r=1;while(n){if(n&1)r=(r*a)%p;n>>=1;a=(a*a)%p;}return r;}
ll inv(ll a){return qpow(a,mod-2);}
ll inv(ll a,ll p){return qpow(a,p-2,p);}
const int maxn=5050;
struct MCMF {
struct E {
int from, to, cap, v;
E() {}
E(int f, int t, int cap, int v) : from(f), to(t), cap(cap), v(v) {}
};
int n, m, s, t;
vector<E> edges;
vector<int> G[maxn];
bool inq[maxn];
int dis[maxn], pre[maxn], a[maxn];
void init(int _n, int _s, int _t) {
n = _n; s = _s; t = _t;
for (int i = 0; i <= n; i++)
G[i].clear();
edges.clear();
m = 0;
}
void add(int from, int to, int cap, int cost) {
edges.emplace_back(from, to, cap, cost);
edges.emplace_back(to, from, 0, -cost);
G[from].push_back(m++);
G[to].push_back(m++);
}
bool spfa() {
for (int i = 0; i <= n; i++) {
dis[i] = 1e9;
pre[i] = -1;
inq[i] = false;
}
dis[s] = 0, a[s] = 1e9, inq[s] = true;
queue<int> Q; Q.push(s);
while (!Q.empty()) {
int u = Q.front(); Q.pop();
inq[u] = false;
for (int& idx: G[u]) {
E& e = edges[idx];
if (e.cap && dis[e.to] > dis[u] + e.v) {
dis[e.to] = dis[u] + e.v;
pre[e.to] = idx;
a[e.to] = min(a[u], e.cap);
if (!inq[e.to]) {
inq[e.to] = true;
Q.push(e.to);
}
}
}
}
return pre[t] != -1;
}
int solve() {
int flow = 0, cost = 0;
while (spfa()) {
flow += a[t];
cost += a[t] * dis[t];
int u = t;
while (u != s) {
edges[pre[u]].cap -= a[t];
edges[pre[u] ^ 1].cap += a[t];
u = edges[pre[u]].from;
}
}
return cost;
}
}f;
ll b[2050],c[2050];
void solve()
{
int n,m,k;
cin>>n>>m>>k;
rep(i,1,n)
cin>>b[i];
rep(i,1,n)
cin>>c[i];
int s=2*n+1,t=2*n+2;
f.init(2*n+2,s,t);
rep(i,1,n)
{
f.add(s,i,1,0);
f.add(i+n,t,1,0);
}
rep(i,1,m)
{
int x,y;
cin>>x>>y;
if(b[x]==c[y])
f.add(x,y+n,1,-b[x]); //负权边求最大花费
}
ll ans=0;
rep(i,1,n)
ans+=b[i]+c[i];
ans+=f.solve(); //减去最大花费
cout<<ans<<'\n';
}
int main()
{
closeSync;
//multiCase
{
solve();
}
return 0;
}