出题人:Cai大佬
题目大意:一张$n$个点$m$条边的无向图,有$Q$个询问,每次给出点集$S$和起点$x$,求从起点出发向四周等概率游走,期望走几步走完点集中的点.
数据范围:$n \leq 18,Q \leq 100000$,时限3秒

分层高消

以下大写字母表示一个点集,小写字母表示一个点
所谓分层高消,就是每进行一次高斯消元,就利用当前高斯消元的结果,构造新的方程矩阵进行高斯消元,以达到优化时间复杂度的目的。
$f(x,S)$表示从$x$出发,已经走过了点集$S$,走遍全图的期望。
由于每次走一个新的点,已经走过的点集只可能扩大不可能缩小,所以我们可以利用这个性质进行分层。每次高斯消元是用于求出$f(x,S)$的。
对于一个在$S$集中的点$x$(由于$x$是起点,所以可以看作走过了),设$du(x)$为$x$的度数,列出方程
$ f(x,S) = \frac{1}{du(x)} (f(y,S) \times [ y \in S ]+ f(y,S \cup {j } ) \times [ y \not\in S ] )$$
做高消即可,复杂度$O(n^3 2^n+Q)$

#include<bits/stdc++.h>
using namespace std;
int read() {
    int q=0;char ch=' ';
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
    return q;
}
int n,m,Q;const int mod=998244353;
int e[19][19],du[19],bin[19],a[19][19],f[19][(1<<18)],b1[19],b2[19];
int ksm(int x,int y) {
    int re=1;
    for(;y;y>>=1,x=1LL*x*x%mod) if(y&1) re=1LL*re*x%mod;
    return re;
}
void gauss(int tot) {
    for(int k=1;k<=tot;++k) {
        int id=k;while(!a[id][k]&&id<tot) ++id;
        if(id!=k) swap(a[id],a[k]);
        int tmp=ksm(a[k][k],mod-2);
        for(int j=k;j<=tot+1;++j) a[k][j]=1LL*a[k][j]*tmp%mod;
        for(int i=1;i<=tot;++i) {
            if(i==k) continue;
            tmp=a[i][k];
            for(int j=k;j<=tot+1;++j)
                a[i][j]=(a[i][j]+mod-1LL*a[k][j]*tmp%mod)%mod;
        }
    }
}
void init() {
    for(int no=bin[n]-2;no>=0;--no) {
        int tot=0;
        for(int i=0;i<n;++i) if(no&bin[i]) b1[i]=++tot,b2[b1[i]]=i;
        for(int i=1;i<=tot;++i)
            for(int j=1;j<=tot+1;++j) a[i][j]=0;
        for(int i=0;i<n;++i) if(no&bin[i]){
            a[b1[i]][b1[i]]=1,a[b1[i]][tot+1]=1;int ni=ksm(du[i],mod-2);
            for(int j=0;j<n;++j) {
                if(!e[i][j]) continue;
                if(no&bin[j]) a[b1[i]][b1[j]]=mod-ni;
                else a[b1[i]][tot+1]=(a[b1[i]][tot+1]+1LL*f[j][no|bin[j]]*ni%mod)%mod;
            }
        }
        gauss(tot);
        for(int i=1;i<=tot;++i) f[b2[i]][no]=a[i][tot+1];
    }
}
int main()
{
    freopen("gemo.in","r",stdin);
    freopen("gemo.out","w",stdout);
    int x,y,num;
    n=read(),m=read();
    for(int i=1;i<=m;++i)
        x=read()-1,y=read()-1,e[x][y]=e[y][x]=1,++du[x],++du[y];
    bin[0]=1;for(int i=1;i<=n;++i) bin[i]=bin[i-1]<<1;
    init(),Q=read();
    while(Q--) {
        num=read();int S=0;
        for(int i=1;i<=num;++i) x=read()-1,S|=bin[x];
        x=read()-1,printf("%d\n",f[x][((bin[n]-1)^S)|bin[x]]);
    }
    return 0;
}

max-min容斥

