【Samara Farewell Contest 2020 H】Video Reviews - 2 题解

题目大意

  有 nn 个人排队准备录视频,轮到第 ii 个人的时候,如果他被商家钦定,或者排他前面的至少有 aia_i 个人录视频,他就会录视频。问商家至少钦定多少人,使得最终录视频的人数 m\ge m
  mn5×107m \le n \le 5 \times 10^7,由于输入过大,仅输入 a1a_1,接下来给出 kk 段生成器,每段生成 cic_iaa(保证 i=1kci=n1\sum_{i=1}^k c_i=n-1),每个生成器形如 ai=(xai1+y)modza_i=(x a_{i-1}+y) \bmod zzz 为质数。
  k105k \le 10^5
  4s, 64MB

题解

  思考许久,转头发现,这个空间,甚至连 nn 的数组都存不下。。。

  感觉题解给得很妙啊。

  考虑最后一个人,如果 an<ma_n < m,他是一定会录视频的,因此转化为子任务 (n1,m1)(n-1,m-1)
  否则,如果 anma_n \ge mn=mn=m,这个人必须被钦定,不然不合法,因此也转化为子任务 (n1,m1)(n-1,m-1)
  否则,anma_n \ge mn>mn > m,此时要么前 n1n-1 个人已经选出了 mm 个,那么第 nn 人直接扔了就好;要么前 n1n-1 个人只选了 m1m-1 个,想要钦定第 nn 个人,但是钦定前面的人只会使答案更优,所以不会有该情况。也就是说,anma_n \ge mn>mn > m 时,直接忽略最后一个人,转化为子任务 (n1,m)(n-1,m)

  于是这样倒着做一遍就做好了。

  由于空间不允许存下整个数组,可以先求出 ana_n,因为 zz 都是质数,因此可以倒推出所有 aia_izz 不同的生成器之间不能倒推,但是可以开一个 O(k)O(k) 的数组记录每个生成器最后生成的 aa 是多少。
  妙啊

代码

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
46
47
48
49
50
51
52
#include<bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;

typedef long long LL;

const int maxk=1e5+5;

int n,m,k,c[maxk],x[maxk],y[maxk],z[maxk],invx[maxk];
LL a,fn[maxk];

LL Pow(LL x,LL y,LL mo)
{
LL re=1;
for(; y; y>>=1, x=x*x%mo) if (y&1) re=re*x%mo;
return re;
}

int main()
{
scanf("%d %d",&n,&m);
scanf("%lld %d",&a,&k);
fn[0]=a;
fo(i,1,k)
{
scanf("%d %d %d %d",&c[i],&x[i],&y[i],&z[i]);
invx[i]=Pow(x[i],z[i]-2,z[i]);
fo(j,1,c[i]) a=(x[i]*a+y[i])%z[i];
fn[i]=a;
}

int ans=0;
fd(i,k,1)
{
a=fn[i];
fo(j,1,c[i])
{
if (m==0) break;
if (a<m) m--, n--;
else if (n==m) ans++, n--, m--;
else n--;
a=(a-y[i]+z[i])*invx[i]%z[i];
}
}
a=fn[0];
if (a<m) m--, n--;
else if (n==m) ans++, n--, m--;
else n--;

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