[爱拼才会赢]命运

壹、关于题目 ¶

题目背景

自从「疯狂の校长」出现以后,狗狗们的生活就变得十分不如意,因为「疯狂の校长」总想抓狗狗做成 套套狗,不过,即使是这样,狗狗们也会尽己所能避免被套上 套子 的命运。不过,狗狗们也会有失手的时候,某一天,它们被困在一个有 \(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  阅读(73)  评论(0)    收藏  举报