[海军国际项目办公室]抑郁刀法

抑郁刀法

题面

在这里插入图片描述
在这里插入图片描述

题解

当我们看到条件 m ⩽ n + 5 m\leqslant n+5 mn+5大概就可以猜到这题的做法与这多出来的几条边有关,否则大概就只能用 2 n 2^n 2n的状压了。
首先我们可以考虑对于一棵树,我们可以怎么染色。
显然,树上的染色要求只有每个节点不能与自己的父亲同色,所以一棵大小为 n n n的树有 ( k − 1 ) n − 1 k (k-1)^{n-1}k (k1)n1k种染色方法。
但如果我们的树上再多出来几条边就很难办了,我们还得要求这几条边连接的点颜色不同。
实际上我们做特殊要求的节点也就这几个在边上的节点,我们不妨专门对这些节点枚举颜色,然后再对整棵树进行 d p dp dp
其中 d p u , i dp_{u,i} dpu,i表示节点 u u u的颜色为 i i i u u u的整棵子树的染色方案数,我们只要让被钦定颜色的节点只有那个颜色的 d p dp dp有值即可。
但事实上不属于特殊节点的颜色的颜色相互之间是等价的,我们可以将所有与特殊节点的颜色不一样的节点颜色用 0 0 0来表示,转程中在 0 0 0上乘以 k − c n t k-cnt kcnt即可, c n t cnt cnt表示特殊节点的颜色数。
而特殊节点颜色之间的顺序也是等价的,我们可以将特殊节点的颜色用最小表示法表示,之后再乘上 c n t ! cnt! cnt!来乱序即可。
时间复杂度 O ( ( 2 m − 2 n ) ! n ( m − n ) ) O\left((2m-2n)!n(m-n)\right) O((2m2n)!n(mn)),可以拿到 40 p t s 40pts 40pts,实测 50 p t s 50pts 50pts

但我们并不需要将这么多节点都设为特殊点,对于一条边,我们仅仅设一个特殊点即可,另外一个点与这个点颜色不同就行了,我们将对应的 d p dp dp赋为 0 0 0
这样,我们需要枚举的点就只剩一半了。
时间复杂度 O ( ( m − n ) ! n ( m − n ) ) O\left((m-n)!n(m-n)\right) O((mn)!n(mn)),实测 75 p t s 75pts 75pts
但这样时间复杂度还是太大了,我们得继续优化。

容易发现我们实际上用到的点并没有这么多,很多子树内不存在特殊边的节点都是没用的,我们可以将这些节点全部删去,很明显,它们的贡献都是会在答案上乘上 ( k − 1 ) (k-1) (k1),它们只有不与其父亲相同的限制。
删除这些节点的方法其实是类似于通过拓扑排序一直删去度数为 1 1 1的节点的,因为度数为 1 1 1的节点贡献时独立的。
如果只删度数为 1 1 1的节点的话实测是 90 p t s 90pts 90pts,还需要继续优化。
我们不妨可以把度数为 2 2 2的节点也都删了,很明显,这些节点都是一些成链状的,连结一些特殊点。
实际上如果我们把特殊点都保留下来,其余的节点度数都不会超过 2 2 2,它们只能在特殊点之间不断连结。
我们不妨把那些特殊点都保留下来,对于长条的链状转移单独处理。
链状的转移只与两个点颜色是否相同有关,相当于在原有的转移前连一个系数,我们记 g i , 0 / 1 g_{i,0/1} gi,0/1表示长度为 i i i的链两端是否相同时我们的系数,明显有递推式,
g i , 0 = ( k − 1 ) g i − 1 , 1 , g i , 1 = ( k − 2 ) g i − 1 , 1 + g i − 1 , 0 g_{i,0}=(k-1)g_{i-1,1},g_{i,1}=(k-2)g_{i-1,1}+g_{i-1,0} gi,0=(k1)gi1,1,gi,1=(k2)gi1,1+gi1,0
我们将原来的链缩成一条带权边后再跑树形 d p dp dp即可。
这是树形 d p dp dp的部分的复杂度就从原来的 O ( n ( m − n ) ) O\left(n(m-n)\right) O(n(mn))变到 O ( ( m − n ) 2 ) O\left((m-n)^2\right) O((mn)2)了。
保留点时最好将根节点也保留下来,方便转移。

时间复杂度 O ( ( m − n ) ! ( m − n ) 2 ) O\left((m-n)!(m-n)^2\right) O((mn)!(mn)2)
跑得还蛮快的,常数吊打 R a i n y B u n n y \color{black}{R}\color{red}{ainyBunny} RainyBunny

源码

