【AtCoder Grand 017C】Snuke and Spells 题解

题目大意

  有 $n$ 个球,每个球上有数字 $a_i$。
  游戏是这样的:若当前还剩 $k$ 个球,就把写有数字 $k$ 的球全部拿走,重复这个过程。你可以修改若干球上的数字,使得最后可以拿完所有的球。求最少修改多少个球。
  并且问题是动态的,有 $m$ 次操作,每次会修改一个球上的数字,每次修改完后问你游戏答案。
  $a_i \leq n \leq 2 \times 10^5,\ \ m \leq 2 \times 10^5$

题解

  日常被欺诈。

  如果数字 $x$ 有 $y$ 个球,我们看成 $(x-y+1, x)$ 这样一条线段。那现在数轴上就有很多条线段,其中没被覆盖的位置数量就是答案。

  如何证明?
  首先这肯定是下界。
  其次我们能构造出一种方案使其可行。若当前数轴上有空位,那就说明别的地方肯定有重复覆盖的位置。重复覆盖要么是线段相交,要么是线段包含,而这两者都必定有线段的开头被重复覆盖,因此我们一定能找到这个开头,挖掉 $1$ 的长度来补数轴上的空位。
  因此这就是最优解。

代码

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
40
41
42
43
44
45
#include<cstdio>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;

const int maxn=2e5+5;

int n,a[maxn],num[maxn];

int ans,cov[maxn];
void ADD(int x)
{
if (x<=0) return;
if (++cov[x]==1) ans++;
}
void DEC(int x)
{
if (x<=0) return;
if (--cov[x]==0) ans--;
}

int m;
int main()
{
scanf("%d %d",&n,&m);
fo(i,1,n)
{
scanf("%d",&a[i]);
num[a[i]]++;
ADD(a[i]-num[a[i]]+1);
}

while (m--)
{
int x,y;
scanf("%d %d",&x,&y);

DEC(a[x]-num[a[x]]+1);
num[a[x]]--;
num[y]++;
ADD(y-num[y]+1);
a[x]=y;

printf("%d\n",n-ans);
}
}