[多校联考2021] 模拟赛5

CF917D Stranger Trees

题目描述

点此看题

解法

以前做过的题都没做出来 \(...\) 以后看到树计数问题一定要往矩阵树方面想一想,\(n\leq100\) 可能是关于 \(n\) 的高次复杂度,还有这种方法理解为用生成函数标记重合点即可,详细这里看

考试的时候以为是容斥,但是有一个计数不太会所以就凉了(而且我不相信可以 \(n\) 的低次复杂度来做),设 \(g[i]\) 表示至少有 \(i\) 条边重合的方案数,\(f[i]\) 表示恰好有 \(i\) 条边重合的方案数,那么根据二项式反演就不难发现:

\[f[i]=\sum_{j=i}^{n-1}(-1)^{j-i}\cdot {j\choose i}\cdot g[j] \]

那么 \(g[i]\) 怎么求呢?至少有 \(i\) 条边重合其实就等价于我们给原树划分 \(n-i\) 个连通块,这些联通块内部用原图的边,连通块之间任意连边形成一棵树,根据 purfer 那套理论,设连通块个数为 \(m\),大小分别为 \(s_i\),可以知道任意连边的方案数是:

\[n^{m-2}\prod_{i=1}^ms_i \]

那么考虑用 \(dp\) 来计数了,一个显然的方法是 \(dp[i][j][k]\) 表示以 \(i\) 为根的子树中划分 \(j\) 个连通块,\(i\) 所在的连通块大小为 \(k\) 的方案数,时间复杂度 \(O(n^3)\);但是还可以优化,复杂度瓶颈之一是要理解 \(s_i\) 这东西,可以考虑组合意义来优化掉他。

发现 \(\prod_{i=1}^ms_i\) 其实就是给每个连通块内部任意定根的方案数,把根是否确定放进状态中即可。设 \(dp[i][j][0/1]\) 表示以 \(i\) 为根的子树中划分了 \(j\) 个连通块,\(i\) 所在的连通块是否定根的方案数,转移就像背包一样把子树合并上来:

tmp[u][j+k][0]=(tmp[u][j+k][0]+dp[u][j][0]*dp[v][k][1])%MOD;//这条边割裂,u还是没定根,v必须定根
tmp[u][j+k][1]=(tmp[u][j+k][1]+dp[u][j][1]*dp[v][k][1])%MOD;//这条边割裂,u还是定了根,v必须定根
tmp[u][j+k-1][0]=(tmp[u][j+k-1][0]+dp[u][j][0]*dp[v][k][0])%MOD;//这条边保留,uv必须都不定根
tmp[u][j+k-1][1]=(tmp[u][j+k-1][1]+dp[u][j][1]*dp[v][k][0]+dp[u][j][0]*dp[v][k][1])%MOD;//这条边保留,uv其一定根

由于每对点只会在 \(\tt lca\) 处做一次贡献,所以时间复杂度 \(O(n^2)\)

#include <cstdio>
const int M = 105;
const int MOD = 1e9+7;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,tot,f[M],g[M],siz[M],C[M][M],dp[M][M][2],tmp[M][M][2];
struct edge
{
	int v,next;
}e[2*M];
void init()
{
	C[0][0]=1;
	for(int i=1;i<=n;i++)
	{
		C[i][0]=1;
		for(int j=1;j<=i;j++)
			C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
	}
}
int qkpow(int a,int b)
{
	int r=1;
	while(b>0)
	{
		if(b&1) r=r*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return r;
}
void dfs(int u,int fa)
{
	dp[u][1][0]=dp[u][1][1]=siz[u]=1;
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==fa) continue;
		dfs(v,u);
		for(int j=1;j<=siz[u];j++)
			for(int k=1;k<=siz[v];k++)
			{
				tmp[u][j+k][0]=(tmp[u][j+k][0]+dp[u][j][0]*dp[v][k][1])%MOD;
				tmp[u][j+k][1]=(tmp[u][j+k][1]+dp[u][j][1]*dp[v][k][1])%MOD;
				tmp[u][j+k-1][0]=(tmp[u][j+k-1][0]+dp[u][j][0]*dp[v][k][0])%MOD;
				tmp[u][j+k-1][1]=(tmp[u][j+k-1][1]+dp[u][j][1]*dp[v][k][0]+dp[u][j][0]*dp[v][k][1])%MOD;
			}
		siz[u]+=siz[v];
		for(int j=1;j<=siz[u];j++)
		{
			dp[u][j][0]=tmp[u][j][0],tmp[u][j][0]=0;
			dp[u][j][1]=tmp[u][j][1],tmp[u][j][1]=0;
		}
	}
}
signed main()
{
	n=read();
	init();
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read();
		e[++tot]=edge{v,f[u]},f[u]=tot;
		e[++tot]=edge{u,f[v]},f[v]=tot;
	}
	dfs(1,0);
	for(int i=2;i<=n;i++)
		g[n-i]=dp[1][i][1]*qkpow(n,i-2)%MOD;
	g[n-1]=1;
	for(int i=0;i<n;i++)
	{
		for(int j=i+1;j<n;j++)
		{
			int f=((j-i)&1)?-1:1;
			g[i]=(g[i]+f*g[j]*C[j][i])%MOD;
		}
		printf("%lld ",(g[i]+MOD)%MOD);
	}
}

