P9041 [PA2021] Fiolki 2
P9041 [PA2021] Fiolki 2
题意
给一个 个点 条边的 DAG 和一个常数 。
定义 表示最多选择不相交路径条数,满足起点 ,终点
。对所有的 ,求出有多少 使得 。
。
思路
即起点集合定了,问几个区间作为终点集合,使得不相交路径条数为 。
考虑一个弱化问题,对于一个大小为 的起点集合和同样大小的终点集合,我们需要判断出不相交路径条数是否为 。
不相交路径使我们想到 LGV 引理。但是 LGV 引理求的是对于这样的起点和终点集合,每一组不相交路径的贡献乘上一个 的若干次方的和。我们要的是对于这样的起点和终点集合,是否存在一组不相交路径。
插播:由于起点只有 个,因此方阵大小是 的,求方阵需要求 表示从 到 的所有路径权值之和(一条路径的权值定义为路径所有边权之积)。这个可以 做。
如果不存在,算的行列式一定为 ,反之不一定。
于是我们可以给每个边随机赋边权,乘法对大质数取模。根据那个 Schwartz–Zippel引理,我不会的,反正根据直觉这样大概率就是对的。
行列式是否为零,等同于方阵是否满秩。
对于终点集合比起点集合大的情况怎么办呢?
还是设起点集和大小为 ,要判定是否存在 条不相交路径。
朴素做法是枚举终点集合中 个点出来。
想到比内柯西公式。不过好像没关系嘻嘻。
相当于问终点集合是否存在 个点,使得方阵行列式非零。
虽然矩阵没有行列式,但是我们可以直接求矩阵是否满秩。解决了。预处理矩阵 。
回到原问题:起点集合大小为 ,终点集合大小任意。问最多的不相交路径数。
就是问最多选择多大的方阵使得行列式非零,相当于问矩阵的秩是多少。
求矩阵的秩,就是求矩阵所有行向量的线性基是多大。
我们枚举终点集合(复杂度 )的话,把终点集合作为矩阵的行,动态维护线性基比较方便。每次插入或者删除一个终点就是插入或者删除一个长度为 的向量。
维护时间戳线性基,就可以支持插入和删除了。具体地,扫描终点集合的右端点,每个基维护一个时间,插入新基的时候,尽量用时间晚的基替代早的基。查询线性基大小的时候只算时间符合要求的基。
线性基一次插入是 的。
枚举终点集合太慢辣!
固定终点集合的右端点 ,显然 是关于 单调递减的。
因此可以对线性基里面所有基的时间排序,这样一共有 个时间节点,把 分成 段,每段的 都是一样的。
时间复杂度 。瓶颈在于插入 次线性基和预处理矩阵。
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(); }
本文来自博客园,作者:wing_heart,转载请注明原文链接:https://www.cnblogs.com/wingheart/p/18650638
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?