[多校联考2021] 模拟赛2

总结

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

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

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

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

wsm

题目描述

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

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

\(1\leq T\leq 500,1\leq n,m\leq 10^{12},n\bmod m=0\)

解法

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

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

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

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

  • 考虑添加 \(A\) 序列中元素:$$dp(k\cdot i,k\cdot 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-1\choose i-1} \]

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

\[f'(i)=\sum_{j=1}^i(-1)^{i-j}f(j)\cdot {i\choose 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\n",(ans+MOD)%MOD);
	}
}

posted @ 2021-04-01 19:36  C202044zxy  阅读(131)  评论(0编辑  收藏  举报