P9041 [PA2021] Fiolki 2

P9041 [PA2021] Fiolki 2

题意

给一个 nn 个点 mm 条边的 DAG 和一个常数 kk

定义 f(l,r)f(l,r) 表示最多选择不相交路径条数,满足起点 s[1,k]s\in[1,k],终点
t[l,r]t\in[l,r]。对所有的 x[0,k]x\in[0,k],求出有多少 [l,r](k,n][l,r] \subseteq (k,n] 使得 f(l,r)=xf(l,r)=x

n105,m106,kmin(50,n1)n\le 10^5,m\le 10^6,k\le \min(50,n-1)

思路

即起点集合定了,问几个区间作为终点集合,使得不相交路径条数为 xx


考虑一个弱化问题,对于一个大小为 xx 的起点集合和同样大小的终点集合,我们需要判断出不相交路径条数是否为 xx

不相交路径使我们想到 LGV 引理。但是 LGV 引理求的是对于这样的起点和终点集合,每一组不相交路径的贡献乘上一个 1-1 的若干次方的和。我们要的是对于这样的起点和终点集合,是否存在一组不相交路径。

插播:由于起点只有 5050 个,因此方阵大小是 50×5050 \times 50 的,求方阵需要求 e(si,tj)e(s_i,t_j) 表示从 sis_itjt_j 的所有路径权值之和(一条路径的权值定义为路径所有边权之积)。这个可以 O(m)O(m) 做。

如果不存在,算的行列式一定为 00,反之不一定。

于是我们可以给每个边随机赋边权,乘法对大质数取模。根据那个 Schwartz–Zippel引理,我不会的,反正根据直觉这样大概率就是对的。


行列式是否为零,等同于方阵是否满秩。

对于终点集合比起点集合大的情况怎么办呢?

还是设起点集和大小为 xx,要判定是否存在 xx 条不相交路径。

朴素做法是枚举终点集合中 xx 个点出来。

想到比内柯西公式。不过好像没关系嘻嘻。

相当于问终点集合是否存在 xx 个点,使得方阵行列式非零。

虽然矩阵没有行列式,但是我们可以直接求矩阵是否满秩。解决了。预处理矩阵 O(nk+mk)O(nk+mk)


回到原问题:起点集合大小为 kk,终点集合大小任意。问最多的不相交路径数。

就是问最多选择多大的方阵使得行列式非零,相当于问矩阵的秩是多少。

求矩阵的秩,就是求矩阵所有行向量的线性基是多大。

我们枚举终点集合(复杂度 O(n2)O(n^2))的话,把终点集合作为矩阵的行,动态维护线性基比较方便。每次插入或者删除一个终点就是插入或者删除一个长度为 kk 的向量。

维护时间戳线性基,就可以支持插入和删除了。具体地,扫描终点集合的右端点,每个基维护一个时间,插入新基的时候,尽量用时间晚的基替代早的基。查询线性基大小的时候只算时间符合要求的基。

线性基一次插入是 O(k2)O(k^2) 的。


枚举终点集合太慢辣!

固定终点集合的右端点 rr,显然 f(l,r)f(l,r) 是关于 ll 单调递减的。

因此可以对线性基里面所有基的时间排序,这样一共有 O(k)O(k) 个时间节点,把 ll 分成 O(k)O(k) 段,每段的 f(l,r)f(l,r) 都是一样的。

时间复杂度 O(nnk2+mk)O(nnk^2+mk)。瓶颈在于插入 nn 次线性基和预处理矩阵。

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();
}
posted @   wing_heart  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
点击右上角即可分享
微信分享提示