Codeforces 2145D Inversion Value of a Permutation 题解 [ 绿 ] [ 背包 ] [ 正难则反 ] [ 构造 ]
Inversion Value of a Permutation:场上被这个 D 卡了 1h,结果往后一看 EF 是板子 /ll/ll/ll。
直接构造不好做,先考虑怎么计算符合条件的区间。
观察逆序对的性质,显然只有极近的逆序对才对计数会有影响。具体而言,如果 \((p_a, p_b)\) 是一个逆序对且满足 \(a + 1 < b\),那么一定存在一个 \(|a - b|\) 更小的逆序对,被夹在 \(a\sim b\) 之间。证明是容易的,考虑枚举中间夹着的那个数,发现它无论比 \(p_a\) 大、夹在 \(p_a, p_b\) 之间、比 \(p_b\) 小,都会产生一个额外的、更小的逆序对。因此所有影响计数的逆序对都是相邻的逆序对。
由上面的结论,我们可以知道,如果任意一个区间,包含了至少一个这种 \(a + 1 = b\) 的相邻逆序对,那么就一定会被计数。但是“至少一个”的条件依然不是很好刻画,于是考虑正难则反的套路,计数被转化为不包含任意一个此类逆序对的区间数。同时可以发现 \(k\) 的限制也能很好地转化成新问题的限制,直接令 \(k'\gets \frac{n(n-1)}{2} - k\) 即可。
把这些相邻的逆序对在序列中标出,容易发现这些逆序对把序列划分成了若干个段(在 \(a\) 与 \(a + 1\) 之间分割)。例如 \(1, 2, 3, 5, 4, 7, 6\) 被分割为了 \(1, 2, 3, 5 ; 4, 7 ; 6\) 三段。而单独的一段产生的贡献显然是 \(\frac{len(len - 1)}{2}\)。
会了对区间计数之后,剩下的就是容易的了。我们考虑用若干个段去拼成整个序列,之后跑一个背包 DP 即可。具体地,定义 \(dp_{i, j}\) 表示用了 \(i\) 个数,区间数为 \(j\) 是否可行,转移的时候记录一下前驱即可。最后从末状态倒过来构造,时间复杂度 \(O(tn^4)\)。需要特判长度为 \(1\) 的段。
实际上本题还有优化空间。注意到长度为 \(\bm1\) 的段贡献为 \(\bm0\),所以对于 \(k\) 固定的情况,只需要保证 \(\bm n\) 最小,后面补一些长度为 \(1\) 的段就能成功构造。所以可以省略背包的第一维,定义 \(dp_i\) 表示当前有 \(i\) 个区间,\(n\) 的最小值,并记录一下转移前驱。这是普通的 01 背包,且与单个测试点中的 \(n, k\) 无关,所以时间复杂度 \(O(tn + n^3)\)。
#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi = pair<int, int>;
const int N = 35, V = 1005, MXN = 30, MXV = 435;
ll n, k, a[N];
ll dp[V], pre[V];
void init()
{
memset(dp, 0x3f, sizeof(dp));
dp[0] = 0;
for(int i = 2; i <= MXN; i++)
{
ll v = i * (i - 1) / 2;
for(int j = v; j <= MXV; j++)
{
if(dp[j] > dp[j - v] + i)
{
dp[j] = dp[j - v] + i;
pre[j] = i;
}
}
}
}
void solve()
{
cin >> n >> k;
k = n * (n - 1) / 2 - k;
if(dp[k] > n)
{
cout << "0\n";
return;
}
if(k == 0)
{
for(int i = n; i >= 1; i--) cout << i << " ";
cout << "\n";
return;
}
int nowx = n, nowy = k;
vector<int> ans;
while(nowy)
{
int tmp = pre[nowy];
nowx -= tmp;
nowy -= tmp * (tmp - 1) / 2;
ans.push_back(tmp);
}
sort(ans.begin(), ans.end());
nowx++;
for(int i = n - nowx + 1, j = n; i <= n; i++, j--) a[i] = j;
for(int i = 1; i <= n - nowx; i++) a[i] = i;
int now = 0;
for(int i = 0; i < ans.size() - 1; i++)
{
now += ans[i];
swap(a[now], a[now + 1]);
}
for(int i = 1; i <= n; i++) cout << a[i] << " ";
cout << "\n";
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
init();
int t;
cin >> t;
while(t--) solve();
return 0;
}