亚特兰大

题目描述

给一棵带边权的树,问两点间边权 \(\gcd\)\(1\) 的路径有多少个。

\(q\) 次边权的修改,每次修改后都要输出答案。

\(n\leq 10^5,q\leq10^2,val\leq 10^6\)

解法

看到 \(\gcd\) 就要想一想莫比乌斯反演,更何况这道题还是统计 \(\gcd\)\(1\) 的路径条数(不用的话难以做出来),设 \(f[i]\) 表示边权 \(\gcd\)\(i\) 倍数的路径条数,那么不难得到答案是:

\[\sum_{i=1}^{10^6} f[i]\cdot \mu(i) \]

那么求出 \(f[i]\) 即可,可以把权值为 \(i\) 倍数的边保留,这样会形成若干个连通块,那么连通块内任意的路径都是合法的,把 \({siz\choose 2}\) 求个和就可以算出来了,可以用并查集来维护连通性。

\(s\)\(\mu\) 有值的最大因数个数(可以当 \(200\) 来估计),那么不难发现复杂度是 \(O(sn\cdot q)\) 的。

这道题的特点是 \(q\) 小的离谱,那么涉及到修改的边数是很少的,其他的边都不会修改,我们就先把这些边加进去。对于可能修改的边每次再暴力加即可,但是这要求我们的并查集是可回撤的,那么并查集用启发式合并,时间复杂度 \(O(s(n+q^2)\log n)\)

#include <cstdio>
#include <vector>
#include <iostream>
#include <map>
using namespace std;
const int M = 1000005;
const int N = 20*M;
#define ll long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,q,cnt,a[M],b[M],c[M],d[M],e[M],fl[M];
int tot,p[M],vis[M],mu[M],fa[N],siz[N];
map<int,int> mp[M];ll ans,tmp;vector<int> vc,eg;
void init(int n)
{
	mu[1]=1;
	for(int i=2;i<=n;i++)
	{
		if(!vis[i])
		{
			mu[i]=-1;
			p[++cnt]=i;
		}
		for(int j=1;j<=cnt && i*p[j]<=n;j++)
		{
			vis[i*p[j]]=1;
			if(i%p[j]==0) break;
			mu[i*p[j]]=-mu[i];
		}
	}
}
int find(int x)
{
	if(x==fa[x]) return fa[x];
	return find(fa[x]);
}
ll cal(int x)
{
	return 1ll*(siz[x]-1)*siz[x]/2;
}
void add(int c,int u,int v,int f)
{
	if(!mu[c]) return ;
	if(!mp[c][u]) mp[c][u]=++tot,fa[tot]=tot,siz[tot]=1;
	if(!mp[c][v]) mp[c][v]=++tot,fa[tot]=tot,siz[tot]=1;
	u=mp[c][u];v=mp[c][v];
	int x=find(u),y=find(v);
	if(siz[x]>siz[y])
	{
		ans-=(cal(x)+cal(y))*mu[c];
		siz[x]+=siz[y];
		fa[y]=x;
		ans+=cal(x)*mu[c];
		if(f) vc.push_back(y);
	}
	else
	{
		ans-=(cal(x)+cal(y))*mu[c];
		siz[y]+=siz[x];
		fa[x]=y;
		ans+=cal(y)*mu[c];
		if(f) vc.push_back(x);
	}
}
void clear()
{
	ans=tmp;
	while(!vc.empty())
	{
		int x=vc.back();
		vc.pop_back();
		siz[fa[x]]-=siz[x];
		fa[x]=x;
	}
}
void fuck()
{
	for(int i=0;i<eg.size();i++)
	{
		int x=eg[i];
		for(int j=1;j*j<=c[x];j++)
			if(c[x]%j==0)
			{
				add(j,a[x],b[x],1);
				if(j*j!=c[x]) add(c[x]/j,a[x],b[x],1);
			}
	}
	printf("%lld\n",ans);
	clear();
}
signed main()
{
	freopen("atoranta.in","r",stdin);
	freopen("atoranta.out","w",stdout);
	n=read();
	init(1e6);
	for(int i=1;i<n;i++)
		a[i]=read(),b[i]=read(),c[i]=read();
	q=read();
	for(int i=1;i<=q;i++)
		d[i]=read(),e[i]=read(),fl[d[i]]=1;
	for(int i=1;i<n;i++)
	{
		if(!fl[i])//边没有被标记过 
		{
			for(int j=1;j*j<=c[i];j++)
				if(c[i]%j==0)
				{
					add(j,a[i],b[i],0);
					if(j*j!=c[i]) add(c[i]/j,a[i],b[i],0);
				}
		}
		else eg.push_back(i);
	}
	tmp=ans;
	fuck();
	for(int i=1;i<=q;i++)
	{
		int x=d[i];c[x]=e[i];
		fuck();
	}
}

