P7967 [COCI2021-2022#2] Magneti 做题记录

P7967 [COCI2021-2022#2] Magneti

Description

给定 \(n\) 个磁铁和 \(m\) 个空位,其中相邻空位之间的距离为 \(1\),每个空位可放置一个磁铁。所有 \(n\) 个磁铁都必须被放置。每个磁铁可以吸引距离小于 \(r_i\) 的其它磁铁。

求所有磁铁互不吸引的方案总数对 \(10^9+7\) 取模的结果。

Solution

很难入手的一道题。因为我们发现,无论是以磁铁个数还是填的格子数为阶段,都无法合理地转移。

我们把整个过程强行拆为两步。首先我们先在板子下面将磁铁排列好,再将磁铁移动到板子上。

先将磁铁按照吸引半径从小到大排列。那么我在加入一个新磁铁时,只需要考虑当前磁铁会不会吸引其他的磁铁。

这样还是不好直接转移,因为不好处理在两个磁铁中再插入一个磁铁的情况。

问题出在,我在现在的决策中需要知道原先具体是哪些磁铁相邻,才能得出新状态中所需要占据的格子数量。

那么不妨在当前时刻就决定我的某两个磁铁要不要相邻,也就是决定我当前磁铁组成的连续段个数。

\(f_{i,j,l}\) 表示考虑到第 \(i\) 个磁铁,当前磁铁组成了 \(j\) 个连续段,总共需要占据 \(l\) 个格子的排列方案数。

我们有三种转移:

  1. 新磁铁单独成段,可以放在 \(j+1\) 个位置里,\(f_{i,j,l}\times(j+1)\rightarrow f_{i+1,j+1,l+1}\)
  2. 新磁铁与某一个连续段合并,与左右合并各有 \(j\) 个位置,\(f_{i,j,l}\times2j\rightarrow f_{i+1,j,l+r_{i+1}}\);
  3. 新磁铁把左右两边的段都合并起来,有 \(j-1\) 个位置,\(f_{i,j,k}\times (j-1)\rightarrow f_{i+1,j-1,k+2r_{i+1}-1}\)

最后我们所有的磁铁都变为了一个连续段。

对于一个状态 \(f_{n,1,l}\),板子上还有 \(m-l\) 个空位。那么我现在就要求出 \(m-l\) 个空位被分为 \(n+1\) 个部分,每个部分可以为空的方案数。

问题转化为不定方程整数解个数问题,就得出答案的转移 \(f_{n,1,l}\times \binom{m-l+n}{n}\rightarrow ans\)

int n,m,a[N];
ll f[N][N][M],fac[M+N+5],caf[N+M+5];

const ll mod=1e9+7;

inline ll Mod(ll x){return (x>=mod)?(x-mod):(x);}

inline void Add(ll &x,ll y){x=Mod(x+y);}

ll QuickPow(ll x,ll y){
	ll res=1;
	while(y){
		if(y&1) res=res*x%mod;
		x=x*x%mod; y>>=1;
	}
	return res;
}

void FacInit(){
	fac[0]=caf[0]=1;
	for(int i=1;i<=m+n+5;i++) fac[i]=fac[i-1]*i%mod;
	caf[m+n+5]=QuickPow(fac[n+m+5],mod-2);
	for(int i=n+m+4;i;i--) caf[i]=caf[i+1]*(i+1)%mod;
}

ll C(int x,int y){
	if(x<0||y<0||x<y) return 0;
	return fac[x]*caf[y]%mod*caf[x-y]%mod;
}

signed main(){
	read(n),read(m);
	for(int i=1;i<=n;i++) read(a[i]);
	sort(a+1,a+n+1);
	FacInit();
	f[0][0][0]=1;
	for(int i=0;i<=n;i++){
		for(int j=0;j<=i;j++){
			for(int l=0;l<=m;l++){
				if(!f[i][j][l]) continue;
				if(l+1<=m) Add(f[i+1][j+1][l+1],f[i][j][l]*(j+1)%mod);
				if(l+a[i+1]<=m) Add(f[i+1][j][l+a[i+1]],f[i][j][l]*2*j%mod);
				if(j&&l+2*a[i+1]-1<=m) Add(f[i+1][j-1][l+2*a[i+1]-1],f[i][j][l]*(j-1)%mod);
			}
		}
	}
	ll ans=0;
	for(int i=1;i<=m;i++) Add(ans,f[n][1][i]*C(m-i+n,n)%mod);
	printf("%lld\n",ans);
	return 0;
}

posted @ 2025-01-24 21:45  XP3301_Pipi  阅读(12)  评论(0)    收藏  举报
Title