5.20 模拟赛

A

求出 $n$ 个数都是不到 $m$ 质数,且 Nim 游戏后手必胜的方案数。

$n \le 10^9,m\le 10^6$ 给定模数。

考虑到质数没啥好的性质,看起来也只能够去算这个异或卷积 $n$ 次方。

但是直接写完发现这东西巨慢无比。考虑怎么优化常数。

可以发现最后只需要求 $A[0]$ ,这个数在异或卷积式子可以看出为所有数的和,而 $\text{IFWT}$ 在异或卷积其实就是做一次 $\text{FWT}$ 然后每个数除以 $len$ 。因此我们最后没必要做一次 $\text{IFWT}$ ,直接求所有数的和除以 $len$ 即可。

然后会发现由于模数不固定每个数做一次 $n$ 次幂巨慢无比。但是做一次 $\text{FWT}$ 后每个数的绝对值不会超过之前所有数的和,而之前所有数的和,即质数个数,是 $O(\frac n{\ln n})$ 级别的,对 $1 \sim 2.5\times 10^5$ 预处理 $n$ 次方即可。这样就可以跑得很快了!

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include "iostream"
#include "algorithm"
#include "cstring"
#include "cstdio"
#include "cmath"
#include "vector"
#include "map"
#include "set"
#include "ctime"
using namespace std;
#define MAXN 4194306
//#define int long long
#define rep(i, a, b) for (int i = (a), i##end = (b); i <= i##end; ++i)
#define per(i, a, b) for (int i = (a), i##end = (b); i >= i##end; --i)
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define eb emplace_back
#define vi vector<int>
#define all(x) (x).begin() , (x).end()
#define mem( a ) memset( a , 0 , sizeof a )
typedef long long ll;
int n , m , P , iv2;


int Pow( int x , int a ) {
int ret = 1;
while( a ) {
if( a & 1 ) ret = ret * 1ll * x % P;
x = x * 1ll * x % P , a >>= 1;
}
return ret;
}

inline void FWT(int a[], int len) {
for (int mid = 2; mid <= len; mid <<= 1)
for (int i = 0; i < len; i += mid)
for (int j = i; j < i + (mid >> 1); j++) {
int x = a[j], y = a[j + (mid >> 1)];
a[j] = ( x + y < P ? x + y : x + y - P ), a[j + (mid >> 1)] = ( x < y ? x + P - y : x - y );
}
}

int isp[MAXN] , pri[MAXN / 10] , en;
int pw[300006];
void sieve( ) {
rep( i , 2 , m ) {
if( !isp[i] ) pri[++ en] = i;
for( int j = 1 ; j <= en && i * pri[j] <= m ; ++ j ) {
isp[i * pri[j]] = 1;
if( i % pri[j] == 0 ) break;
}
}
}
int tte = 0;

void solve() {
cin >> n >> m >> P;
if( P == 1 ) { puts("0"); return; }
iv2 = P + 1 >> 1;
sieve( );
rep( i , 2 , m ) isp[i] ^= 1;
int len = 1 , tr = 0;
while( len <= m ) len <<= 1 , ++ tr;
FWT( isp , len );
rep( i , 1 , en ) pw[i] = Pow( i , n );
rep( i , 0 , len - 1 ) {
isp[i] = isp[i] <= en ? pw[isp[i]] : ( n & 1 ) ? P - pw[P - isp[i]] : pw[P - isp[i]];
}
int as = 0;
rep( i , 0 , len - 1 ) as = ( as + isp[i] ) % P;
printf("%d\n",as * 1ll * Pow( iv2 , tr ) % P);
}

signed main() {
freopen("a.in","r",stdin),freopen("a.out","w",stdout);
// int T;cin >> T;while( T-- ) solve();
solve();
}

B

定义零一序列 $S(k) = {\lfloor \frac{i}{k} \rfloor\bmod 2 }$ 。