迫害DJ

题目描述

有一个递推数列,初始 \(g_0=a,g_1=b\),递归式为:

\[g_i=3\cdot g_{i-1}-g_{i-2} \]

求下式的值,一共迭代 \(k\) 次:

\[g_{g_{...g_n}}\bmod p \]

\(1\leq T\leq 1000,1\leq n,p\leq 10^9,1\leq k\leq 100\)

解法

思路大概就是找循环节了,设 \(F(n)\) 表示模 \(n\) 意义下的循环节长度:

\[F(p_i)= \begin{cases} 3&p_i=2\\ 8&p_i=3\\ 20&p_i=5\\ p_i-1&p_i\bmod 5=1/4\\ 2p_i+2&p_i\bmod 5=2/3 \end{cases} \]

\[F(n)=lcm(F(p_i)\cdot p_i^{k_i-1}) \]

然后就迭代 \(k\) 次即可,用上一层的循环节作为这一层的模数。

#include <cstdio>
#include <cstring> 
#include <iostream>
using namespace std;
#define int long long
const int M = 100005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int T,n,m,k,a,b,mod,cnt,vis[M],p[M];
int gcd(int a,int b) {return !b?a:gcd(b,a%b);}
int lcm(int a,int b) {return a/gcd(a,b)*b;}
int mul(int x,int y) {return (x*y-(int)((long double)x/mod*y)*mod+mod)%mod;}
void init(int n)
{
	for(int i=2;i<=n;i++)
	{
		if(!vis[i]) p[++cnt]=i;
		for(int j=1;j<=cnt && i*p[j]<=n;j++)
		{
			vis[i*p[j]]=1;
			if(i%p[j]==0) break;
		}
	}
}
struct Mat
{
	int a[2][2];
	Mat() {memset(a,0,sizeof a);}
	Mat operator * (const Mat &b) const
	{
		Mat r;
		for(int i=0;i<2;i++)
			for(int j=0;j<2;j++)
				for(int k=0;k<2;k++)
					r.a[i][k]=(r.a[i][k]+mul(a[i][j],b.a[j][k]))%mod;
		return r;
	}
}F,A;
Mat qkpow(Mat a,int b)
{
	Mat r;
	for(int i=0;i<2;i++) r.a[i][i]=1;
	while(b>0)
	{
		if(b&1) r=r*a;
		a=a*a;
		b>>=1; 
	}
	return r;
}
int FF(int x)
{
	if(x==2) return 3;
	if(x==3) return 8;
	if(x==5) return 20;
	if(x%5==1 || x%5==4) return x-1;
	if(x%5==2 || x%5==3) return 2*x+2;
}
int get(int x)
{
	int r=1;
	for(int i=1;p[i]*p[i]<=x;i++)
		if(x%p[i]==0)
		{
			int t=1;
			while(x%p[i]==0) x/=p[i],t*=p[i];
			r=lcm(r,FF(p[i])*t/p[i]);
		}
	if(x>1) r=lcm(r,FF(x));
	return r;
}
int work(int t,int p)
{
	if(t==0)//出口
		return n%p;
	int tmp=work(t-1,get(p));
	mod=p;
	if(tmp==0) return a;
	A=qkpow(F,tmp-1);
	return (mul(a,A.a[0][1])+mul(b,A.a[0][0]))%mod;
}
signed main()
{
	freopen("hakugai.in","r",stdin);
	freopen("hakugai.out","w",stdout);
	T=read();
	init(1e5);
	while(T--)
	{
		F.a[0][0]=3;F.a[0][1]=-1;F.a[1][0]=1;
		a=read();b=read();n=read();k=read();m=read();
		printf("%lld\n",work(k,m));
	}
}
posted @ 2021-04-06 21:11  C202044zxy  阅读(174)  评论(0编辑  收藏  举报