[2018.6.26集训]墨水大师-圆方树-快速幂-数列通项公式推导

题目大意

有一棵$n$个节点$m$条边的仙人掌,定义一种合法的染色方案为,满足每条边的两端的节点颜色均不相同的方案,询问$q$次,每次给出一个可以使用的颜色数量$c$,求出合法方案数。

$n,qleq 100000,mleq 2*n-2$
时限$2s$。

题解

首先,考虑树的情况。
列一个朴素dp,设$f_i$为$i$节点确定为某一种颜色后,子树的合法方案数。
转移:$f_u=prod_{v in ch[u] }f_v*(c-1)$

然后就会发现这™就是个快速幂......
式子为: $ans=c*(c-1)^{n-1}$

然后考虑环的情况。
依旧先考虑朴素$d$
发现树边可以同上处理,同时对于每个环,新开一个dp。
首先,将大小为$size$的环断开成线段,其中一端为环的根。
然后,设$f[i][0/1]$表示考虑到环上顺/逆时针第$i$个点,且这个点与环根的颜色不同/相同的方案数。
于是有转移:
$f[i][0]=f[i][1]$
$f[i][1]=(c-1)f[i][0]+(c-2)f[i][1]$
然后,取$f[size][1]*prod_{v in ring}f_v$作为这个环环根的dp值即可~

咱们依旧想化成快速幂。
于是考虑建一棵圆方树,此时方点代表一个环。
可以发现,方点处的$f_i$只需要满足如下式子:
$$f_i=(prod_{v in ch[u]}f_v*(c-1))*frac{f[size][1]}{(c-1)^{size}}$$
这样就可以融入正常的 $f$ 值计算中了~

于是可以发现,每个环相当于一个额外系数,形如$frac{f[size][1]}{(c-1)^{size}}$的系数~
那么 $ans=c*(c-1)^{n-1}*prod_{ring in G}frac{f[size][1]}{(c-1)^{size}}$

可以发现,$f[size][1]$可以矩阵快速幂,$(c-1){size}$可以快速幂,即单个大小的环的贡献可以$O(23*log n)$算出。

然后,由于不同的环大小最多只有$O(sqrt{n})$种,爆算每种大小的环的贡献即可。
复杂度$O(2^3qsqrt{n}log n)$。

然而,博主使用上面的方法交上去的程序只得了$90pts$,最后一个点要$13s$。
因为,矩阵快速幂太™慢了。

于是优化矩阵快速幂,推一推式子。下面用$f_i$代替$f[i][1]$。
归纳矩阵的转移式,有$f_i=(c-2)f_{i-1}+(c-1)f_{i-2}$。
考虑化成$f_i+lambda f_{i-1}=a(f_{i-1}+lambda f_{i-2})$的形式:

$$ f_i+lambda f_{i-1} = (c-2+lambda)(f_{i-1}+lambda f_{i-2}) $$
$$ f_i = (c-2)f_{i-1}+lambda(c-2+lambda) f_{i-2} $$
$$ lambda(c-2+lambda) = c-1 $$
$$ lambda^2+(c-2)lambda-(c-1) = 0 $$
$$ lambda = 1 或 1-c $$

显然$lambda=1$更好算。
于是有
$$ f_i+ f_{i-1} = (c-1)(f_{i-1}+f_{i-2}) $$
令$s_i=f_i+f_{i-1}$,那么有$s_i=(c-1)s_{i-1}$。
由于$s_2=f_2+f_1=(c-1)+0=c-1$,那么$s_i=(c-1)^{n-1}$。

于是又有
$$f_i=s_i-f_{i-1}=-f_{i-1}+(c-1)^{n-1}$$

考虑化成$f_i+a{n+k}=b(f_{i-1}+a{n+k-1})$的等比数列形式。

