专栏文章
「GFOI Round 2」Aob & Blice
P11281题解参与者 2已保存评论 1
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 1 条
- 当前快照
- 1 份
- 快照标识符
- @mir441hb
- 此快照首次捕获于
- 2025/12/04 15:26 3 个月前
- 此快照最后确认于
- 2025/12/04 15:26 3 个月前
幽默绿题卡了我一个半小时,写篇题解鞭尸一下自己。
题意
定义一个排列 的逆序对集合 。
定义 ;
求有多少种排列 ,使得 。
翻译过来就是求有多少个排列 满足:对于所有逆序对 中的这两组有序数对全部放入一个集合里,且对于所有 和 都能在这个集合中找到。
题解
此题的后效性显然,故不去考虑 DP,考虑使用排列组合解决问题。
先说结论:对于一个满足题目要求的排列 ,当且仅当对于任意的正整数 ,满足 。
翻译过来就是,对于一个位置 ,上面的数字为 ,位置 为 上的数字为 。
下面给出证明。
- 初始状态定义为 ,即排列为单调递增的 。此时排列 一定合法,因为没有逆序对, ;
- 考虑将排列中的两个数 和 交换位置,此时, 且 ,不妨设 ;
- 容易发现,这产生了一些逆序对(下面的叙述建立在交换后的状态): 和 ;
- 容易发现 恰好等于 ;
- 此时接着分讨,在除了 和 的所有数中进行交换,共三种情况,很复杂,但是讨论之后容易发现,这三种交换情况最后都是符合条件的;
- 但是如果把 和 中的任意一个和别的数交换了,很容易发现该排列不再满足题目条件。
综上,我们得出结论:所有的合法状态都来自于初始状态的交换。
下面结合图片更好理解。

回到题目,我们需要先判断是否有解再去计算方案数。
判断有解就是判断对于 ,如果数字 在位置 上,则万事大吉,反之:
- 如果数字 在位置 上,也无妨,合法;
- 如果数字 没有出现过,那么把它填在位置 上。
- 如果数字 出现过还没有填在位置 上,则必定无解,答案为 。
剩下的,就是计算将没填的数拎出来填上,具体方法如下:
- 首先显而易见,设当前没填的数字为 ,则位置 一定是空缺的。然后把所有的数字 全部填到对应的位置 上。
- 对于刚才填进去的数字,我们可以挑出一部分两两匹配,剩下的留在原位置。
- 直接暴力计数即可。
所以,我们可以线性枚举有多少个数字留在原位置,剩下的数字两两交换, 计算方案。
设共 个数两两匹配的方案数为 ,则 ,显然 只能为偶数,不然必定会有一个数留在原位置。
考虑由 递推到 ,发现可以先把第一个数和剩下 个数中随便找一个匹配,剩下的 个数自由匹配,得到递推式 。
由于我们枚举有多少个数留在原位置,这个数的奇偶性是确定的。
设最终我们要把 个数填进去,设 为留在原位置的数的个数,则答案为:
且要求 的奇偶性与 相同。
时间复杂度 。
具体结合代码理解。
Code
CPP#include<bits/stdc++.h>
#define int long long
#define pb push_back
namespace IO{
inline int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
}
using namespace IO;
using namespace std;
const int N=1e6+10;
const int mod=998244353;
int a[N],pos[N];
int n;
bool vis[N];
int f[N];
int df[N];
int ksm(int x,int p){
int ret=1;
while(p){
if(p&1) ret=1LL*ret*x%mod;
x=1LL*x*x%mod;
p>>=1;
}
return ret;
}
int C(int x,int y){
if(y==0) return 1;
return f[x]*df[y]%mod*df[x-y]%mod;
}
int p[N];
signed main(){
n=read();
for(int i=1;i<=n;i++){
a[i]=read();
if(a[i]!=0) pos[a[i]]=i;
}
int cnt=0;
for(int i=1;i<=n;i++){
if(a[i]==0){
if(pos[i]&&!pos[pos[i]]){
a[i]=pos[i];pos[pos[i]]=i;
}else if(pos[i]&&pos[pos[i]]){
puts("0");return 0;
}
}
}
for(int i=1;i<=n;i++){
if(i!=a[i]){
if(pos[i]!=a[i]){
puts("0");return 0;
}
}
if(!a[i]) ++cnt;
}
f[0]=1;
for(int i=1;i<=n;i++){
f[i]=f[i-1]*i%mod;
}
df[n]=ksm(f[n],mod-2);
for(int i=n-1;i>=0;i--){
df[i]=df[i+1]*(i+1)%mod;
}
int ans=0;
p[0]=1;
for(int i=2;i<=n;i+=2){
p[i]=(i-1)*p[i-2]%mod;
}
if(cnt&1){
for(int i=1;i<=cnt;i+=2){
ans=(ans+C(cnt,i)*p[cnt-i]%mod)%mod;
}
}else{
for(int i=0;i<=cnt;i+=2){
ans=(ans+C(cnt,i)*p[cnt-i]%mod)%mod;
}
}
cout<<ans;
return 0;
}
相关推荐
评论
共 1 条评论,欢迎与作者交流。
正在加载评论...