ABC253
F - Operations on a Matrix(树状数组)
Problem
现在有一个大小的矩阵,一开始全部元素都是,现在进行次操作,每次操作有中类型
1 l r x
:把第到第列的所有元素加上2 i x
:把第行的元素全部替换成3 i j
:输出处的元素
,
Solve
把行和列分开维护。对于列来说,我们全局维护,操作 1 对列来说相当于区间同时加上一个数,这个可以用树状数组来维护。对于行来说,由于每进行一次操作 2, 之前列对当前行的影响都会消除,所以行用来维护答案的变化。具体就是:
- 对于操作 1 我们不考虑后面的情况,而是每次遇到操作 1 就区间全部加上一个数。
- 对于操作 2,我们遍历最近的以这行作为的询问(该询问前面的操作 2 是这次的操作,并且没有其他行操作),然后去统计列的影响。
- 对于操作 3,直接用该询问的列的全局的贡献+该询问的行的最新的一次变化的贡献-该询问的行之前列的贡献
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+10;
ll tr[N];
int n,m;
void add(int p,int x)
{
for(;p<=m+1;p+=p&-p) tr[p]+=x;
}
ll ask(int p)
{
ll res=0;
for(;p;p-=p&-p) res+=tr[p];
return res;
}
int t[N],l[N],r[N],x[N],lastest[N],pre[N];
ll cx[N];
vector<int>g[N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int q;
cin>>n>>m>>q;
for(int i=1;i<=q;i++)
{
cin>>t[i];
if(t[i]==1) cin>>l[i]>>r[i]>>x[i];
else if(t[i]==2) cin>>r[i]>>x[i],lastest[r[i]]=i;
else cin>>l[i]>>r[i],g[lastest[l[i]]].push_back(i),pre[i]=lastest[l[i]];
}
for(int i=1;i<=q;i++)
{
if(t[i]==1) add(l[i],x[i]),add(r[i]+1,-x[i]);
else if(t[i]==2)
{
for(auto j:g[i]) cx[j]=ask(r[j]);
}else cout<<ask(r[i])+x[pre[i]]-cx[i]<<'\n';
}
return 0;
}
ABC 213 G - Connectivity 2
Problem
给定一个简单无向图,该图有个顶点和条边。考虑移除一些边从图中(可以不移除),这样产生的新图有种。对于每个点,计算有多少个新图保证点和是连通的。答案对取模
,
Solve
黑科技(Fast Zeta Transform)
定义表示点集构成的的连通子图的数量,表示是为点集构成的的子图的数量,点集在原图中含有的边的数量。
容易得到,每条边可选可不选
由容斥原理容易得到。前面的容易理解是既包含了连通的子图也包含了不连通的子图,后面就是钦定某个点是属于连通块部分的,即要在中,然后根据乘法原理计算即可。
对于点来说,答案就是,其中是整个点集,点集要包含点和
实现的一些具体细节:
-
枚举子集的时候是枚举真子集,这里可以用类似
lowbit
的方法快速枚举子集 -
钦定的时候可以任意钦定,因为后面的实际上是会包含连通部分的,如果每次枚举哪个点,会算重,所以任意枚举一个,这里枚举中最低位的点作为。
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod= 998244353;
ll power(ll x ,int y)
{
ll res=1;
while(y)
{
if(y&1) res=res*x%mod;
x=x*x%mod;
y>>=1;
}
return res;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,m;
cin>>n>>m;
vector<pair<int,int>>e(m);
for(int i=0;i<m;i++)
{
int u,v;
cin>>u>>v;
u--,v--;
e[i]={u,v};
}
vector<int>cnt(1<<n);
vector<ll>f(1<<n),ans(n);
for(int s=0;s<(1<<n);s++)
{
for(auto [u,v]:e)
if(((s>>u)&1)&&((s>>v)&1)) cnt[s]++;
}
for(int s=0;s<(1<<n);s++)
{
f[s]=power(2,cnt[s]);
for(int t=s&(s-1);t;t=(t-1)&s)
{
if(!(t&(s&-s))) continue;
//if(!(t&(1<<(__lg(s))>>))) continue;
f[s]=(f[s]-f[t]*power(2,cnt[s-t])%mod+mod)%mod;
}
if(s&1)
{
for(int j=1;j<n;j++)
{
if((s>>j)&1) ans[j]=(ans[j]+f[s]*power(2,cnt[(1<<n)-s-1])%mod)%mod;
}
}
}
for(int i=1;i<n;i++) cout<<ans[i]<<"\n";
}
Ex- We Love Forest
Problem
有一个个点的图,一开始这个图里面没有边,但有无向边条边(编号从到)可以让你加入到图中。现在你可以做如下操作次,每次你可以随机地选择一条边,并把这边加入到图中,(加过的边还可以再加,所以可以出现重边)。现在问对于,在次操作之后是一个森林的概率。答案对取模
,
Solve
这个题和上面的有异曲同工之秒,所以先写了。
一些定义
- 点集可以构成的树的数量
- 点集可以构成的森林并且含有条边的数量
那么对于每一个,答案就是,其中是整个点集
对于,在已知点集的情况下,可以知道图的构成情况,问题就是求一个无向图的生成树的数量,这个可以用矩阵树定理(基尔霍夫定理)来求解。时间复杂度是。矩阵树定理。
对于的计算,和上面的差不多,即,之所以是,是因为这个点集是构成一棵树,一棵树有条边。
树的集合可以看做森林,所以这里枚举树
注意点
- 这里初始状态时,注意这里递推的时候要从小集合递推到大集合,不能直接枚举大集合的子集。
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353;
const int eps=1e-9;
ll power(ll x,int y)
{
ll res=1;
while(y)
{
if(y&1) res=res*x%mod;
x=x*x%mod;
y>>=1;
}
return res;
}
ll cal_det(vector<vector<ll>>a,int n)
{
ll det=1;
for(int i=0;i<n;i++)
{
int k=i;
for(int j=i+1;j<n;j++)
if(abs(a[j][i])>abs(a[k][i])) k=j;
if(a[k][i]==0){
det=0;
break;
}
swap(a[i],a[k]);
if(i!=k) det=mod-det;
det=det*a[i][i]%mod;
for(int j=i+1;j<n;j++) a[i][j]=a[i][j]*power(a[i][i],mod-2)%mod;
for(int j=0;j<n;j++)
if(j!=i&&a[j][i]!=0)
for(int k=i+1;k<n;k++) a[j][k]=(a[j][k]-a[i][k]*a[j][i])%mod;
}
if(det<0) det=det+mod;
return det;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,m;
cin>>n>>m;
vector<vector<int>>e(n,vector<int>(n));
for(int i=0;i<m;i++)
{
int u,v;
cin>>u>>v;
u--,v--;
e[u][v]++;
e[v][u]++;
}
vector<ll>f(1<<n);
for(int s=0;s<(1<<n);s++)
{
vector<int>p;
for(int j=0;j<n;j++)
if((s>>j)&1) p.push_back(j);
int len=p.size();
vector<vector<ll>>a(len,vector<ll>(len));
for(int i=0;i<len;i++)
for(int j=i+1;j<len;j++)
{
a[i][i]+=e[p[i]][p[j]];
a[j][j]+=e[p[i]][p[j]];
a[i][j]=(a[i][j]+mod-e[p[i]][p[j]])%mod;
a[j][i]=(a[j][i]+mod-e[p[i]][p[j]])%mod;
}
f[s]=cal_det(a,len-1);
}
vector<vector<ll>>g(n+1,vector<ll>(1<<n));
g[0][0]=1;
for(int i=0;i<n;i++)
{
for(int s=0;s<(1<<n);s++)
{
if(g[i][s]==0) continue;
int ns=((1<<n)-1)^s;
//ns是s的补集,枚举补集的子集,从而推出大集合。
for(int t=ns;t;t=(t-1)&ns)
{
int j=__builtin_popcount(t);//统计t中1的个数,计算|t|
if(!(t&(ns&-ns))) continue; //钦定一个v
g[i+j-1][s+t]=(g[i+j-1][s+t]+f[t]*g[i][s]%mod)%mod;
//小集合递推到大集合
}
}
}
vector<ll>fac(n+1);
fac[0]=1;
for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;
for(int i=1;i<n;i++)
{
ll res=fac[i]*g[i][(1<<n)-1]%mod*power(power(m,i),mod-2)%mod;
cout<<res<<'\n';
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通