那么有
$$ f_i = -f_{i-1}+(c-1)^{i-1} $$
$$ f_i + T = -(f_{i-1}+frac{T}{c-1}) $$
$$ f_i = -f_{i-1}-frac{T}{c-1}-T $$
$$ T + frac{T}{c-1} = -(c-1)^{i-1} $$
$$ cT = -(c-1)^i $$
$$ T = -frac{(c-1)^i}{c} $$

于是$f_i - frac{(c-1)^i}{c} = -(f_{i-1}-frac{(c-1)^{i-1}}{c})$。
又$f_1-frac{c-1}{c} = 0 - frac{c-1}{c} = - frac{c-1}{c}$,于是有:

$$ f_i - frac{(c-1)^i}{c} = -(f_{i-1}-frac{(c-1)^{i-1}}{c}) $$
$$ f_i - frac{(c-1)^i}{c} = (-1)^ifrac{c-1}{c} $$
$$ f_i = (-1)^ifrac{c-1}{c} + frac{(c-1)^i}{c} $$

于是便不用矩阵了~
于是就可以过了~

代码:

#include<map>
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long ll;
typedef pair<int,int> pr;
const int N=2e5+9;
const int M=2e5+9;
const ll md=998244353;

inline int read()
{
	int x=0;char ch=getchar();
	while(ch<'0' || '9'<ch)ch=getchar();
	while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
	return x;
}

int co[N];
ll invc,inv;
int n,m,q,c,ans;
int to[M<<1],nxt[M<<1],beg[N],tot=1;

inline void add(int u,int v)
{
	to[++tot]=v;
	nxt[tot]=beg[u];
	beg[u]=tot;
}

inline ll qpow(ll a,ll b)
{
	ll ret=1;
	while(b)
	{
		if(b&1)ret=ret*a%md;
		a=a*a%md;b>>=1;
	}
	return ret;
}

namespace abc
{
	vector<int> g[N];
	vector<pr> vec;
	map<int,int> ha;
	int pre[N],fa[N],dfn,nn;
	ll f[N],stk[N],top;
	bool ring[N];

	inline void dfs(int u,int fae)
	{
		pre[u]=++dfn;
		for(int i=beg[u];i;i=nxt[i])
			if((i^1)!=fae)
			{
				if(!pre[to[i]])
				{
					fa[to[i]]=u;
					ring[u]=0;
					dfs(to[i],i);
					if(!ring[u])
					{
						g[u].push_back(to[i]);
						g[to[i]].push_back(u);
					}
				}
				else if(pre[to[i]]<=pre[u])
				{
					int tmp=u;nn++;
					while(1)
					{
						g[nn].push_back(tmp);
						g[tmp].push_back(nn);
						ring[tmp]=1;
						if(tmp==to[i])break;
						tmp=fa[tmp];
					}
					ha[g[nn].size()]++;
				}
			}
	}
	
	inline void init()
	{
		nn=n;
		dfs(1,0);
		for(map<int,int>::iterator it=ha.begin();it!=ha.end();it++)
			vec.push_back(pr((*it).first,(*it).second));
	}	
}

using namespace abc;

inline ll hv(int x)
{
	return ((x&1?1+md-c:c-1)*qpow(inv,x)%md+1)*invc%md;
}

int main()
{
	n=read();m=read();q=read();
	for(int i=1,u,v;i<=m;i++)
	{
		u=read();v=read();
		add(u,v);add(v,u);
	}

	init();
	if(m==n-1)
	{
		for(int i=1;i<=q;i++)
		{
			c=read()%md;
			printf("%lld
",c*qpow(c-1,n-1)%md);
		}
		return 0;
	}

	for(int i=1;i<=q;i++)
	{
		c=read()%md;invc=qpow(c,md-2);
		ll tmp=1;inv=qpow(c-1,md-2);
		for(int i=0;i<vec.size();i++)
			tmp=tmp*qpow(hv(vec[i].first),vec[i].second)%md;
		printf("%lld
",c*tmp%md*qpow(c-1,nn-1)%md);
	}

	return 0;
}
原文地址:https://www.cnblogs.com/zltttt/p/9231782.html