CF1730F Almost Sorted (状压 dp)
状压 dp
题目的描述有点奇怪,实际上就是将 \(p\) 在满足要求的情况下重排列,求下标的逆序对最小值。
根据条件,我们猜测前面的数都不会很大,于是考虑从左到右插入值,若当前插入的值为 \(a_i\),那么由限制条件可知,前面放的数都 \(\le a_i+k\),同时 \(\le a_i\) 的部分一定已经放完了(否则放后面不满足条件)。所以在任何时刻,若当前最右端的值为 \(a_i\),那么一定使用了 \([1,a_i]\),可能使用过 \([a_i+1,a_i+k+1]\)。
考虑 dp。\([1,a_i]\) 的部分作为阶段,状压 \([a_i+1,a_i+k+1]\) 的部分,所以设 \(f_{i,s}\) 表示当前已经放完了 \([1,a_i]\),\([a_i+1,a_i+k+1]\) 的使用情况为 \(s\),下标的最小逆序对数。
转移考虑枚举下一个要放的数,并处理出对应增加的逆序对数,\([1,a_i]\) 的部分可以转移的时候预处理,状态 \(s\) 的部分直接暴力枚举累加即可。
复杂度 \(O(n^2+n2^kk^2)\)。
PS:可以用 BIT 将 \(n^2\) 优化成 \(n\log n\)。
#include <bits/stdc++.h>
#define pii std::pair<int, int>
#define fi first
#define se second
#define pb push_back
using i64 = long long;
using ull = unsigned long long;
const i64 iinf = 0x3f3f3f3f, linf = 0x3f3f3f3f3f3f3f3f;
const int N = 5e3 + 10, K = 9;
int n, k, lim;
int pos[N], sum[N];
int f[N][1 << K];
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cin >> n >> k;
k++;
for(int i = 1; i <= n; i++) {
int p;
std::cin >> p;
pos[p] = i;
}
lim = (1 << k) - 1;
memset(f, 0x3f, sizeof(f));
f[0][0] = 0;
for(int i = 0; i <= n; i++) {
if(i) {
for(int j = 1; j < pos[i]; j++) sum[j]++;
}
for(int s = 0; s <= lim; s++) {
if(f[i][s] == iinf) continue;
for(int j = 1; j <= k && i + j <= n; j++) {
if(!(s >> (j - 1) & 1)) {
int p = i, t = s | (1 << (j - 1)), val = sum[pos[i + j]];
while(t & 1) {
p++;
t >>= 1;
}
for(int x = 1; x <= k && i + x <= n; x++) if((s >> (x - 1) & 1) && pos[i + x] > pos[i + j]) val++;
f[p][t] = std::min(f[p][t], f[i][s] + val);
}
}
}
}
std::cout << f[n][0] << "\n";
return 0;
}