[爱拼才会赢]命运

壹、关于题目 ¶

题目背景

自从「疯狂の校长」出现以后,狗狗们的生活就变得十分不如意,因为「疯狂の校长」总想抓狗狗做成 套套狗,不过,即使是这样,狗狗们也会尽己所能避免被套上 套子 的命运。不过,狗狗们也会有失手的时候,某一天,它们被困在一个有 \(n\) 个点,\(m\) 条边的迷宫中。更令狗恐怖的是,由目前已知的某些信息,「疯狂の校长」也在这个迷宫之内,并且祂正计划将整个迷宫用一个 套子 套住,这样,所有在迷宫中的狗狗就都会变成 套套狗\(\sf TTG\))。

狗狗们十分害怕,但是,有一只狗狗叫做 \(\sf DDG\),它有过变成 \(\sf TTG\) 的经历,并且它十分敏锐地指出了「疯狂の校长」的 套子 的弱点 —— 每个套子有一个参数 \(k\),让一个参数为 \(k\) 的套子失效,当且仅当这个迷宫能够被 \(k\) 染色。

显然,套子 的质量参差不齐,有许多套子都有大量能够使其失效的图。不过,由于 \(\sf DDG\) 的出逃,「疯狂の校长」十分细致地对这个漏掉进行了分析,并且祂也发现了 套子 的问题。由于你是一只 \(\sf TTG\),你被「疯狂の校长」指派求出一张给定图,能够让参数为 \(k\)套子 失效的染色方案数。

数据范围

对于 \(100\%\) 的数据范围,保证 \(n\le 10^5,m\le n+5\),给出的图连通,且无重边,无自环。

贰、关于题解 ¶

考虑对图进行收缩,模拟一个类似拓扑的过程,对于度数为 \(1\) 的点,可以直接将其删掉,并将最后答案乘上 \(k−1\),在该过程后,对于一条由一系列度数为 \(2\) 的点构成的点,显然他们的染色方案数都只与该链两个端点同异色有关,我们可以将这些度数为 \(2\) 的点也缩起来。如果缩到自环,我们也可以将其当做一个系数,就想前面的 \(k-1\) 一样。考察最后剩下的图,由于 \(m\le n+5\),所以剩下的点 \(s\le 10\),边数也只会有最多 \(15\) 条,我们只需要再进行一个状压 \(\rm DP\),定义状态 \(f(i,s)\) 表示当前共有 \(i\) 个块染上同样的颜色,染色点集为 \(s\),转移时枚举一个相同颜色的块即可,不过转移可能有重复,可以钦定选择连通块包含 \(\rm lowbit\) 来去重。

至于如何缩点,我们的边最开始有 \(<0,1>\),表示 < 相同,不同 >,如果要将一个点缩掉,我们只需要讨论一下它和左右哪个点颜色相同即可。

如果你有足够优秀的预处理,那么复杂度是 \(\mathcal O(s\times 3^s)\) 的。注意最后输出答案不要忘记 \(k-1\) 的系数。

叁、参考代码 ¶

# include <bits/stdc++.h>
using namespace std;
 
// # define USING_STDIN
# define NDEBUG
# include <cassert>
 
