zoukankan      html  css  js  c++  java
  • uoj37 【清华集训2014】主旋律 做题心得

    uoj37 【清华集训2014】主旋律 做题心得

    前言

    好一个毒瘤数数题

    以前集训课上,听老师讲过;但是当时完全没听懂,所以今天来补一补

    思维过程

    算是我做这个题的完整心路历程吧,前面的错误想法可以选择性的看,对正解影响不大

    这个 (n) 非常小,只有 (15);但是这个 (m) 却非常大,最大200余。看来我们的算法需要和 (n) 有关,而且可以猜到是指数的,毕竟这个问题一看就很np

    那我们考虑点。发现直接考虑,所有点都在一大块SCC里面,能考虑个寂寞。于是,容易想到反面考虑:算多少种边集使得图 强联通

    不强联通,那可以考虑缩点,然后就会变成一堆SCC块,它们组成DAG。

    发现DAG有一个性质,就是我们可以把点集划一刀,分成 (A,B) 两块,使得只存在 (A)(B) 的边。

    然后我们设 (f(S)) 表示 (S) 点集强联通的方案,而 (g(S)) 是不强联通。那么 (f(S)+g(S)=2^{E(S)}),其中 (E(S)) 表示 (S) 内部边数。也就是说,这俩算一个就行

    看来是 (g) 比较好算。根据上面的 “划一刀” 理论,机智的想到:我们枚举它一个子集 (T),然后

    (g(S)=sumlimits_{T} 2^{E(T)} imes 2^{E(S/T)} imes 2^{E(T ightarrow S/T)})

    (S/T) 表示 (T)(S) 中的补集,因为我不知道正规写法怎么用 (LaTeX) 打,就暂且这样写

    (E(A,B)) 表示从 (A) 集合到 (B) 集合的边数,即 (# (u,v)|uin A,vin B)

    一看怎么这么简单,肯定不对。拿脑子一想,肯定得重,因为我们这个“切一刀”的方法不唯一,一个缩点的方案会被算很多遍

    能不能去重呢?考虑序列的去重,我们任意划一刀肯定会重,但如果每次只切掉第一个位置,就不会重

    再来考虑这个问题,同样是找一个 唯一 的 “开始” 位置。容易想到,就是那些入度为 (0) 的点。

    那我们如果要计算 (g) ,可以枚举一个集合 (T),强制它作为"开始"点。然后搞一搞 (T ightarrow S/T) 的边就行了

    (G(S)) 表示,把点集 (S) 分成若干部分,使得每个部分是一个SCC,且SCC间两两没有边,方案数。

    然后我们枚举 (T) 作为“开始”,内部划分方案数就是 (G(T))。然后 (T ightarrow S/T)(S/T) 内部,这些边都任选。得到式子:

    [g(S)=sumlimits_{T} G(T) imes 2^{E(T ightarrow S/T)+E(S/T)} ]

    这回脑子转的久一点,但是又发现重了:因为我们没有保证 (S/T) 里面没有“开始”点!

    我自己想就到这,想了好久发现不知道咋整,就去听老师讲回放了

    我一看,嗷,原来我的容斥已经完全不熟练了,尽管看出来要容斥,也不知道咋搞系数了

    假设最终缩成的DAG上有 (k) 个“开始”点,那么它的 (2^k-1) 个非空子集都会被我们枚举,并且把它算一次。也就是说,它一共会被算 (2^k-1) 次。

    关于子集的去重,大家应该都很熟悉。假设 (T) 里面分了 (k) 块 SCC,乘一个 ((-1)^{k+1}) 作为容斥系数就行了。

    形象的说,就是:分一块方案数,-分两块,+分三块,-分四块...

    为啥:

    假设最后是 (k) 块,考虑它被算了几次:

    对于 (T) 里面分了 (k') 块的时候,会把这个东西算 (inom{k}{k'}) 次。那一共被算:

    (sumlimits_{k'=1}^{k} inom{k}{k'} imes (-1)^{k'+1})

    我们发现这玩意就是二项式定理的形式,瞎几把搞一搞,最后发现它就是 (1)

    那现在似乎式子对了,设 (G_k(T)) 表示把 (T) 分成 (k) 块 SCC,SCC间没有边的方案数。则:

    [g(S)=sumlimits_{T} 2^{E(T ightarrow S/T)+E(S/T)} imes sumlimits_{k} (-1)^{k+1}G_k(T) ]

    我们发现我们并不需要知道具体的 (G) 值,只需要分奇偶就行了。那我们给它换个定义:(G_{0/1}(T)) 表示把 (T) 分成偶数/奇数块SCC,SCC间没有边的方案数。则:

    [g(S)=sumlimits_{T} 2^{E(T ightarrow S/T)+E(S/T)} imes (G_1(T)-G_0(T)) ]

    然后我们就能得到 (f),然后有了 (f) 之后,(G_0,G_1) 也可以交叉着互推

    [f(S)=2^{E(S)}-G(S)\G_0(S)=sumlimits_{T} f(T) imes G_1(S/T)\G_1(S)=sumlimits_{T} f(T) imes G_0(S/T) ]

    然后注意一些边界问题,就ok了。

    代码实现啥的并不是本题的重点,直接参考代码即可

    总结

    • 容斥的套路:奇数+,偶数-,解决子集算重的情况

    • 脑子里先搞一个大概想法,然后再往这个想法上面凑

      比如这个题,就可以先看出来是个容斥,然后想想咋容斥

    代码

    #include <bits/stdc++.h>
    using namespace std;
    namespace Flandre_Scarlet
    {
        #define N   15
        #define int long long
        #define mod 1000000007
        #define i2  500000004
        #define F(i,l,r) for(int i=l;i<=r;++i)
        #define D(i,r,l) for(int i=r;i>=l;--i)
        #define Fs(i,l,r,c) for(int i=l;i<=r;c)
        #define Ds(i,r,l,c) for(int i=r;i>=l;c)
        #define MEM(x,a) memset(x,a,sizeof(x))
        #define FK(x) MEM(x,0)
        #define Tra(i,u) for(int i=G.st(u),v=G.to(i);~i;i=G.nx(i),v=G.to(i))
        #define p_b push_back
        #define sz(a) ((int)a.size())
        #define all(a) a.begin(),a.end()
        #define iter(a,p) (a.begin()+p)
        int I() {char c=getchar(); int x=0; int f=1; while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar(); while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return ((f==1)?x:-x);}
        template <typename T> void Rd(T& arg){arg=I();}
        template <typename T,typename...Types> void Rd(T& arg,Types&...args){arg=I(); Rd(args...);}
        void RA(int *p,int n) {F(i,1,n) *p=I(),++p;}
        int n,m,U;
        bool e[N][N];
        bitset<300> so[1<<N],si[1<<N]; // 搞两个bitset, 快速支持E(A,B)的查询
        void Input()
        {
            n=I(),m=I(); U=(1<<n)-1;
            F(i,1,m)
            {
                int u=I()-1,v=I()-1;
                F(s,1,U)
                {
                    if ((s>>u)&1) so[s][i]=1;
                    else so[s][i]=0;
    
                    if ((s>>v)&1) si[s][i]=1;
                    else si[s][i]=0;
                }
            }
        }
        int lg[1<<N],pw2[300];
        void init()
        {
            pw2[0]=1;
            F(i,1,250) pw2[i]=pw2[i-1]*2%mod;
            F(i,0,n-1) lg[1<<i]=i;
            F(i,1,U) if (!lg[i]) lg[i]=lg[i-1];
        }
        int E(int S,int T)
        {
            int ans=(so[S]&si[T]).count();
            return ans;
        }
        int f[1<<N],g[1<<N],G[2][1<<N];
        void Sakuya()
        {
            init();
            g[0]=0;
            G[0][0]=1; G[1][0]=0;
            F(s,1,U)
            {
                for(int t=(s-1)&s;t;t=(t-1)&s)
                {
                    g[s]+=pw2[E(t,s^t)+E(s^t,s^t)]*(G[1][t]-G[0][t]%mod+mod)%mod;
                    g[s]%=mod;
                    if (t&(s&(-s)))
                    {
                        G[0][s]+=f[t]*G[1][s^t]%mod;
                        G[1][s]+=f[t]*G[0][s^t]%mod;
                        G[0][s]%=mod; G[1][s]%=mod;
                    }
                }
                g[s]=(g[s]+G[1][s]+mod-G[0][s]%mod)%mod; 
                f[s]=(pw2[E(s,s)]-g[s]%mod+mod)%mod;
                G[1][s]=(G[1][s]+f[s])%mod;
            }
            printf("%lld
    ",f[U]); 
        }
        void IsMyWife()
        {
            Input();
            Sakuya();
        }
    }
    #undef int //long long
    int main()
    {
        Flandre_Scarlet::IsMyWife();
        getchar();
        return 0;
    }
    
  • 相关阅读:
    git(1)-git关联GitHub-windows-转载
    jenkins(4)-jenkins配置邮件通知
    jenkins(3)-linux下安装jenkins(yum install方式)
    【PAT甲级】1090 Highest Price in Supply Chain (25 分)(DFS)
    【PAT甲级】1087 All Roads Lead to Rome (30 分)(MAP【int,string】,邻接表,DFS,模拟,SPFA)
    【PAT甲级】1018 Public Bike Management (30 分)(DFS,SPFA)
    Educational Codeforces Round 61 (Rated for Div. 2) G(线段树,单调栈)
    Atcoder Grand Contest 032C(欧拉回路,DFS判环)
    Educational Codeforces Round 62 (Rated for Div. 2)E(染色DP,构造,思维,组合数学)
    Atcoder Grand Contest 031C(构造,思维,异或,DFS)
  • 原文地址:https://www.cnblogs.com/LightningUZ/p/15085344.html
Copyright © 2011-2022 走看看