给定一个序列 $A_{1 \dots n}$ ,你需要把它变成一个常数序列,常数为非负整数,你可以进行的操作是选择一个 $2^k \le m$ ,然后选择 $S(2^k)$ 的一个长为 $n$ 的子序列,然后给 $A$ 的对应位置减去这个序列。求最少需要进行的操作数量。

$n \le 10^7 , m \le \frac{n}{2} , m = 2^t$

我们考虑当 $n > 2m$ 的时候会发生什么。不难发现即使你选择最大的 $2^k$ ,你能做到的影响也是具有循环节的,循环节为 $2m$ 。所以可以直接砍掉 $2m$ 后面的部分,它们必须和前面相同。

然后会发现当你选择一个 $2^t < m$ 的时候,循环节必然是小于等于 $m$ 的,那么它对前 $m$ 和后 $m$ 的影响必然是完全相同的。所以就有了这么一个想法,我们先使用 $S(2^t = m)$ 的子序列操作,用尽量少的操作使得前一半和后一半对应位置完全相同,然后再开始递归用较小的操作。而可以发现这样的操作次数也是最少的,因为操作是无序的,所以可以从大到小来排列操作,在你用完最大的操作的时候需要满足前后对应相等显然是必要的,所以你从大到小都是用最少的操作来达成这个就一定可以得到最优的操作序列。

然后现在只需要考虑怎么用 $S(m)$ 的字序列来操作把前 $m$ 个和后 $m$ 个整成对应位置相等的。直接做看起来比较不行,我们考虑区间加一 / 前缀后缀加一这两个东西如果差分后就可以转成很少的位置做加减。因此可以对前一半和后一半分别差分。然后操作就有两种。如果是 $[l,l+m - 1]$ 减 $1$ ,就是 $l$ 位置的差分减 $1$ ,$l + m$ 位置加 $1$ ,然后给第二个序列差分的开头减 $1$ ,另一种是除 $[l,l+m-1]$ 的位置减 $1$ ,那么就是开头 减 $1$ ,然后给 $l$ 加 $1$ ,给 $l + m$ 减 $1$ 。

所以我们可以用这两种操作把除开前后差分的第一个位置给做成对应位置相同。然后最后可以使用 $11\dots 1100\dots 00$ 和 $00 \dots 0011\dots 11$ 两种操作把两半差分的开头调整好。

这样做复杂度是 $n + \frac n 2 + \cdots$ 的,也就是 $O(n)$ 。

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#include "iostream"
#include "algorithm"
#include "cstring"
#include "cstdio"
#include "cmath"
#include "vector"
#include "map"
#include "set"
#include "queue"
using namespace std;
#define MAXN 10000006
//#define int long long
#define rep(i, a, b) for (int i = (a), i##end = (b); i <= i##end; ++i)
#define per(i, a, b) for (int i = (a), i##end = (b); i >= i##end; --i)
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define eb emplace_back
#define vi vector<int>
#define all(x) (x).begin() , (x).end()
#define mem( a ) memset( a , 0 , sizeof a )
typedef long long ll;
#define fuck return void(puts("-1"))
int n , m;
int A[MAXN];

namespace IO {
const int MAXSIZE = 1 << 20;
char buf[MAXSIZE], *p1, *p2;
#define gc() \
(p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, MAXSIZE, stdin), p1 == p2) \
? EOF \
: *p1++)
inline int rd() {
int x = 0, f = 1;
char c = gc();
while (!isdigit(c)) {
if (c == '-') f = -1;
c = gc();
}
while (isdigit(c)) x = x * 10 + (c ^ 48), c = gc();
return x * f;
}
char pbuf[1 << 20], *pp = pbuf;
inline void push(const char &c) {
if (pp - pbuf == 1 << 20) fwrite(pbuf, 1, 1 << 20, stdout), pp = pbuf;
*pp++ = c;
}
inline void write(int x) {
static int sta[35];
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
while (top) push(sta[--top] + '0');
push( 10 );
}
}

