[爱拼才会赢]命运
壹、关于题目 ¶
题目背景
自从「疯狂の校长」出现以后,狗狗们的生活就变得十分不如意,因为「疯狂の校长」总想抓狗狗做成 套套狗,不过,即使是这样,狗狗们也会尽己所能避免被套上 套子 的命运。不过,狗狗们也会有失手的时候,某一天,它们被困在一个有 \(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;
}
肆、关键 の 地方 ¶
初始给出的模型可能有一些无用,或者说对答案影响不大的部分,我们可以将其去掉,以简化问题。