专栏文章
题解:P11834 [省选联考 2025] 岁月
P11834题解参与者 5已保存评论 4
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 4 条
- 当前快照
- 1 份
- 快照标识符
- @minvbe1o
- 此快照首次捕获于
- 2025/12/02 08:56 3 个月前
- 此快照最后确认于
- 2025/12/02 08:56 3 个月前
C 性质
不妨将概率计数转为方案计数,最后再乘上 。
所有边权相等,条件可以转化为存在一棵外向生成树,考虑所有可以成为根的节点,可以发现其组成一个 SCC,更具体的,其恰好是缩点后唯一入度为 的 SCC。
因此我们设 表示集合 恰好是缩点后唯一入度为 的 SCC 方案数,答案即为 。
不合法的方案数等价于缩点后有多个点,并且形成了一个 DAG,因此可以仿照 DAG 容斥的方法,每次删去入度为 的 SCC。
设 表示将 划分为若干个入度为 的 SCC 方案数,且包含容斥系数,容易推导出容斥系数为 , 为 SCC 的个数,转移时要钦定 。
转移时枚举 ,钦定 中形成若干个入度为 的 SCC。对于剩下的边, 可以向 连边, 中可以任意连边。
其中 表示 内部无向边的数量, 表示两端分别在 , 的无向边数量。 容易 求出,。
最后考虑容斥计算 ,记全集为 。我们枚举 ,钦定 中存在若干入度为 的 SCC,发现容斥系数恰好为 即为 。
对于剩下的边, 可以向 连边, 中可以任意连边。
至此我们在 时间复杂度内解决了 C 性质。
正解
考虑 Kruskal 的过程,按边权从小到大合并连通块。显然在加入 的边后,对于每个在原无向图中的连通块,其在有向图中也一定存在一个根集可以到达这个连通块的所有点。
因此我们有这样一种做法,从小到大扫描边权 ,维护目前每个点集 表示 恰好为当前所在连通块根集的方案数 。加入所有边权为 的边时,合并所有连通块,合并根集更新 。
考虑如何合并根集合,我们称一条边是关键边,当且仅当其终点属于根集。那么把连通块看作点,只保留关键边,合并的根集就是新图上最小外向生成树的根集。
因此新问题类似于 C 性质,我们对于每次新合并的连通块跑一遍 C 性质即可,额外的,由于要从 推出 ,每个连通块内的根集已经为其原本所在连通块的根集,要考虑这部分对转移的影响。
一些定义:
- 表示集合 所处原本连通块集合的并。
- 表示合并连通块后集合 所处连通块集合。
- 表示 为 根集的方案数。
- 表示将 在新图上划分为若干个 入度 SCC 方案数,且包含容斥系数。
- 表示 为 根集的方案数。
- 表示原本连通块的 。
考虑 的转移,注意 与 之间可以连非关键边。
表示 在新图上不能有交集, 表示在根集为 , 情况下 与 之间的非关键边。
考虑 的转移,由于需要计算 的总方案数,设 表示保证 分别为原本每个连通块根集情况下任意连边的总方案数,可以递推计算,设 ,。
转移时注意 可以向 连非关键边。
考虑 的转移,记全集 ,为了方便表述,设 , 之间可以任意连边,设其方案数为 ,可以递推计算。
对于每个原本的连通块,。
对于若干个原本连通块的并,设 ,。
转移时注意 与 之间可以连非关键边, 可以向 连非关键边。
最终答案即为
注意除了关键边和非关键边,还有在连通块内部的边,这种边不会影响连通性,可以直接给答案 。
由于我们只在新合并连通块时枚举子集,因此时间复杂度不超过 ,总时间复杂度 。
代码
CPP#include <bits/stdc++.h>
#define int long long
#define lowbit(x) (x&(-x))
using namespace std;
const int N=215,M=(1<<15)+5,mo=1e9+7;
struct edge{
int u,v;
};
int c,T,n,m,U,ans;
int p[N],ed[N],fa[N],fa2[N],msk[N];
int sum[M],f[M],g[M],dp[M],ent[M],ext[M],cof[M],con[M];
vector<edge>e[N];
int read(){
int k=0;
char ch=getchar();
while(ch<'0'||ch>'9')
ch=getchar();
while(ch>='0'&&ch<='9')
k=k*10+ch-'0',ch=getchar();
return k;
}
int qpow(int x,int y){
int res=1;
while(y){
if(y&1)
res=res*x%mo;
x=x*x%mo;
y=y>>1;
}
return res;
}
int find(int x){
return fa[x]==x?x:fa[x]=find(fa[x]);
}
int find2(int x){
return fa2[x]==x?x:fa2[x]=find2(fa2[x]);
}
int ppc(int x){
return __builtin_popcountll(x);
}
int ctz(int x){
return __builtin_ctzll(x);
}
int qsum(int x,int y){
return sum[x^y]-sum[x]-sum[y];
}
int nsum(int x,int y){
return (qsum(x,ext[y]^y)+qsum(ext[x]^x,y)+qsum(ext[x]^x,ext[y]^y)*2);
}
int ok(int x,int y){
return (ext[x]&ext[y])==0;
}
bool check(int x){
return (ent[1<<ctz(x)]&x)!=x||ext[1<<ctz(x)]==ent[x];
}
void work(){
for(int s=1;s<(1<<n);s++){
if(check(s)) continue;
int t=(ext[1<<ctz(s)]&s);
cof[s]=cof[s^t]*dp[t]%mo*p[qsum(ext[s^t],ext[t])*2]%mo;
if(ext[s]==s){
t=ext[1<<ctz(s)];
if(s==t){
con[s]=0;
for(int t=s;t;t=((t-1)&s))
con[s]=(con[s]+cof[t])%mo;
}
else
con[s]=con[s^t]*con[t]%mo*p[qsum(s^t,t)*2]%mo;
}
}
for(int s=1;s<(1<<n);s++){
if(check(s)) continue;
g[s]=0,f[s]=cof[s];
for(int t=((s-1)&s);t;t=((t-1)&s))
if(ok(t,s^t)&&lowbit(t)==lowbit(s))
g[s]=(g[s]-f[t]*g[s^t]%mo*p[nsum(t,s^t)])%mo;
for(int t=s;t;t=((t-1)&s))
if(ok(t,s^t))
f[s]=(f[s]-g[t]*cof[s^t]%mo*p[qsum(ext[t]^t,ext[s^t])+qsum(ext[t],ext[s^t])])%mo;
g[s]=(g[s]+f[s])%mo;
}
for(int s=1;s<(1<<n);s++){
if(check(s)) continue;
U=ent[s],dp[s]=0;
for(int t=s^U;;t=((t-1)&(s^U))){
if(ok(s,t)&&ok(s,U^ext[s^t]))
dp[s]=(dp[s]-f[s]*g[t]%mo*con[U^ext[s^t]]%mo*p[nsum(s,t)+qsum(ext[s^t]^s^t,U^ext[s^t])+qsum(ext[s^t],U^ext[s^t])])%mo;
if(!t) break;
}
}
}
void solve(){
int u,v,w;
n=read(),m=read();
for(int i=1;i<=m;i++){
u=read(),v=read(),w=read();
e[w].push_back((edge){u-1,v-1});
}
dp[0]=1,g[0]=-1,cof[0]=1,con[0]=1;
for(int s=1;s<(1<<n);s++)
ent[s]=s,dp[s]=0,cof[s]=0,con[s]=0;
for(int i=0;i<n;i++){
fa[i]=i,fa2[i]=i;
msk[i]=(1<<i),dp[1<<i]=1,cof[1<<i]=1,con[1<<i]=1;
}
int res=1;
for(int l=1;l<=m;l++){
int stt=0;
for(edge i:e[l]){
u=find(i.u),v=find(i.v);
if(u==v)
res=res*4%mo;
else{
stt=1;
ed[i.u]|=(1<<i.v),ed[i.v]|=(1<<i.u);
u=find2(i.u),v=find2(i.v);
if(u!=v){
fa2[v]=u;
msk[u]|=msk[v];
}
}
}
if(!stt) continue;
for(int s=1;s<(1<<n);s++){
int t=ctz(s);
sum[s]=sum[s^(1<<t)]+ppc(ed[t]&s);
}
for(int s=1;s<(1<<n);s++){
ext[s]=ent[s],ent[s]=0;
for(int i=0;i<n;i++)
if(s&(1<<i))
ent[s]|=(msk[find2(i)]);
}
work();
for(int i=0;i<n;i++)
fa[i]=fa2[i],ed[i]=0;
}
ans=0;
for(int s=1;s<(1<<n);s++)
ans=(ans+res*dp[s])%mo;
ans=ans*qpow(p[2*m],mo-2)%mo;
ans=(ans+mo)%mo;
printf("%lld\n",ans);
for(int i=1;i<=m;i++)
e[i].clear();
}
signed main(){
c=read(),T=read();
p[0]=1;
for(int i=1;i<=210;i++)
p[i]=p[i-1]*2%mo;
while(T--)
solve();
return 0;
}
相关推荐
评论
共 4 条评论,欢迎与作者交流。
正在加载评论...