ABC253

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

Problem

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

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

\(1\le N,M,Q\le 2\times 10^5\)\(1\le x\le 10^9\)

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\)中(可以不移除),这样产生的新图\(H\)\(2^M\)种。对于每个点\(k=2,3,...,N\),计算有多少个新图\(H\)保证点\(1\)\(k\)是连通的。答案对\(998244353\)取模

\(1\le N\le 17\)\(1\le M\le \frac{N(N-1)}{2}\)

Solve

黑科技(Fast Zeta Transform)

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

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

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

对于点\(k\)来说,答案就是\(res=\sum_{1,k\in S}f(S)g(V-S)\),其中\(V\)是整个点集,点集\(S\)要包含点\(1\)\(k\)

实现的一些具体细节:

  • 枚举子集\(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\)无向边条边(编号从\(1\)\(m\))可以让你加入到图中。现在你可以做如下操作\(N-1\)次,每次你可以随机地选择一条边,并把这边加入到图中,(加过的边还可以再加,所以可以出现重边)。现在问对于\(K=1,2,...,N-1\),在\(K\)次操作之后\(G\)是一个森林的概率。答案对\(998244353\)取模

\(1\le N\le 14\)\(1\le M\le 500\)

Solve

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

一些定义

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

那么对于每一个\(K\),答案就是\(\frac{g_k(V)k!}{m^k}\),其中\(V\)是整个点集

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

对于\(g_i(S)\)的计算,和上面的\(G\)差不多,即\(g_i(S)=\sum_{v\in T\ T\subseteq S}f(T)g_{i-|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 @ 2022-05-30 17:15  Arashimu  阅读(76)  评论(0编辑  收藏  举报