max-min容斥就是下面这个式子:
$$max(S)=\sum (-1)^{|S’|-1} min(S’) (S’ \subset S)$$
然后呢,我们就把若干个点里,期望最晚到其中的某个点这个问题转化为若干个点里,期望最早到其中的某个点了。这样的话枚举点集做高斯消元,复杂度是$O(2^n n^3)$
然后可以把大小为偶数的子集和都取相反数,利用一点黑科技很快求出子集和:(每次求出当前集合去掉小于j的若干元素后的子集的和)

for(int j=0;j<n;++j)//必须先枚举j,以免重复计算一个集合
    for(int zt=0;zt<=bin[n]-1;++zt)
    if(zt&bin[j]) f[i][zt]=(f[i][zt]+f[i][zt^bin[j]])%mod;

总复杂度就是$O(2^n n^3 +2^n n+Q)$的了。

#include<bits/stdc++.h>
using namespace std;
int read() {
    int q=0;char ch=' ';
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
    return q;
}
int n,m,Q;const int mod=998244353;
int e[19][19],du[19],bin[19],a[19][19],f[19][(1<<18)],b1[19],b2[19];
int ksm(int x,int y) {
    int re=1;
    for(;y;y>>=1,x=1LL*x*x%mod) if(y&1) re=1LL*re*x%mod;
    return re;
}
void gauss(int tot) {
    for(int k=1;k<=tot;++k) {
        int id=k;while(!a[id][k]&&id<tot) ++id;
        if(id!=k) swap(a[id],a[k]);
        int tmp=ksm(a[k][k],mod-2);
        for(int j=k;j<=tot+1;++j) a[k][j]=1LL*a[k][j]*tmp%mod;
        for(int i=1;i<=tot;++i) {
            if(i==k) continue;
            tmp=a[i][k];
            for(int j=k;j<=tot+1;++j)
                a[i][j]=(a[i][j]+mod-1LL*a[k][j]*tmp%mod)%mod;
        }
    }
}
void init() {
    for(int zt=0;zt<bin[n]-1;++zt) {//最早走到zt外的点的期望
        int tot=0;
        for(int i=0;i<n;++i) if(zt&bin[i]) ++tot,b1[i]=tot,b2[tot]=i;
        for(int i=1;i<=tot;++i)
            for(int j=1;j<=tot;++j) a[i][j]=0;
        for(int i=0;i<n;++i) {
            if(!(zt&bin[i])) continue;
            a[b1[i]][b1[i]]=1,a[b1[i]][tot+1]=1;int ni=ksm(du[i],mod-2);
            for(int j=0;j<n;++j)
                if(e[i][j]&&(zt&bin[j])) a[b1[i]][b1[j]]=mod-ni;
        }
        gauss(tot);
        for(int i=1;i<=tot;++i) f[b2[i]][(bin[n]-1)^zt]=a[i][tot+1];
    }
    for(int zt=0;zt<bin[n]-1;++zt) {
        int js=0;
        for(int i=0;i<n;++i) if(zt&bin[i]) ++js;
        if(!(js&1)) for(int i=0;i<n;++i) f[i][zt]=mod-f[i][zt];//取相反数
    }
    for(int i=0;i<n;++i)
        for(int j=0;j<n;++j)
        for(int zt=0;zt<=bin[n]-1;++zt)
        if(zt&bin[j]) f[i][zt]=(f[i][zt]+f[i][zt^bin[j]])%mod;
}
int main()
{
    freopen("gemo.in","r",stdin);
    freopen("gemo.out","w",stdout);
    int x,y,num;
    n=read(),m=read();
    for(int i=1;i<=m;++i)
        x=read()-1,y=read()-1,e[x][y]=e[y][x]=1,++du[x],++du[y];
    bin[0]=1;for(int i=1;i<=n;++i) bin[i]=bin[i-1]<<1;
    init(),Q=read();
    while(Q--) {
        num=read();int kl=0;
        for(int i=1;i<=num;++i) kl|=bin[read()-1];
        printf("%d\n",f[read()-1][kl]);
    }
    return 0;
}

分享至ヾ(≧∇≦*)ゝ:
分类: 所有

litble

苟...苟活者在淡红的血色中,会依稀看见微茫的希望

1 条评论

juruo-oier · 2018年3月1日 3:36 下午

%%%%

发表评论

电子邮件地址不会被公开。 必填项已用*标注

你是机器人吗? =。= *