P11281 「GFOI Round 2」Aob & Blice

让我们思考一下“无限轮”。

不难发现,根据 Aob 的行动方式,经过足够次随机后,最终集合 \(S_A=\set{(i,j),(p_i,p_j)|1\le i<j\le n,p_i>p_j}\),即排列 \(\set{p_n}\) 中所有逆序对及其对应下标。

因此只需要考虑 \(S_B\) 即可。

对于一组满足 \(1\le i<j\le n,p_i>p_j\)\((i,j)\),显然 \((j,i)\neq(i,j)\)。为了使 \(S_B=S_A\)\(S_B\) 中应当存在 \((p_j,p_i)=(i,j)\);同时,\(S_A\) 中也应当有 \((p_{p_i},p_{p_j})=(p_j,p_i)\)。将两式合并,得到 \(p_{p_i}=i\)

这样我们已经完成了性质 A,只需要统计对于每个 \(p_i\) 是否满足 \(p_{p_i}=i\) 即可,若不满足直接输出 \(0\)。接下来考虑排列 \(\set{p_n}\) 有缺失的情况。

我们先将能填充的缺失部分填好。对于 \(p_i\neq0\) 的项,若 \(p_{p_i}=0\),那我们可以直接将 \(p_{p_i}\) 填上 \(i\)

当所有能填的空填完后,问题就转化为了性质 B。记排列 \(\set{p_n}\) 中有 \(cnt\) 项为 \(0\),我们需要求出对这些空格填空的总方案数。对于第 \(i\) 个空,我们要么填入 \(i\),要么填入 \(k\) 同时在第 \(k\) 个空填入 \(i\)

这个是问题相当于求数列 A000085,记 \(dp(i)\) 为有 \(i\) 个空时的总方案数,则 \(dp(i)=dp(i-1)+dp(i-2)\times(i-1)\),边界为 \(dp(0)=dp(1)=1\),答案为 \(dp(cnt)\)场上查到 oeis 之后把递推式打成了 dp[i]=dp[i-1]-(cnt-1)*dp[i-2] 怒挂 70 分,唐!

至此,此题得解。


#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=1e6+10,P=998244353;

int n,p[MAXN],dp[MAXN];

signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)cin>>p[i];
	for(int i=1;i<=n;i++)
	{
		if(p[i]&&!p[p[i]])p[p[i]]=i;
	}
	int cnt=0;
	for(int i=1;i<=n;i++)
	{
		if(p[i]==0)cnt++;
		else if(p[p[i]]==i)continue;
		else
		{
			cout<<0;
			return 0;
		}
	}
	dp[0]=dp[1]=1;
	for(int i=2;i<=cnt;i++)dp[i]=(dp[i-1]+dp[i-2]*(i-1))%P;
	cout<<dp[cnt];
	return 0;
}
posted @ 2025-02-09 22:30  H2ptimize  阅读(21)  评论(0)    收藏  举报