[多校联考2021] 模拟赛5
CF917D Stranger Trees
题目描述
解法
以前做过的题都没做出来 \(...\) 以后看到树计数问题一定要往矩阵树方面想一想,\(n\leq100\) 可能是关于 \(n\) 的高次复杂度,还有这种方法理解为用生成函数标记重合点即可,详细这里看
考试的时候以为是容斥,但是有一个计数不太会所以就凉了(而且我不相信可以 \(n\) 的低次复杂度来做),设 \(g[i]\) 表示至少有 \(i\) 条边重合的方案数,\(f[i]\) 表示恰好有 \(i\) 条边重合的方案数,那么根据二项式反演就不难发现:
那么 \(g[i]\) 怎么求呢?至少有 \(i\) 条边重合其实就等价于我们给原树划分 \(n-i\) 个连通块,这些联通块内部用原图的边,连通块之间任意连边形成一棵树,根据 purfer
那套理论,设连通块个数为 \(m\),大小分别为 \(s_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\) 倍数的路径条数,那么不难得到答案是:
那么求出 \(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\),递归式为:
求下式的值,一共迭代 \(k\) 次:
\(1\leq T\leq 1000,1\leq n,p\leq 10^9,1\leq k\leq 100\)
解法
思路大概就是找循环节了,设 \(F(n)\) 表示模 \(n\) 意义下的循环节长度:
然后就迭代 \(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));
}
}