题解:P16449 [XJTUPC 2026] 怪商一克拉七鲜鱼丸
Nuclear_Fish_cyq · · 题解
神仙题。人生首黑/ll
我们会发现这两个东西疑似不是很好搞,所以我们考虑转化一下。
从置换组成的环来看,每次交换两个数最多让环数加一,并且对于未排序任意局面都一定可以让环数加一。那么我们考虑到排序好的置换是唯一的
小移的本质是把一个数取出来插进任意位置,那么从前缀最大值个数的方面看,每次旋转一个区间最多让前缀最大值个数加一,对于任意未排序局面也一定可以让个数加一。同理,设置换
然后问题就变成了如何把前缀最大值和不动点数一起计数。
容易设计一个 DP 状态
这两个东西还是很难维护的,我们再转化一下:定义置换
我们可以得出状态
采用滚动数组优化下空间,容易得出递推:
for(int i = n - 1; i >= 1; i--){
memset(f[i % 2], 0, sizeof(f[i % 2]));
for(int j = 1; j <= n; j++){
for(int k = 1; k <= (n - i); k++){
for(int l = 0; l <= (n - i); l++){
if(!f[1 - i % 2][j][k][l]){
continue;
}
for(int qwq = 1; qwq < i; qwq++){//当前a_i的取值
if(qwq < j){//比当前最小值还小,更新最小值和后缀最小值
f[i % 2][qwq][k + 1][l] += f[1 - i % 2][j][k][l];
f[i % 2][qwq][k + 1][l] %= mod;
}
else{//大于最小值
f[i % 2][j][k][l] += f[1 - i % 2][j][k][l];
f[i % 2][j][k][l] %= mod;
}
}
//a_i=i的情况
if(i < j){//比当前最小值还小,更新最小值和后缀最小值
f[i % 2][i][k + 1][l + 1] += f[1 - i % 2][j][k][l];
f[i % 2][i][k + 1][l + 1] %= mod;
}
else{//大于最小值
f[i % 2][j][k][l + 1] += f[1 - i % 2][j][k][l];
f[i % 2][j][k][l + 1] %= mod;
}
}
}
}
}
但是容易发现这是
:::success[代码]
#include <bits/stdc++.h>
#define ll long long
#define db double
#define ull unsigned long long
#define inf INT_MAX
#define linf LLONG_MAX
#define ninf INT_MIN
#define nlinf LLONG_MIN
//#define mod 998244353
#define lwbd lower_bound
#define upbd upper_bound
//#define range
using namespace std;
void read(int &x){
cin >> x;
return;
}
void readll(ll &x){
cin >> x;
return;
}void readdb(db &x){
cin >> x;
return;
}
ll n, mod, f[2][151][151][151], suf[151];
//如果再忘记把题目给的1~n变为0~n-1自罚20仰卧起坐
int main(){
std::ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> mod;
for(int i = 1; i <= n; i++){
f[n % 2][i][1][i == n] = 1;
}
for(int i = n - 1; i >= 1; i--){
memset(f[i % 2], 0, sizeof(f[i % 2]));
for(int k = 1; k <= (n - i); k++){
for(int l = 0; l <= (n - i); l++){
suf[n + 1] = 0;
for(int j = n; j >= 1; j--){
suf[j] = (suf[j + 1] + f[1 - i % 2][j][k][l]) % mod;
}
for(int qwq = 1; qwq < i; qwq++){
f[i % 2][qwq][k + 1][l] += suf[qwq + 1];
f[i % 2][qwq][k + 1][l] %= mod;
}
f[i % 2][i][k + 1][l + 1] += suf[i + 1];
f[i % 2][i][k + 1][l + 1] %= mod;
}
}
for(int j = 1; j <= n; j++){
for(int k = 1; k <= (n - i); k++){
for(int l = 0; l <= (n - i); l++){
if(!f[1 - i % 2][j][k][l]){
continue;
}
ll val = f[1 - i % 2][j][k][l];
if(j <= i - 1){
f[i % 2][j][k][l] += val * (i - j);
f[i % 2][j][k][l] %= mod;
}
if(i >= j){
f[i % 2][j][k][l + 1] += val;
f[i % 2][j][k][l + 1] %= mod;
}
}
}
}
}
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
ll dsa = 0;
for(int k = 1; k <= n; k++){
dsa += f[1][k][n - i][n - j];
}
cout << dsa % mod << " ";
}
cout << endl;
}
return (0 - 0);
}
:::
upd:
观察样例可以发现这个矩阵是对称的,试图证一下。我们构建如下双射:
- 对于排列
P ,拆出所有置换环。例如对于P=(624531) ,得到(16)(2)(435) 。 - 将每个环的最大值拉到最前面,例如此时得到
(61)(2)(543) 。 - 环之间按最大值从小到大排序,然后去除所有中间的括号。得到新置换
F(P)=(254361) 。
这个东西显然是双射,所以有答案的对称性。所以小移小换不用吵架了,好耶!