ARC162E Strange Constraints 做题记录

给定长度为 nn 的序列 AA,求满足以下条件的序列 BB 的个数模 998244353998244353 的结果:

  • 值域 [1,n][1, n]
  • ii 的个数不超过 AiA_i
  • BiB_i 的个数不超过 AiA_i

1Ain5001 \le A_i\le n \le 500

刚开始想的是把 AA 从大到小排序,这样每个数的出现次数就由它最后一次出现的位置决定,设 fi,j,kf_{i,j,k} 表示处理完前 A[1,i]A_{[1,i]},一共填了 jj 个位置,一共填了 kk 种数的方案数,但是发现是 O(n4)O(n^4) 的。

看题解发现忽略了一个很重要的东西:调和级数 i=1nO(ni)=O(nlnn)\sum\limits_{i=1}^n O(\frac{n}{i})=O(n\ln n)

did_i 表示 ii 的出现次数,观察 dd 需要满足什么性质:

  • diAid_i\le A_i
  • 对于所有满足 diAjd_i\le A_j 的位置 jjBjB_j 可以等于 ii

观察到 di<dj\forall d_i<d_jjj 能放的位置 ii 都能放,即 ii 的决策包含 jj

那么设 fi,j,kf_{i,j,k} 表示决定完 dx[i,n]d_x\in[i,n]xx,共放了 jj 种不同的数,消耗了 kk 个位置的答案。

转移直接枚举有多少个 xx 满足 dx=id_x=i 即可。

由于 jj 的上界是 ni\frac{n}{i}dx=id_x=ixx 的个数上界也是 ni\frac{n}{i},所以时间复杂度为 O(ni=1nn2i2)O(n\sum\limits_{i=1}^n\frac{n^2}{i^2}),约为 O(n2ln3n)O(n^2\ln^3 n),代码如下:

#include <iostream>
#include <algorithm>
#include <cstdio>

using namespace std;

const int S=505,p=998244353;

int n,a[S];
int fra[S],inv[S],pinv[S][S],C[S][S],f[S][S][S];

inline void add(int &x,int y)
{
	x+=y;
	if(x>=p) x-=p;
}

inline int qpow(int x,int y)
{
	int res=1;
	for(;y>0;y>>=1,x=1ll*x*x%p) res=y&1?1ll*res*x%p:res;
	return res;
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	sort(a+1,a+n+1);
	for(int i=0;i<=n;i++)
	{
		fra[i]=i==0?1:1ll*i*fra[i-1]%p;
		C[i][0]=1;
		for(int j=1;j<=i;j++) C[i][j]=(C[i-1][j]+C[i-1][j-1])%p;
	}
	inv[n]=qpow(fra[n],p-2);
	for(int i=n;i>=1;i--) inv[i-1]=1ll*inv[i]*i%p;
	for(int i=0;i<=n;i++)
	{
		pinv[i][0]=1;
		for(int j=1;j<=n;j++) pinv[i][j]=1ll*pinv[i][j-1]*inv[i]%p;
	}
	f[n+1][0][0]=1;
	for(int i=n,pp=n+1;i>=1;i--)
	{
		while(pp>1&&a[pp-1]>=i) pp--;
		int len=n-pp+1;
		for(int j=0;j<=n/i&&j<=len;j++)
		{
			for(int k=0;k<=n;k++)
			{
				for(int l=0;l<=j&&l<=n/i&&l<=k/i;l++)
				{
					int bse=1ll*C[len-j+l][l]*C[len-k+i*l][i*l]%p*fra[i*l]%p*pinv[i][l]%p;
					add(f[i][j][k],1ll*bse*f[i+1][j-l][k-i*l]%p);
				}
			}
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++) add(ans,f[1][i][n]);
	printf("%d\n",ans);
	return 0;
}