*题解:[ABC134F] Permutation Oddness

题目链接

解析

考虑拆贡献,把 \(\sum_{i = 1}^n\lvert i - p_i\rvert\) 转化为 \(\sum_{i = 1}^n \max(i,p_i) - \min(i,p_i)\),也就是说,一个数 / 位置做正贡献还是做负贡献只取决于它与其对应的位置 / 数的大小关系。这样我们就不用关心具体放在哪个位置了。

故设 \(f_{i,j,k}\) 表示前 \(i\) 个数中有 \(j\) 个匹配了比它大的位置,做 \(k\) 的贡献的方案数,\(g_{i,j,k}\) 表示前 \(i\) 个位置中有 \(j\) 个匹配了比它小的数,做 \(k\) 的贡献时的方案数。但是直接在结尾合并的话可能会出现某段前缀数与位置大小关系不合法的情况。例如对于 \(f_{i,j,k}\) 和 \(g_{i,j',k'}\),\(j'\) 意味着前 \(i\) 个数中必须有至少 \(j'\) 个匹配比它大的位置,此时若 \(j' > j\),则不合法。对此的解决方案是把前 \(i\) 个数中需要匹配比它大的位置但是没有匹配到的个数设到状态里,即设 \(f_{i,j,k}\) 表示考虑了前 \(i\) 个数和位置,向后匹配但是没匹配到的数和位置有 \(j\) 个(匹配时数和位置个数都减一,故二者未被匹配个数始终相等),贡献为 \(k\) 的方案数,于是在转移过程中保证 \(j \ge 0\) 即可。我们在向前匹配时计算贡献,转移方程见代码。

时间复杂度 \(O(n ^ 4)\)。

想到了的地方 / 探索过的思路

以把数字放到指定位置上的想法设计状态。

没想到的地方

拆贡献。

排列问题转化为匹配问题。

代码

/*
容易想到状压,无法通过

那么就需要一种方式记录已经选了哪些位置和数

考虑区间 DP,设 f[i][l][r][j] 表示把 i 开头的连续段放到 [l,r] 中,权值为 j 的方案数
无法合并

或者,把 n 和 k 设到状态里 
设 f[i][j] 表示长度为 i,奇妙度为 j 的排列个数  
f[i][j] = f[i - 1][j]
这是 i 排在最后的情况。
对于 i 插在中间的情况,需要看一段后缀右移带来的贡献
多加一维 k 表示有多少个位置上数大于等于下标

f[i][j][k] += f[p][j1][k1] * f[i - p][j2][k2] 

但是不知道怎么处理 k 的变换。 
*/
#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int N = 50 + 5,M = 50 * 50,MX = 2 * N * N,mod = (int)1e9 + 7;
int f[N][N][MX];
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
//	freopen("in.txt","r",stdin);
	int n,k;
	cin>>n>>k;
	f[0][0][M] = 1;
	for(int i=1;i<=n;i++){
		for(int j=0;j<=i;j++){
			for(int k=0;k<MX;k++){
				if(k + 2 * i < MX && j){//两个都向后匹配 
					f[i][j][k] = (f[i][j][k] + f[i - 1][j - 1][k + 2 * i]) % mod;
				}
				if(k - 2 * i >= 0){//两个都向前匹配 
					f[i][j][k] = (f[i][j][k] + 1ll * f[i - 1][j + 1][k - 2 * i] * (j + 1) % mod * (j + 1)) % mod; 
				}
				f[i][j][k] = (f[i][j][k] + f[i - 1][j][k]) % mod;//当前数字与位置就地匹配 
				f[i][j][k] = (f[i][j][k] + 1ll * f[i - 1][j][k] * j) % mod;//数字向后匹配,位置向前匹配
				f[i][j][k] = (f[i][j][k] + 1ll * f[i - 1][j][k] * j) % mod;//数字向前匹配,位置向后匹配
			}
		}
	}
	cout<<f[n][0][k + M];
	return 0;
}
posted @ 2026-10-13 18:05  yutar  阅读(6)  评论(0)    收藏  举报