namespace Elaina {
 
# define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
# define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
# define fi first
# define se second
# define mp(a, b) make_pair(a, b)
# define Endl putchar('\n')
 
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair <int, int> pii;
typedef pair <ll, ll> pll;
 
template <class T> inline T fab(T x) { return x < 0? -x: x; }
template <class T> inline void getmin(T& x, const T rhs) { x=min(x, rhs); }
template <class T> inline void getmax(T& x, const T rhs) { x=max(x, rhs); }
 
# ifndef USING_STDIN
inline char freaGET() {
    # define BUFFERSIZE 5000
    static char BUF[BUFFERSIZE], *p1=BUF, *p2=BUF;
    return p1==p2 && (p2=(p1=BUF)+fread(BUF, 1, BUFFERSIZE, stdin), p1==p2)? EOF: *p1++;
    # undef BUFFERSIZE
}
#  define CHARGET freaGET()
# else
#  define CHARGET getchar()
# endif
template <class T> inline T readin(T x) {
    x=0; int f=0; char c;
    while((c=CHARGET)<'0' || '9'<c) if(c=='-') f=1;
    for(x=(c^48); '0'<=(c=CHARGET) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
    return f? -x: x;
}
 
template <class T> inline void writc(T x, char s='\n') {
    static int fwri_sta[55], fwri_ed=0;
    if(x<0) putchar('-'), x=-x;
    do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
    while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
    putchar(s);
}
 
} using namespace Elaina;
 
/**
 * @param MOD used for modulo
 * @param RT the primitive root of @p MOD
*/
template<int MOD, int RT> struct Mint {
    int val;
    static const int mod=MOD;
    Mint(ll v=0) { val=int(-mod<v && v<mod? v: v%mod); if(val<0) val+=mod; }
    inline friend bool operator ==(const Mint& a, const Mint& b) { return a.val == b.val; }
    inline friend bool operator !=(const Mint& a, const Mint& b) { return !(a==b); }
    inline friend bool operator <(const Mint& a, const Mint& b) { return a.val<b.val; }
    inline friend bool operator >(const Mint& a, const Mint& b) { return a.val>b.val; }
    inline friend bool operator <=(const Mint& a, const Mint& b) { return a.val<=b.val; }
    inline friend bool operator >=(const Mint& a, const Mint& b) { return a.val>=b.val; }
    inline Mint& operator +=(const Mint& rhs) { return (*this)=Mint((*this).val+rhs.val); }
    inline Mint& operator -=(const Mint& rhs) { return (*this)=Mint((*this).val-rhs.val); }
    inline Mint& operator *=(const Mint& rhs) { return (*this)=Mint(1ll*(*this).val*rhs.val); }
    inline Mint operator -() const { return Mint(-val); }
    inline Mint& operator ++() { return (*this)=(*this)+1; }
    inline Mint& operator --() { return (*this)=(*this)-1; }
    inline friend Mint operator +(Mint a, const Mint& b) { return a+=b; }
    inline friend Mint operator -(Mint a, const Mint& b) { return a-=b; }
    inline friend Mint operator *(Mint a, const Mint& b) { return a*=b; }
    inline friend Mint qkpow(Mint a, ll n) {
        assert(n>=0); Mint ret=1;
        for(; n; n>>=1, a*=a) if(n&1) ret*=a;
        return ret;
    }
    inline friend Mint inverse(Mint a) { assert(a!=0); return qkpow(a, mod-2); }
};
using mint=Mint<1000000007, 5>;
 
const int maxn=1e5;
const int maxm=maxn+5;
const int mod=1e9+7;
const int maxs=10;
 
struct Edge { int to, nxt, rev; mint sam, dif; } e[maxm*2+5];
int tail[maxn+5], in[maxn+5], ecnt;
inline void add_edge(int u, int v) {
    ++in[u], ++in[v];
    e[ecnt]=Edge{v, tail[u], ecnt^1, 0, 1}; tail[u]=ecnt++;
    e[ecnt]=Edge{u, tail[v], ecnt^1, 0, 1}; tail[v]=ecnt++;
}
int n, m; mint k;
 
inline void input() {
    n=readin(1), m=readin(1), k.val=readin(1);
    memset(tail+1, -1, n<<2);
    rep(i, 1, m) add_edge(readin(1), readin(1));
}
 
mint coe=1;
vector<int>S;
int id[maxn+5], z;
struct newEdge { int to; mint sam, dif; };
vector<newEdge>g[maxs+5];
inline void reBuild() {
    memset(id+1, -1, n<<2);
    queue<int>Q;
    rep(i, 1, n) if(in[i]==1) Q.push(i);
    while(!Q.empty()) {
        int u=Q.front(); Q.pop();
        coe*=(k-1), in[u]=0;
        for(int i=tail[u], v; ~i; i=e[i].nxt)
            if(in[v=e[i].to] && --in[v]==1) Q.push(v);
    }
    rep(u, 1, n) if(in[u]==2) {
        int x=-1, y, tox, toy, rev_tx, rev_ty;
        for(int i=tail[u]; ~i; i=e[i].nxt) if(in[e[i].to]) {
            if(!~x) x=e[i].to, tox=i;
            else { y=e[i].to, toy=i; break; }
        }
        in[u]=0;
        /** have shrunk a circle */
        if(x==u && y==u) { coe=coe*e[tox].sam; continue; }
        mint sx=e[tox].sam, dx=e[tox].dif;
        mint sy=e[toy].sam, dy=e[toy].dif;
        /** redirect the reverse edge */
        rev_tx=e[tox].rev, rev_ty=e[toy].rev;
        e[rev_tx].to=y, e[rev_ty].to=x;
        e[rev_tx].rev=rev_ty, e[rev_ty].rev=rev_tx;
        /** update @p sam & @p dif */
        e[rev_tx].sam=e[rev_ty].sam=sx*sy+(k-1)*dx*dy;
        e[rev_tx].dif=e[rev_ty].dif=sx*dy+sy*dx+(k-2)*dx*dy;
    }
    rep(i, 1, n) {
        assert(!in[i] || in[i]>2);
        if(in[i]) S.push_back(i), id[i]=z++;
    }
    rep(u, 1, n) if(in[u]) {
        for(int i=tail[u], v; ~i; i=e[i].nxt) if(~id[v=e[i].to]) {
            if((u^v) || (u==v && i<e[i].rev))
                g[id[u]].push_back({id[v], e[i].sam, e[i].dif});
        }
    }
}
 
mint same[1<<maxs], dif[maxs][1<<maxs];
mint dp[maxs+1][1<<maxs];
inline void doDP() {
    assert(z<=10); int U=1<<z;
    /** initial part */
    for(int b=0; b<U; ++b) {
        same[b]=1;
        for(int u=0; u<z; ++u) if(b>>u&1) {
            for(auto e: g[u]) if(u<=e.to && (b>>e.to&1))
                same[b]*=e.sam;
        }
    }
    for(int u=0; u<z; ++u) {
        for(int b=0; b<U; ++b) if((b>>u&1)^1) {
            dif[u][b]=1;
            for(auto e: g[u]) if(b>>e.to&1)
                dif[u][b]*=e.dif;
        }
    }
    /** DP part */
    dp[0][0]=1;
    for(int b=1; b<U; ++b) {
        int low=b&(-b);
        rep(i, 1, __builtin_popcount(b)) {
            for(int sub=b, __sub; sub; --sub&=b) if(sub&low) {
                __sub=b^sub; mint dif_coe=1;
                for(int u=0; u<z; ++u) if(sub>>u&1)
                    dif_coe*=dif[u][__sub];
                dp[i][b]+=dp[i-1][__sub]*same[sub]*dif_coe;
            }
        }
    }
    /** calculate the answer */
    mint ans=0;
    vector<mint>fac(maxn+5); fac[0]=1;
    rep(i, 1, maxn) fac[i]=fac[i-1]*i;
    rep(i, 1, z) ans+=fac[k.val]*inverse(fac[k.val-i])*dp[i][U-1];
    writc((ans*coe).val);
}
 
signed main() {
    input();
    reBuild();
    /** a big circle or a simple tree */
    if(!z) return writc((k*coe).val), 0;
    doDP();
    return 0;
}

肆、关键 の 地方 ¶

初始给出的模型可能有一些无用,或者说对答案影响不大的部分,我们可以将其去掉,以简化问题。

posted @ 2021-09-14 16:39  Arextre  阅读(35)  评论(0编辑  收藏  举报