专栏文章

题解:P9418 [POI 2021/2022 R1] Impreza krasnali

P9418题解参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@miq4rhsx
此快照首次捕获于
2025/12/03 22:56
3 个月前
此快照最后确认于
2025/12/03 22:56
3 个月前
查看原文

前言

模拟赛T1,正解是DP但是很显然本蒟蒻不会,所以搞了个神秘做法。

题意

有一个 1n1 \sim n 的排列 PP。现在给定一个长度为 nn,值域为 [1,n][1,n] 的序列 AA,其中 AiA_i 表示 Pi1P_{i-1}Pi+1P_{i+1}AiA_i
特别的,P2P_2 总是等于 AiA_iPn1P_{n-1} 总是等于 AnA_n
问你有多少个 PP 满足 AA 对其的限制,对 109+710^9+7 取模。
1n1051 \le n \le 10^5

题解

先判断掉一些不合法的情况:
  • 某个数在 AA 之中的出现次数 >2> 2
  • 两个相同的数的间隔 2\not = 2
  • i[1,n],Pi=i\forall i \in [1,n],P_i=i,且 nn 为奇数。
现在来依次看看这些不合法的情况。
第一种情况很显然,直接讨论第二种。
如果有两个相同的数 Pi1P_{i-1}Pi+1P_{i+1},我们就可以确定 Ai=Pi1A_i=P_{i-1}
反之不合法。
对于第三种,因为我们可以结合已经确定的 A2A_2An1A_{n-1} 来进一步确定中间的 AiA_i,如果 nn 是奇数的话,拓展到中间时就会矛盾。
举个例子
CPP
P: 1 2 3 4 5
A: ? 1 ? 5 ?
我们发现对于 P3=3P_3=3 来说,既没有 A2=3A_2=3,也没有 A4=3A_4=3。所以矛盾了。
在这个手摸的过程中我们还发现如果 Pi=iP_i=inn 为偶数时有且仅有一种排列满足。
我们先将可以确定的数确定下来。因为每一次确定的数都可能对前/后的数有影响,所以我们没有办法通过顺序扫一遍完成这个操作。
我这里是使用了两个指针 l,rl,r ,不断往前推来进行互相更新。
现在我们再来思考怎么样计算答案。
这里给出一组样例:
CPP
10
3 1 3 4 9 8 10 6 2 6
在这个样例中,能确定的数如下
CPP
? 3 ? ? ? ? ? ? 6 ?
把这两个序列结合起来看,如图。
一个数箭头指向的两个位置指的是这两个位置中一定要有一个位置的值和它相等。
观察发现,红色箭头与蓝色箭头是相互独立的,所以我们可以分开考虑。
单独拎出来的话长这样。
假设另外加入的数为 x,情况如下
加入的 x,指在 AA 中没有出现过的数,即没有限制。
CPP
x 1 4 8
1 x 4 8
1 4 x 8
1 4 8 x
实际上就是把 x 插入每两个数之间。假设这段 00tottot 个,就会有 tottot 种方案。
分别单/偶数下标遍历一遍找到每一个 tottot 即可。
根据乘法原理,每一段 00 之间不互相影响,需要乘起来。
又因为我们要给每一段 00 分配一个 x,假设有 cntcnt 个数没有出现过,分配的方案即为 cnt!cnt!
答案为
cnt!totcnt! \prod tot
时间复杂度 O(n)\mathcal{O}(n)
talk is cheap,show me the code!
CPP
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define debug(x)	cerr<<"in "<<x<<"!!!\n";
const int N=1e5+5,mod=1e9+7;
int n;
int h[N],a[N];
bool used[N];
int bk[N]; 
int ans=1;
bool deal(int x){//推导能确定的 A_i。
	if(a[x-1]&&a[x+1]&&a[x-1]!=h[x]&&a[x+1]!=h[x])	return 1;
	if(a[x-1]||a[x+1]){
		if(a[x-1]&&a[x-1]!=h[x])	a[x+1]=h[x];
		else a[x-1]=h[x];
	}
	return 0;
}
signed main(){
	scanf("%d",&n);
	bool fl=1;
	for(int i=1;i<=n;i++){
		scanf("%d",&h[i]);
		bk[h[i]]++;
		if(bk[h[i]]>2){
			printf("0\n");
			return 0;
		}
		if(h[i]!=i)	fl=0;
	}
	if(fl){//判断 P_i=i 的情况。
		printf("%d\n",!(n&1));
		return 0;
	}
	for(int i=3;i<=n-2;i++){
		if(bk[h[i]]==2){
			if(h[i]!=h[i-2]&&h[i]!=h[i+2]){//判断距离。
				printf("0\n");
				return 0;
			}
		}
	}
	for(int i=2;i<=n-1;i++){
		if(h[i-1]==h[i+1]){//确定那种 P_{i-1} 和 P_{i+1} 都确定的。
			a[i]=h[i-1];
			if(used[a[i]]){
				printf("0\n");
				return 0;
			}
			used[a[i]]=1;
		}
	}
	if((a[2]&&a[2]!=h[1])||(a[n-1]&&a[n-1]!=h[n])){
		printf("0\n");
		return 0;
	}
	a[2]=h[1],a[n-1]=h[n];
	used[a[2]]=used[a[n-1]]=1;
	int l=3,r=n-2;
	while(l<n||r>1){//确定能确定的 A_i。
		while(l<n&&used[h[l]])	l++;
		while(r>1&&used[h[r]])	r--;
		if(l<n&&deal(l)){
			printf("0\n");
			return 0;
		}
		if(r>1&&deal(r)){
			printf("0\n");
			return 0;
		}
		if(l<n) l++;
		if(r>1)	r--;
	}
	int qwq=0;
	for(int i=1;i<=n;i++){
		if(!bk[i])	qwq++;//找出没有出现过的数的个数。
	}
	int tot=0;
	for(int i=1;i<=n;i+=2){//单数下标
		if(!a[i])	tot++;
		else{
			if(tot)	ans=1ll*ans*tot%mod;
			tot=0;
		}
	}
	if(tot)	ans=1ll*ans*tot%mod;
	tot=0;
	for(int i=2;i<=n;i+=2){//偶数下标
		if(!a[i])	tot++;
		else{
			if(tot)	ans=1ll*ans*tot%mod;
			tot=0;
		}
	}
	if(tot)	ans=1ll*ans*tot%mod;
	if(qwq){
		for(int i=1;i<=qwq;i++)	ans=1ll*ans*i%mod;//阶乘
	}
	printf("%d\n",ans);
	return 0;
}
/*//一些样例
10 
3 1 3 4 9 8 10 6 2 6 
*/
/*
5
3 2 3 2 4
*/

评论

0 条评论,欢迎与作者交流。

正在加载评论...