题解:AT_arc187_e [ARC187E] Replace Triplets
我们是 CQYC。
教练说这是铜牌题,但实际考下来我们却感觉像银牌题。
可能是因为题解思路的关系吧。
此篇会讲的很详细。
每部分内容用 --- 分隔。
题目这个操作无疑是奇怪的,所以我们定义“反向操作”为将相邻三个数替换为其中的一个,比如
容易发现一个排列
结论
::::info[证明]
注意反向操作从原三个数中选一个替换。
那么它只能添加或修改相邻的数。
因为
::::
代码中先将这种无解判掉,之后的讨论也基于有解。
结论
稍微手玩下即可得。
我们定义:
-
旧数字为
A 中出现的数。 -
新数字为
A 中未出现的数。 -
-
先判断几个特殊情况:
结论
::::info[证明]
考虑正向操作,最后一步一定是将
其中
距离为
::::
先分讨。
若块长形似
否则块长形似
此时设经历若干次正向操作后,块长为
而
此时
但是这些排列并不一定全部可达。
给出形式化证明,具体手玩一下。
::::info[证明]{open}
考虑反向操作。
最后一次正向操作对应的三个位置,反向之后会形成一个三连块。
设
为了还原到
反向移动后
假设总共移动
那么需要满足
根据中国剩余定理,这两个方程有解的充要条件是:
又因为
继续分讨。
任意
现在要求
由于
所以
注意
此时
手玩发现所有排列都可达,
感性理解的话,此时两个新数字对应的长度为
::::
结论
注意此时
:::info[证明]
虽然结论说的有点绕,但
考虑反向操作可以做到什么,依旧有
第一种:合并,
第二种:转换,
为了符合环形顺序,我们将一个三连块移动时,如果遇到旧数字,那么就转换。遇到新数字就合并。
这里的初始三连块是两个新数加一个旧数变的,显然可以找到。
在执行完上面的所有合并与转换后,
并且原顺序不变。
发现此时块长是可以随意调整的。
因为就算初始合并了两个新数,还有
同时,它们还可以让上文的
:::
计数。
先只考虑环形顺序的限制,考虑钦定
还剩下
然后剩下
那么:
展开化简:
此时减去不可得排列。
由结论
考虑此时排列形如
那么等同于有
由隔板法得方案数为
这里我们只统计了旧数字的间隔,还要计数新数字。
依旧钦定
剩下
再钦定
那么:
化简随意。
::::success[Code]
#include <bits/stdc++.h>
#define int long long
#define PII pair < int , int >
#define x first
#define y second
using namespace std ;
bool bbb ;
const int N = 1e7 + 5 , P = 998244353 ;
int n , a[N << 1] ;
bool vis[N] ;
int fac[N] , inv[N] ;
int qpow(int a , int b){
int res = 1 ;
while(b){
if(b & 1) res = res * a % P ;
b >>= 1 , a = a * a % P ;
}
return res ;
}
void init(int n){
fac[0] = 1 ;
for(int i = 1 ; i <= n ; i++) fac[i] = fac[i - 1] * i % P ;
inv[n] = qpow(fac[n] , P - 2) ;
for(int i = n - 1 ; ~i ; i--) inv[i] = inv[i + 1] * (i + 1) % P ;
}
int C(int n , int m){
if(n < 0 || m < 0 || n < m) return 0 ;
return fac[n] * inv[m] % P * inv[n - m] % P ;
}
int f(int x , int m){ return C(x - m - 1 , m - 1) ; }
bool eee ;
signed main(){
// freopen("fx.in" , "r" , stdin) ;
// freopen("fx.out" , "w" , stdout) ;
ios :: sync_with_stdio(0) ;
cin.tie(0) ; cout.tie(0) ;
cerr << "Memory: " << abs((char*)&eee - (char*)&bbb) / 1024.0 / 1024.0 << " MB\n" ;
init(N - 5) ;
cin >> n ;
for(int i = 1 ; i <= n ; i++) cin >> a[i] , a[i + n] = a[i] ;
int x = n , k = 0 , mx = 0 , lat = x ;
while(x > 1 && a[x - 1] == a[n]) -- x ;
for(int i = x ; i < n + x ; i++) if(a[i] != a[i - 1]){
if(vis[a[i]]) return cout << "0\n" , 0 ;
vis[a[i]] = 1 , ++ k ;
mx = max(mx , i - lat) , lat = i ;
}
mx = max(mx , n + x - lat) ;
if(k == n) return cout << "1\n" , 0 ;
if(k == 1) return cout << fac[n] << '\n' , 0 ;
if(mx <= 2) return cout << "0\n" , 0 ;
if(k == n - 2){
if(n == 4) return cout << "24\n" , 0 ;
return cout << 4 * (n - 2) * n % P * ((n > 4) && (n % 2 == 0) ? qpow(2 , P - 2) : 1) % P << '\n' , 0 ;
}
int ans = fac[n] * inv[k - 1] % P , m = n - k ;
(ans -= n * k % P * fac[m - 1] % P * C(k - m - 1 , m - 1) % P - P) %= P ;
cout << ans << '\n' ;
return 0 ;
}
/*
6
1 2 3 3 1 1
5
1 2 1 1 1
5
3 1 3 4 1
4
4 4 4 4
24
10
1 1 1 8 8 8 7 7 7 10
6
1 2 2 2 1 1
*/
::::