#include<bits/stdc++.h>
using namespace std;
#define MAXN 100005
#define lowbit(x) (x&-x)
#define reg register
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
typedef long long LL;
typedef unsigned long long uLL;       
const LL INF=0x3f3f3f3f3f3f3f3f;       
const int mo=1e9+7;
const int inv2=499122177;
const int jzm=2333;
const int zero=10000;
const int orG=3,invG=332748118;
const double Pi=acos(-1.0);
const double eps=1e-5;
typedef pair<int,int> pii;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
template<typename _T>
void print(_T x){if(x<0){x=(~x)+1;putchar('-');}if(x>9)print(x/10);putchar(x%10+'0');}
LL gcd(LL a,LL b){return !b?a:gcd(b,a%b);}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1LL)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1LL;}return t;}
int n,m,K,d[MAXN],dp[MAXN][15],fa[MAXN],totd,tots,tot,head[MAXN];
int fac[MAXN],inv[MAXN],f[MAXN],col[MAXN],ans,totp;
int dep[MAXN],Tot,Head[MAXN],deg[MAXN],cnt,totpd,g[MAXN][2];
bool spc[MAXN],del[MAXN];
queue<int>q;
struct ming{int u,v,w;}s[MAXN],p[MAXN],pd[MAXN];
struct edge{int to,nxt,paid;}e[MAXN<<1],E[MAXN];
void addedge(int u,int v){E[++Tot]=(edge){v,Head[u]};Head[u]=Tot;}
void addEdge(int u,int v,int w){e[++tot]=(edge){v,head[u],w};head[u]=tot;}
void makeSet(int x){for(int i=1;i<=x;i++)fa[i]=i;}
int findSet(int x){return fa[x]==x?x:fa[x]=findSet(fa[x]);}
void unionSet(int a,int b){int u=findSet(a),v=findSet(b);if(u!=v)fa[u]=v;}
void init(){
	fac[0]=fac[1]=inv[0]=inv[1]=f[1]=1;
	for(int i=2;i<=K;i++){
		fac[i]=1ll*i*fac[i-1]%mo;
		f[i]=1ll*(mo-mo/i)*f[mo%i]%mo;
		inv[i]=1ll*f[i]*inv[i-1]%mo;
	}
}
int C(int x,int y){
	if(x<0||y<0||x<y)return 0;
	return 1ll*fac[x]*inv[y]%mo*inv[x-y]%mo;
}
void dosaka(int u,int fa){
	if(!col[u])for(int i=0;i<=cnt;i++)dp[u][i]=1;
	else for(int i=0;i<=cnt;i++)dp[u][i]=(col[u]==i);
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to,w=e[i].paid,tmp=0;if(v==fa)continue;
		dosaka(v,u);for(int j=1;j<=cnt;j++)tmp=add(tmp,1ll*g[w][1]*dp[v][j]%mo,mo);
		for(int j=0;j<=cnt;j++){
			int summ=tmp;
			if(j)summ=add(summ,1ll*(mo-g[w][1]+g[w][0])*dp[v][j]%mo,mo),
				summ=add(summ,1ll*(K-cnt)*g[w][1]%mo*dp[v][0]%mo,mo);
			else summ=add(summ,1ll*(1ll*(K-cnt-1)*g[w][1]%mo+g[w][0])*dp[v][0]%mo,mo);
			dp[u][j]=1ll*summ*dp[u][j]%mo;
		}
	}
	for(int i=Head[u];i;i=E[i].nxt)if(col[E[i].to])dp[u][col[E[i].to]]=0;
}
void sakura(int ip,int ct){
	if(ip==totd+1){
		for(int i=1;i<=tots;i++)
			if(col[s[i].u]==col[s[i].v])return ;
		cnt=ct;dosaka(1,0);int res=0;
		for(int i=1;i<=cnt;i++)res=add(res,dp[1][i],mo);
		res=add(res,1ll*(K-cnt)*dp[1][0]%mo,mo);
		ans=add(ans,1ll*C(K,cnt)*fac[cnt]%mo*res%mo,mo);
		return ;
	}
	for(int i=1;i<=ct;i++)col[d[ip]]=i,sakura(ip+1,ct);
	col[d[ip]]=ct+1;sakura(ip+1,ct+1);col[d[ip]]=0;
}
void dosaka1(int u,int fa,int las){
	dep[u]=dep[fa]+1;bool saved=0;
	if(las!=u&&(Head[u]||deg[u]>2||spc[u]))
		pd[++totpd]=(ming){las,u,dep[u]-dep[las]-1},saved=1;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;if(v==fa)continue;
		dosaka1(v,u,saved?u:las);
	}
}
signed main(){
	read(n);read(m);read(K);makeSet(n);init();
	for(int i=1;i<=m;i++){
		int u,v;read(u);read(v);deg[u]++;deg[v]++;
		if(findSet(u)==findSet(v)){s[++tots]=(ming){u,v};continue;}
		unionSet(u,v);p[++totp]=(ming){u,v};addEdge(u,v,0);addEdge(v,u,0);
	}
	for(int i=2;i<=n;i++)if(deg[i]==1)q.push(i),del[i]=1;
	while(!q.empty()){
		int t=q.front();q.pop();
		for(int i=head[t];i;i=e[i].nxt){
			deg[e[i].to]--;
			if(deg[e[i].to]==1&&e[i].to!=1)
				q.push(e[i].to),del[e[i].to]=1;
		}
	}
	for(int i=1;i<=n;i++)head[i]=0;tot=0;
	for(int i=1;i<=totp;i++)
		if(!del[p[i].u]&&!del[p[i].v])
			addEdge(p[i].u,p[i].v,0),addEdge(p[i].v,p[i].u,0);
	for(int i=1;i<=tots;i++){
		if(spc[s[i].u]||spc[s[i].v])continue;
		spc[s[i].u]=1,d[++totd]=s[i].u;
	}
	for(int i=1;i<=tots;i++)if(!spc[s[i].u]||!spc[s[i].v])
		addedge(s[i].u,s[i].v),addedge(s[i].v,s[i].u);
	sort(d+1,d+totd+1);dosaka1(1,0,1);
	for(int i=1;i<=n;i++)head[i]=0;tot=0;
	for(int i=1;i<=totpd;i++)
		addEdge(pd[i].u,pd[i].v,pd[i].w),
		addEdge(pd[i].v,pd[i].u,pd[i].w);
	g[0][0]=0;g[0][1]=1;
	for(int i=1;i<=n;i++)g[i][0]=1ll*(K-1)*g[i-1][1]%mo,
		g[i][1]=add(g[i-1][0],1ll*(K-2)*g[i-1][1]%mo,mo);
	sakura(1,0);for(int i=2;i<=n;i++)if(del[i])ans=1ll*(K-1)*ans%mo;
	printf("%d\n",ans);
	return 0;
}

谢谢!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值