题面
[COCI2014-2015#2] STUDENTSKO
题目描述
一年一度的萨格勒布大学学生乒乓球团体赛将于下周六举行!每队由 k k k 名学生组成。 n n n 个兴奋的学生们正在排队等候登记。Krešo 在登记处工作。他真的不想做他的工作,所以他决定不让学生选择团队。他决定第一组将由第一个排队的 k k k 名学生组成,第二组由下面的 k k k 名学生组成,第三组由后面的 k k k 名学生组成,以此类推。( n ≡ 0 ( m o d k ) n\equiv 0\pmod k n≡0(modk),因此没有人剩余。)
Ante 用一个整数来估计每个玩家的技能。他希望第一队拥有最弱的 k k k 个球员,第二队拥有第二弱的 k k k 个球员,依此类推,最后一队拥有最强的 k k k 个球员。
Krešo 刚刚休息了一下,Ante 决定转移排队的学生以达到自己的目标。他转移学生的方式是告诉一个学生从队列中走出来,在另一个学生后面排队,或者走到队列的前面。每转移一个学生花费一分钟。Krešo 有可能随时从休息中回来,所以 Ante 需要尽快实现他的目标。帮助 Ante 确定实现目标所需的最少分钟数。
输入格式
第一行输入包含整数 n n n 和 k k k,满足 n m o d k = 0 n\bmod k=0 nmodk=0。
第二行包含 n n n 个空格分隔的整数 v i v_i vi,表示第 i i i 个站在队列中的玩家技能的水平。
所有参赛者都有不同水平的技能。
输出格式
仅一行,即转移学生所用的最短分钟数。
样例 #1
样例输入 #1
4 1
9 12 5 13
样例输出 #1
1
样例 #2
样例输入 #2
6 2
16 2 1 7 5 10
样例输出 #2
1
样例 #3
样例输入 #3
6 3
7 9 8 3 6 5
样例输出 #3
3
提示
样例 3 说明
Ante 应该将技能等级为 5 , 6 5,6 5,6 和 3 3 3 的学生移到队列的前面,花了三分钟。
数据范围与约定
- 对于 30 % 30\% 30% 的数据,有 1 ≤ n ≤ 20 1\le n\le 20 1≤n≤20。
- 对于 100 % 100\% 100% 的数据,有 1 ≤ n ≤ k ≤ 5 × 1 0 3 1\le n\le k\le 5\times 10^3 1≤n≤k≤5×103。
对于所有合法的 v i v_i vi,都有 1 ≤ v i ≤ 1 0 9 1\le v_i\le 10^9 1≤vi≤109。
说明
题目译自 COCI2014-2015 CONTEST #2 T3 STUDENTSKO。
题解
思路
- 因为 Ante 希望第一队拥有最弱的 k k k 个球员,第二队拥有第二弱的 k k k 个球员,依此类推,最后一队拥有最强的 k k k 个球员。所以应先将球员按能力从小到大排序。
- 此时球员的顺序是最理想的状态,将球员排序后,打上球队的标记,并还原球员的顺序。
- 因为原来的顺序不一定是最理想的顺序,所以要让移动次数最小,符合队伍排序的顺序最长。而这个符合队伍排序的顺序就是队伍编号的最长不降子序列。
代码
蒟蒻还不会 O ( n log 2 n ) O\left ( n\log_{2}{n} \right ) O(nlog2n),只会 O ( n 2 ) O\left ( n^2 \right ) O(n2)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
struct stu {
int a, id, team;
} s[5005];
int n, k;
bool cmp1(stu A, stu B) {
return A.a < B.a;
}
bool cmp2(stu A, stu B) {
return A.id < B.id;
}
int f() {//最长不降子序列,即符合队伍排序的顺序
int dp[5005], ans = -1e9;
for (int i = 1; i <= n; i++) {
dp[i] = 1;
for (int j = 1; j < i; j++)
if (s[i].team >= s[j].team)
dp[i] = max(dp[i], dp[j] + 1);
}
for (int i = 1; i <= n; i++) ans = max(ans, dp[i]);
return ans;
}
signed main() {
cin.tie(0), cout.tie(0);
cin >> n >> k;
for (int i = 1; i <= n; i++) cin >> s[i].a;//输入
for (int i = 1; i <= n; i++) s[i].id = i;//编号,为后面还原做标记
sort(s + 1, s + 1 + n, cmp1);//按能力排序
for (int i = 1; i <= n; i++) s[i].team = (i + k - 1) / k;//打上队伍标号
sort(s + 1, s + 1 + n, cmp2);//还原
cout << n - f()<<"\n";
return 0;
}