void solve() {
using namespace IO;
n = rd() , m = rd();
rep( i , 0 , n - 1 ) A[i] = rd();
m <<= 1;
rep( i , m , n - 1 ) if( A[i] != A[i - m] ) fuck;
int res = 0;
for( int b = m ; b != 1 ; b >>= 1 ) {
int lt = b >> 1;
per( i , lt - 1 , 1 ) A[i] = A[i] - A[i - 1];
per( i , b - 1 , lt + 1 ) A[i] = A[i] - A[i - 1];
rep( i , 1 , lt - 1 ) {
int t = A[i] - A[i + lt];
if( !t ) continue;
if( t & 1 ) fuck;
t >>= 1;
if( t > 0 ) A[i] -= t , res += t , A[lt] -= t;
else A[i] -= t , res += -t , A[0] += t;
}
int t = A[0] - A[lt];
if( t > 0 ) {
A[0] -= t , res += t;
}
if( t < 0 ) {
res += -t;
}
rep( i , 1 , lt - 1 ) A[i] += A[i - 1];
}
if( A[0] < 0 ) puts("-1");
else cout << res << endl;
}

signed main() {
freopen("b.in","r",stdin) , freopen("b.out","w",stdout);
int T;cin >> T;while( T-- ) solve();
//solve();
}



C

给定一棵启发式的线段树,具体来说,给定 $2n-1$ 个区间,保证这些区间可以形成线段树的结构。然后有两种操作,要么进行一次与 splay 相同的 rotate 操作,要么询问当对区间 $[l,r]$ 进行询问时,会访问多少节点。

首先考虑这个 rotate 操作,手玩一下会发现每次操作只会改变 $O(1)$ 个区间。

然后考虑怎么进行询问。暴力上线段树肯定是不行的。考虑看成一个偏序问题,如果直接算被这个区间包含的所有线段树区间肯定是会挂的。但是线段树有一个优秀性质:子树中叶子个数减去非叶子个数正好是 $1$ ,所以统计极大子树的数量可以直接给叶子权值设为 $1$ 非叶子设为 $-1$ ,然后统计所有被包含的区间和即可。

这题卡空间,但是可以离线下来,跑 CDQ 分治即可。复杂度 $O(n\log^2 n)$ 。我场上就是这个做法。

如果想要变成 $O(n\log n)$ 需要观察性质,可以发现求的其实就是一个点到根路径上的左儿子个数以及右儿子个数。但是其实写出来还不如两个 $\log$ 快。

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#include "iostream"
#include "algorithm"
#include "cstring"
#include "cstdio"
#include "cmath"
#include "vector"
#include "map"
#include "set"
#include "queue"
using namespace std;
#define MAXN 200006
//#define int long long
#define rep(i, a, b) for (int i = (a), i##end = (b); i <= i##end; ++i)
#define per(i, a, b) for (int i = (a), i##end = (b); i >= i##end; --i)
#define pii pair<int,int>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define eb emplace_back
#define vi vector<int>
#define all(x) (x).begin() , (x).end()
#define mem( a ) memset( a , 0 , sizeof a )
typedef long long ll;
int n , m , P , iv2;

vector<pii > sg[MAXN];
int ch[MAXN << 1][2] , fa[MAXN << 1] , L[MAXN << 1] , R[MAXN << 1] , rt;


void pu( int rt ) {
L[rt] = L[ch[rt][0]] , R[rt] = R[ch[rt][1]];
}

void rotate( int u ) {
if( u == rt ) return;
int f = fa[u] , g = fa[f] , w = ch[f][1] == u , k = ch[u][w ^ 1];
if( g ) ch[g][ch[g][1] == f] = u; else rt = u;
ch[u][w ^ 1] = f , ch[f][w] = k;
fa[u] = g , fa[k] = f , fa[f] = u;
pu( f ) , pu( u );
}

