P9041 [PA2021] Fiolki 2
P9041 [PA2021] Fiolki 2
题意
给一个 \(n\) 个点 \(m\) 条边的 DAG 和一个常数 \(k\)。
定义 \(f(l,r)\) 表示最多选择不相交路径条数,满足起点 \(s\in[1,k]\),终点
\(t\in[l,r]\)。对所有的 \(x\in[0,k]\),求出有多少 \([l,r] \subseteq (k,n]\) 使得 \(f(l,r)=x\)。
\(n\le 10^5,m\le 10^6,k\le \min(50,n-1)\)。
思路
即起点集合定了,问几个区间作为终点集合,使得不相交路径条数为 \(x\)。
考虑一个弱化问题,对于一个大小为 \(x\) 的起点集合和同样大小的终点集合,我们需要判断出不相交路径条数是否为 \(x\)。
不相交路径使我们想到 LGV 引理。但是 LGV 引理求的是对于这样的起点和终点集合,每一组不相交路径的贡献乘上一个 \(-1\) 的若干次方的和。我们要的是对于这样的起点和终点集合,是否存在一组不相交路径。
插播:由于起点只有 \(50\) 个,因此方阵大小是 \(50 \times 50\) 的,求方阵需要求 \(e(s_i,t_j)\) 表示从 \(s_i\) 到 \(t_j\) 的所有路径权值之和(一条路径的权值定义为路径所有边权之积)。这个可以 \(O(m)\) 做。
如果不存在,算的行列式一定为 \(0\),反之不一定。
于是我们可以给每个边随机赋边权,乘法对大质数取模。根据那个 Schwartz–Zippel引理,我不会的,反正根据直觉这样大概率就是对的。
行列式是否为零,等同于方阵是否满秩。
对于终点集合比起点集合大的情况怎么办呢?
还是设起点集和大小为 \(x\),要判定是否存在 \(x\) 条不相交路径。
朴素做法是枚举终点集合中 \(x\) 个点出来。
想到比内柯西公式。不过好像没关系嘻嘻。
相当于问终点集合是否存在 \(x\) 个点,使得方阵行列式非零。
虽然矩阵没有行列式,但是我们可以直接求矩阵是否满秩。解决了。预处理矩阵 \(O(nk+mk)\)。
回到原问题:起点集合大小为 \(k\),终点集合大小任意。问最多的不相交路径数。
就是问最多选择多大的方阵使得行列式非零,相当于问矩阵的秩是多少。
求矩阵的秩,就是求矩阵所有行向量的线性基是多大。
我们枚举终点集合(复杂度 \(O(n^2)\))的话,把终点集合作为矩阵的行,动态维护线性基比较方便。每次插入或者删除一个终点就是插入或者删除一个长度为 \(k\) 的向量。
维护时间戳线性基,就可以支持插入和删除了。具体地,扫描终点集合的右端点,每个基维护一个时间,插入新基的时候,尽量用时间晚的基替代早的基。查询线性基大小的时候只算时间符合要求的基。
线性基一次插入是 \(O(k^2)\) 的。
枚举终点集合太慢辣!
固定终点集合的右端点 \(r\),显然 \(f(l,r)\) 是关于 \(l\) 单调递减的。
因此可以对线性基里面所有基的时间排序,这样一共有 \(O(k)\) 个时间节点,把 \(l\) 分成 \(O(k)\) 段,每段的 \(f(l,r)\) 都是一样的。
时间复杂度 \(O(nnk^2+mk)\)。瓶颈在于插入 \(n\) 次线性基和预处理矩阵。
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 pragmatic {
constexpr int N=1e5+7,K=55,M=1e6+7,mod=1e9+7;
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 n,m,k;
int u,v;
vector<int> to[N];
int e[N][K];
mt19937 rd(random_device{}());
int in[N];
void init() {
queue<int> q;
rep(i,1,n) if(!in[i]) q.push(i);
rep(i,1,k) e[i][i]=1;
while(!q.empty()) {
int u=q.front();
q.pop();
for(int v : to[u]) {
int val=add(mod,rd()%mod);
rep(i,1,k) _add(e[v][i],mul(e[u][i],val));
if(!--in[v]) q.push(v);
}
}
}
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;
}
ll ans[N];
int p[K][K];
int tim[K];
void insert(int *x,int t) {
rep(i,1,k) if(x[i]) {
if(!p[i][i]) {
memcpy(p[i]+i,x+i,sizeof(int)*(k-i+1));
tim[i]=t;
return;
}
if(t>tim[i]) {
swap(tim[i],t);
rep(j,i,k) swap(p[i][j],x[j]);
}
int tmp=mul(x[i],ksm(p[i][i]));
rep(j,i,k) _add(x[j],mod-mul(tmp,p[i][j]));
}
}
void main() {
sf("%d%d%d",&n,&m,&k);
rep(i,1,m) sf("%d%d",&u,&v), to[u].push_back(v), in[v]++;
init();
rep(r,k+1,n) {
insert(e[r],r);
vector<int> vec;
rep(j,1,k) if(tim[j]) vec.push_back(tim[j]);
int la=k;
sort(vec.begin(),vec.end());
int cnt=vec.size();
for(int j : vec) ans[cnt--]+=j-la,la=j;
ans[cnt]+=r-la;
}
rep(i,0,k) pf("%lld\n",ans[i]);
}
}
int main() {
#ifdef LOCAL
freopen("in.txt","r",stdin);
freopen("my.out","w",stdout);
#endif
pragmatic :: main();
}
本文来自博客园,作者:liyixin,转载请注明原文链接:https://www.cnblogs.com/liyixin0514/p/18650638