LGP9545 [HBTS-Pre 2023] 环山危路 学习笔记
LGP9545 [HBTS-Pre 2023] 环山危路 学习笔记
题意简述
给定一个 \(n\) 个结点的竞赛图,每条边容量为 \(1\)。\(m\) 次询问,每次给定 \(k_i\) 个源点 \(s_{i,j}\),\(1\) 个汇点 \(t_i\)(保证所有 \(s_i\) 与 \(t_i\) 互不相同),求最大流。
\(n\le 3\times 10^3,m\le 3\times 10^4\)。
竞赛图的定义:满足任意两点间恰有一条有向边的简单图。
做法解析
直接跑最大流肯定不行,会爆。由于最大流最小割定理,我们求最大流不行就试试考虑最小割,因为这图是张竞赛图,我们可以期待其上的割有一些性质……?
确实有性质:对于两个点集 \(S,T\),我们有 \(f(S,T)+f(T,S)=|S|\times |T|\),因为 \(S,T\) 的点两两有边相连。
另外,我们还有 \(f(S,T)-f(T,S)=\sum_{u\in S} oud_u-ind_u\)。这是因为从 \(S\) 连到 \(T\) 的边会做出 \(1\) 的贡献,从 \(T\) 连到 \(S\) 的边会做出 \(-1\) 的贡献,\(S\) 内部的边做出 \(0\) 的贡献,化一下就可以得到上式。
所以我们就有 \(f(S,T)=\frac{1}{2}(|S|\times |T|+\sum_{u\in S}oud_u-ind_u)\)。
这样,对于每个 \(|S|\),我们最小化 \(\sum_{u\in S} oud_u-ind_u\) 并更新答案,就可以得出答案。
实现上就是先算一遍初始 \(S\) 的答案,然后每次贪心地往 \(S\) 里面加 \(oud_u-ind_u\) 最小的点尝试更新答案。
代码实现
真是精彩的签到!
#include <bits/stdc++.h>
using namespace std;
namespace obasic{
template <typename _T>
void readi(_T &x){
_T k=1;x=0;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')k=-1;
for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-'0';
x*=k;return;
}
template <typename _T>
void writi(_T x){
if(x<0)putchar('-'),x=-x;
if(x>9)writi(x/10);
putchar(x%10+'0');
}
template <typename _T>
void minner(_T &x,_T y){x=min(x,y);}
};
using namespace obasic;
const int MaxN=3e3+5;
int N,M,T,K,X;char S[MaxN];
int deg[MaxN],vis[MaxN];
struct anob{int v,id;}P[MaxN];
bool cmpv(anob a,anob b){return a.v<b.v;}
int main(){
readi(N),readi(M);
for(int i=1;i<=N;i++){
scanf("%s",S+1);
for(int j=1;j<=N;j++)if(S[j]=='1')deg[i]++,deg[j]--;
}
for(int i=1;i<=N;i++)P[i]={deg[i],i};
sort(P+1,P+N+1,cmpv);queue<int> q;
for(int csiz,csum;M--;){
readi(T),readi(K);csiz=K,csum=0;
for(int i=1;i<=K;i++)readi(X),vis[X]=1,q.push(X),csum+=deg[X];
int ans=(csiz*(N-csiz)+csum)/2;
for(int i=1;i<=N;i++){
auto [cv,cid]=P[i];
if(cid==T||vis[cid])continue;
csiz++,csum+=cv;minner(ans,(csiz*(N-csiz)+csum)/2);
}
writi(ans),puts("");
while(!q.empty())vis[q.front()]=0,q.pop();
}
return 0;
}
反思总结
想求 \(x\) 时,如果实在别的方法都不行,试试求 \(x+y\) 和 \(x-y\)。
神奇吧,HBTS-Pre。
浙公网安备 33010602011771号