[多校联考2021] 模拟赛2

总结

( t tly,yyds!) 今天他直接碾压第二题百来分,( t A) 掉了两个题。

怎么说呢,我觉得正是这种难度的比赛才能看得出思维能力,这种比赛的特点就是淡化套路,( t NOI) 的出题风格也确实向这方面靠近了(我觉得这套题和 ( t NOI2020space Day2) 很像),我认为 ( t tly) 巨佬是最有钻研精神的一个,我常常看到他在走廊上独自思考,而且他从来不听讲评总是自己想题,最重要的是即使这么强他也从不摸鱼,永远再挑战更高的境界。

我在想,这也许就是所谓不忘初心吧 (...)

还有考试策略的问题,这次我口胡了很多暴力分上去,导致最后都没有时间打。下次应该先预估比赛难度,如果像这种难度的比赛就边想边打暴力,这次第二题写动态动规直接飞黄腾达

wsm

题目描述

定义两个非递减数列 (A,B) 之间的笛卡尔和数列 (C) 为任意的 ((A_i+B_j)) 排序之后得到的非递减数列。

(T) 组数据,给定 (C) 的长度 (n)(A) 的长度 (m),问有多少对合法的 ((A,B)) 满足笛卡尔和数列为 (C),答案对 (998244353) 取模。

(1leq Tleq 500,1leq n,mleq 10^{12},nmod m=0)

解法

感谢 ( t lsk) 学长的耐心讲解,他在考试时 ( t A) 了这个题 (\%\%\%)

首先 ( t yy) 一下最后的结果是怎么样的?想象我们把 (C) 排成一个序列,然后序列 (A) 的功能是在这个序列上面打标记,(B) 的功能是平移这个标记,最后的要求是让标记覆盖所有的 (C)( t lsk) 学长的神奇转化)

我们考虑在 (C) 的这个序列上 (dp),设 (dp(i,j)) 表示已经填满了前 (i) 个空,用了 (j)(A) 序列的方案数,那么转移考虑把前面的序列 ( t copy) 一份(因为 (B) 的作用是整体平移,所以考虑 ( t copy)),转移:

  • 考虑添加 (B) 序列中元素:$$dp(kcdot i,j)leftarrow dp(i,j)$$

  • 考虑添加 (A) 序列中元素:$$dp(kcdot i,kcdot j)leftarrow dp(i,j)$$

边界条件是 (dp(1,1)=1),但是这样 (dp) 会有问题,如果 (S) 能转移到 (SS)(SSSS),然后 (SS) 又能转移到 (SSSS),那么显然会算多,所以我们强制第一种转移和第二种转移交替进行(状态增加一维就行)。

但是这样写转移肯定优化不了,因为最后我们只需要求 (dp(n,m)),所以把转移写成递归的形式,由于转移要交替进行我们定义 (f,g) 两个 (dp) 数组,下面的转移一定要把条件写清哦:

[f(n,m)leftarrow g(frac{n}{k},m) ]

[g(n,m)leftarrow f(frac{n}{k},frac{m}{k}) ]

现在优化的方式很唯一,就是研究转移路径,因为转移都是做的除法,所以考虑质因数分解。这里把 (frac{n}{m})(m) 质因数分解就行了,记分解出来的质因数集合分别为 (S)(T),发现第一种转移就是在 (S) 中取若干质因数,第二种转移就是在 (T) 中取若干质因数。

那么再把题目做一个转化,有 (S,T) 两个小球集合,有大约 (20) 种颜色 (50) 个小球,问交替划分小球集合的方案数。设 (f(i))(S) 划分出 (i)非空盒子的方案数,(g(i))(T) 划分出 (i) 个非空盒子的方案数,答案可以直接合并:

[sum_{i}f(i)cdot (g(i-1)+2g(i)+g(i+1)) ]

上式就是交替放置,不难理解。那么现在算出 (f(i)) 就行了((g(i)) 同理算),我们知道允许空盒子的情况是特别好算的,这就是考虑每种颜色的球,然后问不定方程解的个数,就是隔板法,设 (a(i)) 表示质因数 (p_i) 的指数:

[f(i)=prod_{j} {a(j)+i-1choose i-1} ]

现在要求盒子非空,那么容斥回去就可以了,这里我用的是二项式反演

[f'(i)=sum_{j=1}^i(-1)^{i-j}f(j)cdot {ichoose j} ]

时间复杂度瓶颈在于质因数分解,我们预处理一下 (10^6) 以内的质数就能少掉一个 (log) 了,好消息是不卡常。

小小的总结一下吧,考试时候总以为要推结论然后就没敢做。

方法还是老方法,先分析结果有什么特点是最重要的(这个需要强大的感知能力),这道题就是直接 (dp) 然后考虑优化的一道妙妙题,优化需要强大的观察能力哦(才能观察到质因数),这道题确实没有什么套路。

#include <cstdio>
const int N = 105;
const int M = 1000005;
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 T,n,m,cnt,ans,p[M],vis[M],C[N][N],a[N],f[N],g[N];
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;
		}
	}
	C[0][0]=1;
	for(int i=1;i<=100;i++)
	{
		C[i][0]=1;
		for(int j=1;j<=i;j++)
			C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
	}
}
void fuck(int x,int *f)
{
	//质因数分解 
	a[0]=0;
	for(int i=1;p[i]*p[i]<=x;i++)
		if(x%p[i]==0)
		{
			a[++a[0]]=0;//只用计数就行了吧 
			while(x%p[i]==0)
				x/=p[i],a[a[0]]++;
		}
	if(x>1) a[++a[0]]=1;
	//下面是容斥
	for(int i=1;i<=50;i++) f[i]=1; 
	for(int i=1;i<=a[0];i++)//枚举每种球
	{
		for(int j=1;j<=50;j++)//j个盒子里面乱几把放 
			f[j]=f[j]*C[a[i]+j-1][j-1]%MOD;
	}
	for(int i=50;i>=1;i--)//高往低容斥 
	{
		for(int j=i-1;j>=1;j--)
		{
			if((i-j)&1) f[i]=(f[i]-f[j]*C[i][j])%MOD;
			else f[i]=(f[i]+f[j]*C[i][j])%MOD;
		}
	}
}
signed main()
{
	freopen("wsm.in","r",stdin);
	freopen("wsm.out","w",stdout);
	T=read();
	init(1e6);
	while(T--)
	{
		n=read();m=read();ans=0;
		if(n==m || m==1)
		{
			puts("1");
			continue;
		}
		fuck(n/m,f);//对n质因数分解后容斥 
		fuck(m,g);//对n/m质因数分解后容斥 
		for(int i=1;i<=50;i++)
			ans=(ans+f[i]*(2*g[i]+g[i-1]+g[i+1])%MOD)%MOD;
		printf("%lld
",(ans+MOD)%MOD);
	}
}

原文地址:https://www.cnblogs.com/C202044zxy/p/14607802.html