P10698 [SNCPC2024] 最大流
题意
给一个 \(n\) 个点 \(m\) 条边的 DAG,起点为 \(1\),终点不定,容量全为 \(1\)。再给定一个常数 \(k\)。设从 \(1\) 到 \(i\) 的最大流是 \(f_i\),对所有的 \(i\in[2,n]\) 求出 \(\min(f_i,k)\)。
\(n\le 10^5,m\le 2\times 10^5,k\le \min(50,n-1)\)。
思路
只有一个终点就可以直接跑网络流。
最大流就是问最多有多少路径满足起点在 \(S\),终点在 \(T\),没有交边。
将边转化成点,称之为假点,原来的点叫做真点。
问题变成给你一个起点的假点集合 \(A\),即真点起点的出边集合,和一个终点的假点集合 \(B\),即真点终点的入边集合。问最多的没有交点的路径条数,和 \(k\) 取 \(\min\)。
容易想到 LGV 引理。LGV 引理可以求所有划分不相交路径方式的贡献之和。一条路径的贡献定义为路径所有边的边权之积。
经典的套路,要知道最多有多少条不交的路径,就是随机赋边权,然后求 LGV 引理的矩阵 \(M\) 的秩。
具体地,\(M_{i,j}\) 定义为从 \(A_i\) 到 \(B_j\) 所有路径权值之和。
目前 \(|A|\) 和 \(|B|\) 都是 \(O(n)\) 的。
答案对 \(k\) 取 \(\min\)。
起点集合太大的情况,我们可以建 \(k\) 个虚点,分别连向起点集合所有的点。这样新增了 \(O(nk)\) 条边。
然后我们就只有 \(k\) 个起点了,\(M\) 是 \(n\times k\) 的。
考虑按着 DAG 的拓扑序扫描终点集合 \(B\)。处理到真点 \(u\) 时,新的终点集合 \(B'\),对于其中一个假点 \(B_i'\),我们已知与它相邻的拓扑序较小的那个店的矩阵 \(M\),有 \(M_i' = \sum M_j w_{i,j}\),其中 \(w\) 是我们随机赋的边权。
然后你惊奇地发现,因为 \(w\) 是随机的,所以对于线性相关的 \(M_x,M_y\),你只需要加其中一个就可以了,因为总是存在随机 \(w\) 的方式使得某种随机权值下加两个的效果等同于另一种随机权值下加一个的效果。
因此我们对 \(M\) 的行向量求线性基。然后线性基的大小是 \(k \times k\) 的。
因此对于每条真边,都要做一次 \(O(k^2)\) 的转移,对每个真点得到一个 \(O(nk)\) 的矩阵,还要对矩阵求线性基,是 \(O(nk^2)\) 的。
因此总时间复杂度 \(O((n+m)k^2)\)。给了 \(15s\),包过的吧。
code
#include<bits/stdc++.h>
#define sf scanf
#define pf printf
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
using namespace std;
typedef long long ll;
namespace proficient {constexpr int N=1e5+7,M=2e5+7,mod=1e9+7;mt19937 rd(random_device{}());int add(int a,int b) { return a+b>=mod ? a+b-mod : a+b; }void _add(int &a,int b) { a=add(a,b); }int mul(int a,int b) { return 1ll*a*b%mod; }void _mul(int &a,int b) { a=mul(a,b); }int ksm(int a,int b=mod-2) { int s=1;while(b) {if(b&1) _mul(s,a);_mul(a,a);b>>=1;}return s;}int n,m,k;int u,v;vector<int> fr[N],tp,to[N];int in[N];int ans[N];struct node {int x[50];int &operator [] (int pos) { return x[pos-1]; }void clear() { memset(x,0,sizeof(x)); }}a;node operator * (node x,const int y) {rep(i,1,k) _mul(x[i],y);return x;}node operator += (node &x,node y) {rep(i,1,k) _add(x[i],y[i]);return x;}node operator -= (node &x,node y) {rep(i,1,k) _add(x[i],mod-y[i]);return x;}struct xxj {node x[50];node &operator [] (int pos) { return x[pos-1]; }void insert(node y) {rep(i,1,k) {if(!y[i]) continue;if(x[i-1][i]) {int d=mul(y[i],ksm(x[i-1][i]));y-=x[i-1]*d;}else {x[i-1]=y;return;}}}int size() {int cnt=0;rep(i,1,k) if(x[i-1][i]) ++cnt;return cnt;}}b[N+50];void main() {sf("%d%d%d",&n,&m,&k);rep(i,1,m) {sf("%d%d",&u,&v);in[v]++;fr[v].push_back(u);to[u].push_back(v);}queue<int> q;rep(i,1,n) if(!in[i]) q.push(i);while(!q.empty()) {int u=q.front();tp.push_back(u);q.pop();for(int v : to[u]) if(!(--in[v])) q.push(v);}rep(i,1,k) {// e[i+m]={i+n,1};fr[1].push_back(i+n);a.clear();a[i]=1;b[i+n].insert(a);}for(int u : tp) {int it=0;for(int v : fr[u]) {++it;a.clear();rep(i,1,k) {int w=abs((int)rd())%mod;if(b[v][i][i]) a+=b[v][i]*w;}b[u].insert(a);}ans[u]=b[u].size();}rep(i,2,n) pf("%d ",ans[i]);}
}
int main() {#ifdef LOCALfreopen("in.txt","r",stdin);freopen("my.out","w",stdout);#endifproficient :: main();
}