题解:P9418 [POI 2021/2022 R1] Impreza krasnali
chaeminter2467 · · 题解
前言
模拟赛T1,正解是DP但是很显然本蒟蒻不会,所以搞了个神秘做法。
题意
有一个
特别的,
问你有多少个
题解
先判断掉一些不合法的情况:
- 某个数在
A 之中的出现次数> 2 。 - 两个相同的数的间隔
\not = 2 。 -
现在来依次看看这些不合法的情况。
第一种情况很显然,直接讨论第二种。
如果有两个相同的数
反之不合法。
对于第三种,因为我们可以结合已经确定的
举个例子
P: 1 2 3 4 5
A: ? 1 ? 5 ?
我们发现对于
在这个手摸的过程中我们还发现如果
我们先将可以确定的数确定下来。因为每一次确定的数都可能对前/后的数有影响,所以我们没有办法通过顺序扫一遍完成这个操作。
我这里是使用了两个指针
现在我们再来思考怎么样计算答案。
这里给出一组样例:
10
3 1 3 4 9 8 10 6 2 6
在这个样例中,能确定的数如下
? 3 ? ? ? ? ? ? 6 ?
把这两个序列结合起来看,如图。
一个数箭头指向的两个位置指的是这两个位置中一定要有一个位置的值和它相等。
观察发现,红色箭头与蓝色箭头是相互独立的,所以我们可以分开考虑。
单独拎出来的话长这样。
假设另外加入的数为 x,情况如下
加入的
x,指在A 中没有出现过的数,即没有限制。
x 1 4 8
1 x 4 8
1 4 x 8
1 4 8 x
实际上就是把 x 插入每两个数之间。假设这段
分别单/偶数下标遍历一遍找到每一个
根据乘法原理,每一段
又因为我们要给每一段 x,假设有
答案为
时间复杂度
talk is cheap,show me the code!
#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
*/