• [多校联考2021] 模拟赛1


    总结

    对于以前讲过的题还是要复习吧,比如今天 ( t T3) 就是正睿讲的那个题,但是不会做了。

    考场上 (A)( t T2) 挺不错,只是语言选错了拿了暴力分

    思维不行真的没办法,但是能拿的分都要拿到吧。

    盗梦空间

    题目描述

    (q) 次询问,每次给定树上 (k) 个关键点,求所有点中距离关键点最近距离的最大值。

    (n,m,qleq 100000)

    解法

    对于每次询问建虚树,然后讨论一下答案点落在哪里。

    • 落在虚树结点上:树形DP解决。
    • 落在空子树内:预处理子树内最远点,将儿子按照该值排序。
    • 在虚树边上:二分边上的分界点在哪里,预处理倍增数组查询答案。

    这东西除了 ( t Oneindark) 谁写得出来啊!

    爱乐之城

    题目描述

    要不你给我谨慎点,要不你给我常数小点。

    提交的时候没选 ( t O_2),直接 ( t T) 飞了。嘤嘤嘤

    给定 (n) 个元素 (a_{1..n}),对于 (iin[1,n]),求 (forall iin[1,n],F({a_{1..i}})),其中:

    [zxy_1(n)=sum_{i=1}^nisum_{j=1}^n[(i,j)=1]j ]

    [zxy_2(n)=sum_{i=1}^nsum_{j=1}^nmu(ij) ]

    [F(S)=sum_{Tin S}zxy_2(gcd(ain T))prod_{ain T}zxy_1(a) ]

    答案对 (998244353) 取模,强制在线,(1leq n,a_ileq 4e5)

    解法

    你要知道其实是三个不相关的子问题。

    对于子问题 (1),也就是求这个:

    [zxy_1(a_i)=sum_{j=1}^{a_i}jsum_{k=1}^{a_i}[(j,k)=1]cdot k ]

    这个只能在线算,把里面那东西反演一下:

    [sum_{j=1}^{a_i}jsum_{k=1}^{a_i}ksum_{x|(j,k)}mu(x) ]

    [sum_{x=1}^{a_i}mu(x)cdot x^2cdot sum(frac{a_i}{x})^2 ]

    其中 (sum(i)) 表示 (1+2+....i),这个数论分块可以直接 (O(sqrt m)),应该是正确的复杂度。


    对于子问题 (2),记 (d=gcd(a_1,a_2...,a_i)),也就是求这个:

    [zxy_2(d)=sum_{i=1}^dsum_{j=1}^dmu(ij) ]

    额,这个东西可以 (O(m^2)) 预处理,期望得分 (35) 分。

    因为是 ( t gcd),而且 (a_1) 是知道的,所以他是 (a_1) 的因数,有 (sqrt m) 种取值,如果对于每一种取值能够快速算就好了,会不会 (ij) 对数很小呢?别想了,已经试过了,数量级大的一批。

    因为不能把 (mu) 筛都不能筛出来,分析一下他的性质,有了,我们先加一些条件再积性函数分拆。

    An idea strikes me.

    [sum_{i=1}^dsum_{j=1}^d[(i,j)=1]mu(ij) ]

    [sum_{i=1}^dmu(i)sum_{j=1}^dmu(j)sum_{x|(i,j)}mu(x) ]

    [sum_{x=1}^{d}mu(x)cdot zy(x)^2 ]

    其中 (zy(x)=sum_{x|i}mu(i)),这个东西很容易预处理,额 (...) 所以我们在线算 (zy(x)) 不就行了?


    凉啦,求的是所有情况的答案,(100->0),先不做这题了。

    这可能就是命运吧。

    不管了我就是要硬刚这个题:

    [sum_{Tin S}Big(prod_{x}zxy_1(x)Big)cdot zxy_2(d) ]

    [sum_{d=1}^{m}zxy_2(d)sum_{Tin S}Big(prod_{xin T}zxy_1(x)cdot [d|x]Big)(gcd(x_i/d)=1) ]

    [sum_{d=1}^mzxy_2(d)Big(prod_{xin S}(zxy_1(x)cdot [d|x]+1)Big)sum_{z|gcd(x_i/d)}mu(z) ]

    [sum_{d=1}^mzxy_2(d)sum_{z=1}^{m/d}mu(z)Big(prod_{xin S}zxy_1(x)cdot [dz|x]+1Big) ]

    [sum_{T=1}^msum_{z|T}mu(z)cdot zxy_2(frac{T}{z}).... ]

    前面那个东西是系数,可以直接算出来。

    后面那东西好像可以边做边维护诶,每次就把 (a_i) 拿去分解就行了呗,把因数的答案更新下。

    但是算 (zxy_2(d)) 的时间复杂度又爆掉了啊,现在要 (O(m^2)) 来算了,对了,那个东西在 (zy(x)) 改的时候顺便修改一下不就行了吗?

    时间复杂度 (O(msqrt m)),真不错!但是好像还是过不了。


    第一个子问题可以搞,我们直接把所有 (v) 的都处理出来,还是一个一个增加。

    可以考虑成有若干个 (x),如果碰到一个 (x) 的倍数那么会改,所以也可以 (O(mlog m))

    都用因数的方式来考虑,都可以 (O(mlog m))

    #include <cstdio>
    #include <vector>
    using namespace std;
    const int M = 400005;
    const int MOD = 998244353;
    #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;
    }
    void write(int x)
    {
    	if(x<0) x=-x,putchar('-');
    	if(x<=9) {putchar(x+'0');return ;}
    	write(x/10);putchar(x%10+'0');
    }
    int n,m,op,cnt,ans,sy,a[M],b[M],c[M],f[M],g[M],zxy[M],now[M];
    int cur,mu[M],p[M],vis[M],zy[M],sum[M];vector<int> v[M];
    void sieve(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];
    		}
    	}
    	for(int i=1;i<=n;i++)
    	{
    		b[i]=mu[i]*i*i%MOD; 
    		c[i]=(c[i-1]+i)%MOD;
    	}
    	for(int i=1;i<=n;i++)
    		c[i]=c[i]*c[i]%MOD;
    }
    int gcd(int a,int b)
    {
    	return !b?a:gcd(b,a%b);
    }
    void upd(int x,int y)
    {
    	cur=(cur-mu[x]*zy[x]*zy[x])%MOD;
    	zy[x]+=y;
    	cur=(cur+mu[x]*zy[x]*zy[x])%MOD;
    }
    void fuck(int x,int y)
    {
    	ans=(ans-g[x]*f[x])%MOD;
    	f[x]=f[x]*(y+1)%MOD;
    	ans=(ans+g[x]*f[x])%MOD;
    }
    void add(int x)
    {
    	sy=(sy-b[x]*c[now[x]])%MOD;
    	now[x]++;
    	sy=(sy+b[x]*c[now[x]])%MOD;
    }
    signed main()
    {
    	freopen("lalaland.in","r",stdin);
    	freopen("lalaland.out","w",stdout); 
    	n=read();m=read();op=read();
    	sieve(m);
    	for(int i=1;i<=n;i++)
    		a[i]=read();
    	//把每个数的因数预处理出来
    	for(int i=1;i<=m;i++)
    		for(int j=i;j<=m;j+=i)
    			v[j].push_back(i);
    	for(int i=1;i<=m;i++)
    	{
    		//这东西要在线算 
    		for(int j=0;j<v[i].size();j++)
    			upd(v[i][j],mu[i]);
    		sum[i]=cur;f[i]=1;
    	}
    	for(int i=1;i<=m;i++)
    		for(int j=i;j<=m;j+=i)
    			g[j]=(g[j]+mu[i]*sum[j/i])%MOD; 
    	//优化 
    	for(int i=1;i<=m;i++)
    	{
    		for(int j=0;j<v[i].size();j++)
    			add(v[i][j]);
    		zxy[i]=sy;
    	}
    	//在线算呗
    	for(int i=1;i<=n;i++)
    	{
    		if(op==1) a[i]=(19891989*ans+a[i])%m+1;
    		//出题人死了几个吗这么搞,WDNMD!
    		//枚举a[i]的所有因数 
    		for(int j=0;j<v[a[i]].size();j++)
    			fuck(v[a[i]][j],zxy[a[i]]);
    		ans=(ans+MOD)%MOD;
    		if(op==0 && i==n) printf("%lld
    ",ans);
    		else if(op==1) printf("%lld
    ",ans);
    	}
    }
    

    星际穿越

    题目描述

    Once you’re a parent, you’re the ghost of your children’s future.

    And love is the only thing we’re capable of perceiving that transcends time and space.

    求满足下列条件的 (r imes n) 的矩阵个数:

    • 给定一个正整数 (k),我们说 (j(1leq j<n)) 是稳定的,当且仅当 (forall 1leq ileq r,a_{i,j}<a_{i,j+1})
    • 每一行是一个长度为 (n) 的排列
    • (j)(k) 的倍数,则第 (j) 列应该是不稳定的,否则若 (j) 不是 (k) 的倍数,则 (j) 列应该是稳定的。

    答案对 (998244353) 取模。

    (nleq 1000000,rleq 19891989)

    解法

    先讲一个 (40) 分的针对 (r=1,nleq2000) 的做法,不会真的有人去打五分的爆搜吧

    就用类似连续段 (dp) 的思想,每次插入一个新的数就会带来一些改变,也就是 (dp) 过程中排列是不确定的。所以就不能存固定的数值,设 (dp[i][j]) 表示填完前 (i) 个位置,(i) 位置的数位前 (i) 个数中的第 (j) 大,转移就看当前位置是不是 (k) 的倍数,用前缀后缀和算一下就行了,时间复杂度 (O(n^2))


    正解需要容斥,但是我们是做过类似的题的:不等关系,令 (m=lfloorfrac{n-1}{k} floor)(ans_i) 表示有 (i) 个位置违反的方案数,那么:

    [Ans=sum_{j=0}^m(-1)^jans_j ]

    考虑 (dp) 维护容斥系数,设 (f_i) 表示考虑到 (i) 的容斥系数,转移就枚举连续的一段放小于号,段的端点一定是不稳定的大于号,但是我们让他是随便放的,所以就会划分出段。对于那些被钦定成了小于号的大于号,会贡献一个 (-1) 的系数,我们再最后乘上 ((-1)^m) 在随便放的那里贡献 (-1)(方便转移):

    [f_i=-sum_{j=0}^{i-1}frac{f_j}{[k(i-j)]!} ]

    除以 ([k(i-j)]!) 表示可重集的排列数,因为段内顺序一定,全局又是无须的,那么最后的答案是:

    [n!cdot(-1)^msum_{i=0}^mfrac{f_i}{(n-ik)!} ]

    上面讨论的是 (r=1) 的情况,你发现转移算的是一段小于号的情况,这个可以直接扩展到 (r>1) 的情况,因为这样每一行就是独立的了,直接乘法原理即可,直接把所有的阶乘快速幂一下。

    暴力算这个 (dp)(O(n^2)) 的,但是可以分治 ( t FFT) 可以 (O(nlog^2n))

    众所周知,简单的分治 ( t FFT) 可以用多项式求逆优化,设 (F(x)=sum_{j=1}^infty f_ix^i,G(x)=sum_{j=1}^inftyfrac{1}{(jk)!})

    [F(x)=-(F(x)+1)G(x) ]

    [F(x)=frac{-G(x)}{1+G(x)} ]

    直接多项式求逆做到 (O(nlog n))

    #include <cstdio>
    #include <iostream>
    using namespace std;
    const int M = 4000005;
    const int MOD = 998244353;
    #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,m,r,k,ans,fac[M],inv[M],a[M],f[M],g[M];
    namespace poly
    {
    	int len,A[M],B[M],rev[M];
    	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 NTT(int *a,int len,int op)
    	{
    		for(int i=0;i<len;i++)
    		{
    			rev[i]=(rev[i>>1]>>1)|((i&1)*(len/2));
    			if(i<rev[i]) swap(a[i],a[rev[i]]);
    		}
    		for(int s=2;s<=len;s<<=1)
    		{
    			int t=s/2,w=(op==1)?qkpow(3,(MOD-1)/s):qkpow(3,MOD-1-(MOD-1)/s);
    			for(int i=0;i<len;i+=s)
    				for(int j=0,x=1;j<t;j++,x=x*w%MOD)
    				{
    					int fe=a[i+j],fo=a[i+j+t];
    					a[i+j]=(fe+x*fo)%MOD;
    					a[i+j+t]=((fe-x*fo)%MOD+MOD)%MOD;
    				}
    		}
    		if(op==1) return ;
    		int inv=qkpow(len,MOD-2);
    		for(int i=0;i<len;i++) a[i]=a[i]*inv%MOD;
    	}
    	void work(int n,int *a,int *b)
    	{
    		len=1;while(len<2*n) len<<=1;
    		for(int i=0;i<len;i++) A[i]=B[i]=0;
    		for(int i=0;i<n;i++) A[i]=a[i];
    		for(int i=0;i<n/2;i++) B[i]=b[i];
    		NTT(A,len,1);NTT(B,len,1);
    		for(int i=0;i<len;i++)
    			A[i]=((2*B[i]-B[i]*B[i]%MOD*A[i])%MOD+MOD)%MOD;
    		NTT(A,len,-1);
    		for(int i=0;i<n;i++) b[i]=A[i];
    	}
    	void inv(int n,int *a,int *b)
    	{
    		b[0]=qkpow(a[0],MOD-2);
    		int cur=1;
    		while(cur<n)
    		{
    			cur<<=1;
    			work(cur,a,b);
    		}
    	}
    	void mul(int n,int *a,int *b)
    	{
    		len=1;while(len<2*n) len<<=1;
    		for(int i=0;i<len;i++) A[i]=B[i]=0;
    		for(int i=0;i<n;i++) A[i]=a[i],B[i]=b[i];
    		NTT(A,len,1);NTT(B,len,1);
    		for(int i=0;i<len;i++) A[i]=A[i]*B[i]%MOD;
    		NTT(A,len,-1);
    		//只需要n项 
    		for(int i=0;i<n;i++) b[i]=A[i];
    	}
    };
    void init(int n)
    {
    	fac[0]=inv[0]=inv[1]=1;
    	for(int i=2;i<=n;i++) inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
    	for(int i=2;i<=n;i++) inv[i]=inv[i-1]*inv[i]%MOD;
    	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
    	for(int i=1;i<=n;i++)
    	{
    		inv[i]=poly::qkpow(inv[i],r);
    		fac[i]=poly::qkpow(fac[i],r);
    	}
    }
    signed main()
    {
    	freopen("interstellar.in","r",stdin);
    	freopen("interstellar.out","w",stdout);
    	n=read();r=read();k=read();m=(n-1)/k+1;
    	init(1e6);
    	for(int i=1;i<m;i++)
    		g[i]=MOD-inv[i*k],a[i]=inv[i*k];
    	a[0]=1;
    	poly::inv(m,a,f);
    	poly::mul(m,g,f);
    	f[0]=1;//这里别忘记了哦 
    	for(int i=0;i<m;i++)
    		ans=(ans+f[i]*inv[n-i*k])%MOD;
    	if((m-1)&1) ans=MOD-ans;
    	ans=ans*fac[n]%MOD;
    	printf("%lld
    ",ans);
    }
    
  • 相关阅读:
    转载--C 的回归
    学嵌入式不是你想的那么简单--转载
    scanf() 与 gets()--转载
    getchar、getch、getche 与 gets()
    scanf()函数原理
    C/C++头文件一览
    再论函数指针、函数指针数组
    初论函数指针、指针函数、指针的指针
    转载--一个“码农”自述的血泪史:当了35年程序员,我最大的遗憾就是没抓住机遇转行
    转载--协方差的意义和计算公式
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/14598361.html
Copyright © 2020-2023  润新知