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

 


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

前言

好一个毒瘤数数题

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

思维过程

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

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

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

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

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

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

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

g(S)=T2E(T)×2E(S/T)×2E(TS/T)

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

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

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

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

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

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

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

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

g(S)=TG(T)×2E(TS/T)+E(S/T)

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

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

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

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

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

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

为啥:

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

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

k=1k(kk)×(1)k+1

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

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

g(S)=T2E(TS/T)+E(S/T)×k(1)k+1Gk(T)

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

g(S)=T2E(TS/T)+E(S/T)×(G1(T)G0(T))

然后我们就能得到 f,然后有了 f 之后,G0,G1 也可以交叉着互推

f(S)=2E(S)G(S)G0(S)=Tf(T)×G1(S/T)G1(S)=Tf(T)×G0(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\n",f[U]); 
    }
    void IsMyWife()
    {
        Input();
        Sakuya();
    }
}
#undef int //long long
int main()
{
    Flandre_Scarlet::IsMyWife();
    getchar();
    return 0;
}
posted @   Flandre-Zhu  阅读(147)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示