namespace bruteforce {

int que( int rt , int l , int r ) {
if( l <= L[rt] && r >= R[rt] ) return 1;
int mid = R[ch[rt][0]] , res = 0;
if( l <= mid ) res += que( ch[rt][0] , l , r );
if( r > mid ) res += que( ch[rt][1] , l , r );
return res;
}

void main() {
rep( i , 1 , m ) {
int op , l , r;
scanf("%d",&op);
if( op == 1 ) {
scanf("%d",&l);
rotate( l );
} else {
scanf("%d%d",&l,&r);
printf("%d\n",que( rt , l , r ));
}
}
}
}

void build( int rt , int l , int r ) {
if( l == r ) return;
int ls = sg[l].back( ).se , m = sg[l].back( ).fi , rs = sg[m + 1].back( ).se;
sg[l].pop_back( ) , sg[m + 1].pop_back( );
ch[rt][0] = ls , ch[rt][1] = rs , fa[ls] = fa[rs] = rt;
build( ls , l , m ) , build( rs , m + 1 , r );
}

struct opt {
int o , l , r;
} Q[MAXN * 3] , tmp[300006];
int cnt = 0;

int T[MAXN];
void add( int x , int c ) {
while( x <= n ) T[x] += c , x += ( x & -x );
}
int sum( int x ) {
int ret = 0;
while( x > 0 ) ret += T[x] , x -= ( x & -x );
return ret;
}

int as[MAXN];

void cdq( int l , int r ) {
// cout << l << ' ' << r << endl;
if( l == r ) return;
int m = l + r >> 1;
cdq( l , m ) , cdq( m + 1 , r );
int lt = l , cn = l - 1;
rep( i , l , m ) tmp[i - l + 1] = Q[i];
rep( i , m + 1 , r ) {
while( lt <= m && tmp[lt - l + 1].l >= Q[i].l ) {
Q[++ cn] = tmp[lt - l + 1];
if( tmp[lt - l + 1].o > 0 ) { ++ lt; continue; }
add( tmp[lt - l + 1].r , tmp[lt - l + 1].o == -2 ? 1 : -1 );
++ lt;
}
Q[++ cn] = Q[i];
if( Q[i].o < 0 ) continue;
as[Q[i].o] += sum( Q[i].r );
}
rep( i , lt , m ) Q[++ cn] = tmp[i - l + 1];
rep( i , l , lt - 1 ) if( tmp[i - l + 1].o < 0 ) add( tmp[i - l + 1].r , -( tmp[i - l + 1].o == -2 ? 1 : -1 ) );
}

void solve() {
cin >> n >> m;
rep( i , 1 , 2 * n - 1 ) {
scanf("%d%d",L + i,R + i);
sg[L[i]].eb( mp( R[i] , i ) );
}
rep( i , 1 , n ) sort( all( sg[i] ) );
rt = sg[1].back().se;
sg[1].pop_back();
build( rt , 1 , n );
if( 0 && n <= 1000 && m <= 1000 ) { bruteforce::main(); return; }
rep( i , 1 , n * 2 - 1 ) if( L[i] != R[i] ) Q[++ cnt] = (opt) { -1 , L[i] , R[i] };
rep( i , 1 , m ) {
int op , l , r;
scanf("%d",&op);
if( op == 1 ) {
scanf("%d",&l);
as[i] = -114514;
if( l == rt ) continue;
Q[++ cnt] = (opt) { -2 , L[l] , R[l] };
int w = ch[fa[l]][1] == l;
rotate( l );
Q[++ cnt] = (opt) { -1 , L[ch[l][w ^ 1]] , R[ch[l][w ^ 1]] };
}
else {
scanf("%d%d",&l,&r);
Q[++ cnt] = (opt) { i , l , r };
as[i] = r - l + 1;
}
}
// cerr << cnt << endl;
cdq( 1 , cnt );
rep( i , 1 , m ) if( as[i] != -114514 ) printf("%d\n",as[i]);
}

signed main() {
freopen("c.in","r",stdin); freopen("c.out","w",stdout);
// int T;cin >> T;while( T-- ) solve();
solve();
}






文章作者: yijan
文章链接: https://yijan.co/2021/05/29/520-mo-ni-sai/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Yijan's Blog