zoukankan      html  css  js  c++  java
  • [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;
    }
    
  • 相关阅读:
    搭建maven web项目
    Promise封装AJAX
    迭代器Iterator
    ...扩展运算符
    解构赋值和模板字符串(反引号 ` `)
    记录一下let,const,var的区别
    重拾旧笔,好久不见
    SQL 中实现递归(根据子节点查找父节点)
    SQL 利用case when 动态给SQL添加条件查询语句
    关于webapi练习过程中遇到的一系列问题记录
  • 原文地址:https://www.cnblogs.com/zltttt/p/9231782.html
Copyright © 2011-2022 走看看