题目 | Circle of Mistery
2023牛客暑期多校训练营5
B - Circle of Mistery
https://ac.nowcoder.com/acm/contest/57359/B
题意
考虑长度为 $n$ 的每个排列 $P=\left\{p_1, p_2, \cdots, p_n\right\}$,并给定权值数组 $\{w\}_{i=1}^n$ 和权值阈值 $k$,若 $P$ 中存在一个任意长度的置换环 $\left\{a_1, a_2, \cdots, a_l\right\}$ 满足 $\sum_{i=1}^l w_{a_i} \geq k$,则考虑统计该排列 $P$ 的逆序对数。问符合条件的排列中最小逆序对数目是多少。
$1 \leq n \leq 10^3,-10^6 \leq w_i, k \leq 10^6$ 。
题解
首先,需要考虑选择了 $\left\{a_1, a_2, \cdots, a_x\right\}$ 这些元素构造置换环,剩下的不在置换环中的元素该如何排布。观察可以得到,若要让逆序对最小化,除了构造的环内的元素外,其他的位置的元素我们都让它满足 $p_i=i$ 即处于原位。
然后,需要考虑选择了 $\left\{a_1, a_2, \cdots, a_x\right\}$ 这些元素构造置换环,这些元素该怎么成环。为了保证逆序对最小化,置换环中尽量保持顺序,一种可行的方式是 $p_{a_1}=a_{2},\;p_{a_2}=a_3,\;\dots,p_{a_{x-1}}=a_x,\;p_{a_x}=a_1$,还有一种最大值在第一个的其实是一样的。
现在,根据上面的策略,如果我们选择了 $\left\{a_1, a_2, \cdots, a_x\right\}$ 这些元素构造置换环,那么逆序对的数量就是确定的了。例如下面这个例子,下划线代表选中在置换环中的元素,为了方便,记置换环起点 $i$(本例为 $2$),终点为 $j$(本例为 $8$),环长度 $l$(本例为 $4$):
$$ 1,\underline5,3,4,\underline7,6,\underline8,\underline2,9 $$
那么对于位于最后的最小值(本例中的 $2$),它与置换环中的所有其他元素(本例中的 $5,7,8$)都逆序,个数为 $l-1$。还有处于置换环区间里的满足 $p_i=i$ 的元素,它们每个会产生 $2$ 个逆序,一个是与右侧的最小值(本例中的 $2$),一个是与左侧的错位值(本例中 $5,7$),个数共 $2\cdot(j-i+1-l)$.
综上,给定一个置换环元素集合,它的逆序对数目为:$l-1+2\cdot(j-i+1-l)=2\cdot(j-i)-l+1$.
现在,问题就转换为了选出 $\left\{a_1, a_2, \cdots, a_x\right\}$ 元素,满足 $\sum_{i=1}^l w_{a_i} \geq k$ 的同时使 $2\cdot(j-i)-l+1$ 最小化.
注意到逆序对数目这个式子,对于一个固定区间 $[i,j]$,如果选择的个数 $l$ 越多逆序对越少。由于 $n\leq 10^3$,那么区间 $[i,j]$ 可以用双重循环解决,只需要考虑的是给定一个区间,如何尽量选更多的元素。时间复杂度 $O(n^2)$
考虑贪心思想,要求求和最大的话,对于 $\geq0$ 的元素必选,而对于 $<0$ 的元素,从最大的开始选,一直尝试选到 $k$ 不成立为止。如此就能保证 $[i,j]$ 区间中选出尽量多的元素,然后更新答案即可。
可用一个 multiset
储存元素并维护大小,取的时候从最大开始往小取。时间复杂度 $O(\log n)$
总时间复杂度:$O(n^2\log n)$.
注意,无法取任何数成环是代表不成立,即代码中出现 $l=0$,那是不成立的状态,不能用 $2\cdot(j-i)-l+1$ 把答案更新了,需要特判跳过。
代码
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
void solve()
{
int n, k;
cin >> n >> k;
vector<int> w(n + 10);
for (int i = 1; i <= n; i++)
cin >> w[i];
int ans = INT64_MAX;
for (int i = 1; i <= n; i++)
{
int sum = 0, cnt = 0;
multiset<int> ms;
for (int j = i; j <= n; j++)
{
if (w[j] < 0)
{
ms.insert(w[j]);
}
else
{
sum += w[j];
cnt++;
}
if (sum >= k)
{
int tsum = sum, tcnt = cnt;
for (auto ti = ms.rbegin(); ti != ms.rend(); ++ti)
{
if (tsum + *ti < k)
break;
tsum += *ti;
tcnt++;
}
if (tcnt)
ans = min(ans, 2 * (j - i) - tcnt + 1);
}
}
}
if (ans == INT64_MAX)
cout << -1 << endl;
else
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t = 1;
// cin >> t;
while (t--)
solve();
return 0;
}
本文采用 CC BY-SA 4.0 许可,本文 Markdown 源码:Haotian-BiJi