ABC253

F - Operations on a Matrix(树状数组)

Problem

现在有一个N×M大小的矩阵,一开始全部元素都是0,现在进行Q次操作,每次操作有3中类型

  • 1 l r x:把第l到第r列的所有元素加上x
  • 2 i x:把第i行的元素全部替换成x
  • 3 i j:输出(i,j)处的元素

1N,M,Q2×1051x109

Solve

把行和列分开维护。对于列来说,我们全局维护,操作 1 对列来说相当于区间同时加上一个数,这个可以用树状数组来维护。对于行来说,由于每进行一次操作 2, 之前列对当前行的影响都会消除,所以行用来维护答案的变化。具体就是:

  • 对于操作 1 我们不考虑后面的情况,而是每次遇到操作 1 就区间全部加上一个数。
  • 对于操作 2,我们遍历最近的以这行作为i的询问(该询问前面的操作 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

给定一个简单无向图G,该图有N个顶点和M条边。考虑移除一些边从图G中(可以不移除),这样产生的新图H2M种。对于每个点k=2,3,...,N,计算有多少个新图H保证点1k是连通的。答案对998244353取模

1N171MN(N1)2

Solve

黑科技(Fast Zeta Transform)

定义f(S)表示点集S构成的G的连通子图的数量,g(S)表示是为点集S构成的G的子图的数量,cnt(S)点集S在原图G中含有的边的数量。

容易得到g(S)=2cnt(S),每条边可选可不选

由容斥原理容易得到f(S)=g(S)vT TSf(T)g(S/T)。前面的g(S)容易理解是既包含了连通的子图也包含了不连通的子图,后面就是钦定某个点v是属于连通块部分的,即v要在T中,然后根据乘法原理计算即可。

对于点k来说,答案就是res=1,kSf(S)g(VS),其中V是整个点集,点集S要包含点1k

实现的一些具体细节:

  • 枚举子集T的时候是枚举真子集,这里可以用类似lowbit的方法快速枚举子集

  • 钦定v的时候可以任意钦定,因为后面的g(S/T)实际上是会包含连通部分的,如果每次枚举哪个点v,会算重,所以任意枚举一个,这里枚举S中最低位的点作为v

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

有一个N个点的图G,一开始这个图里面没有边,但有M无向边条边(编号从1m)可以让你加入到图中。现在你可以做如下操作N1次,每次你可以随机地选择一条边,并把这边加入到图中,(加过的边还可以再加,所以可以出现重边)。现在问对于K=1,2,...,N1,在K次操作之后G是一个森林的概率。答案对998244353取模

1N141M500

Solve

这个题和上面的G有异曲同工之秒,所以先写了。

一些定义

  • f(S):=点集S可以构成的的数量
  • gi(S):=点集S可以构成的森林并且含有i条边的数量

那么对于每一个K,答案就是gk(V)k!mk,其中V是整个点集

对于f(S),在已知点集的情况下,可以知道图的构成情况,问题就是求一个无向图的生成树的数量,这个可以用矩阵树定理(基尔霍夫定理)来求解。时间复杂度是O(2nn3)矩阵树定理

对于gi(S)的计算,和上面的G差不多,即gi(S)=vT TSf(T)gi|T|+1(S/T),之所以是i|T|+1,是因为T这个点集是构成一棵树,一棵树有|T|1条边。

树的集合可以看做森林,所以这里枚举树

注意点

  • 这里初始状态时g[0][0]=1,注意这里递推的时候要从小集合递推到大集合,不能直接枚举大集合的子集。

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';
    }
}
posted @   Arashimu  阅读(79)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示