ARC128E 题解

First Post:

Last Update:

最近开的题都有些 atcoder,不过相比于看题解都看得一头雾水的 ARC127E,这题反而更加良心。

已经想到了构造可行解的方法,但没有抽象出判断可行解的条件。看来卡在比较弱智的地方确实是个比较常见的事情。希望某天可以踢出这临门一脚。

说到底还是在这方面没什么脑子(

题意:请构造序列 ,满足元素 () 在该序列中恰好出现 次,且任意连续 个元素都互不相同,或报告无解。有解则输出字典序最小的解。

首先考虑如何判断有无解,尝试设计一个贪心。

既然是贪心,那就先满足最紧的限制,即先考虑 较大的元素,那么有一个方法就是隔 个位置放一个。

然后容易想到对 序列每 个分一块,那么最先考虑的元素当然是放在每一块的起始位置。设块数为 ,最后一块长度为 (因为最后一块长度最小)。

那么显然 ,且 。(就是这一步没有抽象出来)。

构造的方法是简单的,按 从大到小考虑每个元素,然后每次选择空位最多的段插进去即可。

然后基于此设计贪心使得字典序最小。

然后可以猜出一个贪心,即每次找到前 个位置中未出现的最小的元素,如果此时 ,那就只能选 的元素,然后处理后面长度 的子问题。

证明正确性,我们就是要证明,只要初始序列有解,每个时刻就都能选一个数出来。

反证法,考察第一个不合法的时刻。

  1. 如果 ,因为无数可选,所以 的所有数都在前面 个位置出现过,而如果将前面 个数加入进来重新分块,那么此时 ,但仍然有 的数,那么会有 ,但在合法条件下,这个策略肯定不会让这种事发生,故矛盾。
  2. 如果 ,此时选数没有限制,无数可选只可能是因为 的数只有至多 个,设当前位置加上后面有 个空位,如果 ,那么显然后面无解,而 保证了后面有解,故矛盾。所以 ,那么此时 ,所有的 都会等于 ,所以 ,矛盾。

综上所述,上述贪心一定可以构造出解,时间复杂度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <bits/stdc++.h>

using namespace std;
const int N = 5e2 + 5,M = 2e5 + 5;
inline int Knum(int n,int k) { return (n + k - 1) / k;}
inline int Klst(int n,int k) { return n % k == 0 ? k : n % k;}
int n,k,len;
int a[N];
int ans[M];
int ton[N];
int main()
{
cin >> n >> k;
for(int i = 1;i <= n;i++) cin >> a[i],len += a[i];
int num = 0;
for(int i = 1;i <= n;i++)
{
if(a[i] > Knum(len,k)) return puts("-1"),0;
num += (a[i] == Knum(len,k));
}
if(num > Klst(len,k)) return puts("-1"),0;
for(int i = 1;i <= len;i++)
{
memset(ton,0,sizeof ton);
for(int j = max(1,i - k + 1);j < i;j++) ton[ans[j]] = true;
num = 0;
for(int j = 1;j <= n;j++) num += (a[j] == Knum(len - i + 1,k));
for(int j = 1;j <= n;j++)
if(!ton[j] && ((num < Klst(len - i + 1,k) && a[j])|| (num == Klst(len - i + 1,k) && a[j] == Knum(len - i + 1,k))))
{
ans[i] = j;
--a[j];
break;
}
}
for(int i = 1;i <= len;i++)
cout << ans[i] << ' ';
return 0;
}