GDKOI2021好题选做
被迫做毒瘤题
当时打GDKOI的时候连前20都没有
wtcl
不得不说,现在看看这些题发现难题的质量还是蛮高的啊
直接开始吧
题目难度从难到易(大概)
二叉树
小多项式快速幂板子题
题解:
浅谈小多项式快速幂
这里写过了
堆
对于一个小根堆,有两种操作
上滤:不断和父亲节点比较,小就交换
下滤:不断和儿子节点中的较小者交换
对于任意一颗完全二叉树,自底向上对每个元素进行下滤,就可以得到一个
O
(
n
)
O(n)
O(n)的建堆方法(考虑每一层的个数*往下走的次数求和)
给出一个
A
[
1...
n
]
A[1...n]
A[1...n],保证为
1...
n
1...n
1...n的排列
每次询问一个区间
[
l
,
r
]
[l,r]
[l,r],把
A
[
l
.
.
.
r
]
A[l...r]
A[l...r]抽出来按照上面的方式建堆,
- 值 x x x所在的节点编号
- 节点
x
x
x上的值
n , q < = 1 0 5 n,q<=10^5 n,q<=105
题解
乍一看没法做啊
肯定是要把下滤转换为上滤
先给出一个结论:上面的那种建堆方式等价于从小到大对每个元素进行上滤
打个表就知道了
其实是不会证明
这样有什么好处呢?
可以发现,每次上滤后,堆被分成了独立的两部分
于是乎可以先对这段区间建一颗完全二叉树(一层一层按顺序放)
然后每次找到子树的最小值,一路上滤到根
往要去的那边走
然后就没了
可以用线段树模拟这个过程
考虑如何找子树的最小值,可以一层一层找,因为对于堆上的一层,对应在原序列(线段树)上是编号连续的一段
考虑时间复杂度
对于每次上滤
- 要查询一次,每次查询有 l o g log log层,每层找一次最小值,所以是 l o g 2 log^2 log2的
- 查询后要把那个最小值一路交换到根,单次修改 l o g log log,要交换 l o g log log次,所以是 l o g 2 log^2 log2的
然后一共
l
o
g
log
log次上滤,所以线段树暴力做时间复杂度是
O
(
q
l
o
g
3
n
)
O(qlog^3n)
O(qlog3n)的
看起来1e5完全不能过
可以用四毛子或其他玄学性质去掉一个
l
o
g
log
log
但我太菜了不会
于是乎搞了一发
z
k
w
zkw
zkw线段树莽过去了
短的一批好嘛
code:
#include<bits/stdc++.h>
#define N 200050
using namespace std;
int read() {
register int x = 0; char ch = getchar();
for(;!isdigit(ch);) ch = getchar();
for(;isdigit(ch);) x = (x << 3) + (x << 1) + (ch - '0'), ch = getchar();
return x;
}
const int M = 1 << 17;
struct zkw {
int mi[M << 1 | 1];
void init() { memset(mi, 0x3f, sizeof mi); }
void update(int rt) { mi[rt] = min(mi[rt << 1], mi[rt << 1 | 1]); }
void insert(int x, int o) { x += M;
if(mi[x] == o) return; mi[x] = o;
for(x >>= 1; x; x >>= 1) update(x);
}
int query(int l, int r) {
int ans = 1e9;
for(l += M - 1, r += M + 1; l ^ r ^ 1; l >>= 1, r >>= 1) {
if(l & 1 ^ 1) ans = min(ans, mi[l ^ 1]);
if(r & 1) ans = min(ans, mi[r ^ 1]);
} return ans;
}
} t;
int query(int rt, int L, int R) {
int l = rt, r = rt, ret = 1e9;
while(L + l <= R) {
int ql = L + l, qr = min(R, r + L);
ret = min(ret, t.query(ql, qr));
l <<= 1, r = r << 1 | 1;
}
return ret;
}
int id[N], a[N], b[N], n, m, lg[N], c[N];
vector<int> sta;
void up(int L, int x, int y) {
while(y != x) {// printf(" %d %d\n", x, y);
int val = b[L + (y >> 1)];
id[val] = L + y; b[L + y] = val; sta.push_back(L + y);
t.insert(L + y, val); y >>= 1;
} sta.push_back(L + y);
}
void clear() {
for(int i = 0; i < sta.size(); i ++) {
int x = sta[i]; b[x] = a[x];
id[b[x]] = x; t.insert(x, a[x]);
}
sta.clear();
}
int equall(int x, int y) {
int k = lg[y] - lg[x];
return (y >> k) == x;
}
int Qa(int l, int r, int x) {
clear(); int gs = 0;
while(x > 1) c[++ gs] = x & 1, x >>= 1; x = 1;
int ans = 0;
for(int i = gs; i >= 0; i --) {
ans = query(x, l - 1, r);// printf("**%d\n", ans);
up(l - 1, x, id[ans] - l + 1);
x = x << 1 | c[i];
}
return ans;
}
int Qb(int l, int r, int o) {
clear();
int x = 1;
while(1) {
int val = query(x, l - 1, r);
if(val == o) break; //printf(" %d %d\n", x, val);
up(l - 1, x, id[val] - l + 1);
x = x << 1;
if(!equall(x, id[o] - l + 1)) x ++;
}
return x;
}
int main() {
n = read(), m = read(); t.init();
for(int i = 2; i <= n; i ++) lg[i] = lg[i >> 1] + 1;
for(int i = 1; i <= n; i ++)
id[b[i] = a[i] = read()] = i, t.insert(i, a[i]);
while(m --) {
int o = read(), l = read(), r = read(), x = read();
// printf(" %d %d %d %d\n", o, l, r, x);
if(o == 1) printf("%d\n", Qa(l, r, x));
else printf("%d\n", Qb(l, r, x));
}
return 0;
}
生命游戏
用来纪念因新冠肺炎失去的生命游戏发明者John Horton Conway教授
在一个无限大的网格图上,每个格子有一个细胞,每一时刻,对于任意一个细胞,它下一个时刻存活当且仅当现在这个时刻周围四个细胞存活数为奇数
一开始给出n个存活细胞的位置,求时刻 L R L~R L R存活的细胞个数之和
题解
这神仙题啊
转换来转换去把我转换吐了
大家好!我是转转!!!
对于每个点,下一时刻它的值就是上下左右的异或和
中间那个下一时刻的值就是1234这四个位置的异或和
下面要分几步转换
Step 1
然而我考场上只想到第一步
wtcl
首先看上面那个图
把头往右歪一下发现是个正方形
Step 2
这一步就不会了
考虑转换一下,本来是对于一个点,每次它的值是左上,右上,左下,右下一格的异或和
改成 正上,正右一格,右上两格,和自己的值的异或和
就是对于4,它的值为val[4] ^ val[3] ^ val[1] ^ val[2]
然后再把整个图往右上移动一格就可以得到和原来等价的图
然后你可以发现,对这个图染色,不同颜色的已经互不干扰了
把不同颜色的分离出来后,曼哈顿距离转切比雪夫距离,构成新图
此时每个格子下一个时刻的值为上,右上,右和自身的值的异或
Step 3
如何判断某个位置
O
(
X
,
Y
)
O(X,Y)
O(X,Y)在时刻
T
T
T是否为1
异或可以看作在mod 2意义下的加法
所以可以分别考虑每个初始为1的细胞对这个位置的贡献
对于某个初始细胞,点为
(
x
0
,
y
0
)
(x_0,y_0)
(x0,y0)
在
T
T
T时刻有贡献当且仅当从
(
0
,
0
)
(0,0)
(0,0)到
(
X
−
x
0
,
Y
−
y
0
)
(X-x_0,Y-y_0)
(X−x0,Y−y0)长度为
T
T
T的路径数
m
o
d
2
=
1
\mod 2 = 1
mod2=1
方案数即
为
(
T
X
−
x
0
)
(
T
Y
−
y
0
)
为\large \tbinom{T}{X-x_0} \tbinom{T}{Y-y_0}
为(X−x0T)(Y−y0T) mod 2
根据
L
u
c
a
s
Lucas
Lucas定理,
(
X
−
x
0
)
,
(
Y
−
y
0
)
(X-x_0),(Y-y_0)
(X−x0),(Y−y0)均为T(在二进制下的)子集时才为奇数
L
u
c
a
s
Lucas
Lucas相当于是按
P
P
P进制拆分
Step 4
考虑如何计数
当
n
=
1
n=1
n=1的时候时刻
T
T
T活着的细胞为
4
b
i
t
c
o
u
n
t
(
T
)
4^{bitcount(T)}
4bitcount(T)个
当
n
>
1
n>1
n>1时考虑容斥
枚举初始细胞集合
S
S
S,计算对于
S
S
S中的细胞,都是活的的位置个数,即为
f
(
S
)
f(S)
f(S)
然后对
f
(
S
)
f(S)
f(S)进行容斥
举个简单的例子
只有两个初始细胞
容斥为:对于第一个初始细胞是活的+对于第二个初始细胞是活的-2*对于两个初始细胞都是活的
然后对
f
f
f做高维差分,把
∣
S
∣
m
o
d
2
=
1
|S|\mod 2=1
∣S∣mod2=1的求和就是答案
或者直接推一把容斥系数
A
N
S
=
∑
(
−
2
)
∣
S
∣
−
1
f
(
S
)
ANS = \sum(-2)^{|S|-1}f(S)
ANS=∑(−2)∣S∣−1f(S)
Step 5
考虑如何计算
f
(
S
)
f(S)
f(S)
先考虑对于一个固定的时刻
T
T
T
相当于给出若干个
(
X
i
,
Y
i
)
(X_i,Y_i)
(Xi,Yi)(初始细胞位置), 要求
x
>
=
X
m
a
x
,
y
>
=
Y
m
a
x
x >= X_{max}, y >= Y_{max}
x>=Xmax,y>=Ymax且
(
x
−
X
i
)
,
(
y
−
Y
i
)
都
是
T
(
在
二
进
制
下
的
)
的
子
集
,
的
(
x
,
y
)
个
数
(x-X_i),(y-Y_i)都是T(在二进制下的)的子集,的(x,y)个数
(x−Xi),(y−Yi)都是T(在二进制下的)的子集,的(x,y)个数
减法不太好做,考虑转换一下坐标 (转转转)
令
x
=
x
−
X
m
a
x
,
X
i
=
X
m
a
x
−
X
i
x=x-X_{max}, X_i=X_{max}-X_i
x=x−Xmax,Xi=Xmax−Xi
原来的
(
x
−
X
i
)
(x-X_i)
(x−Xi) 相当于现在的
(
x
+
X
i
)
(x+X_i)
(x+Xi)
然后
y
y
y同理,就转换成了加法
约束变为:
(
x
,
y
)
中
x
>
=
0
,
y
>
=
0
,
且
(
x
+
X
i
)
,
(
y
+
Y
i
)
均
为
T
(
在
二
进
制
下
)
的
子
集
(x,y)中 x>=0,y>=0,且(x+X_i),(y+Y_i)均为T(在二进制下)的子集
(x,y)中x>=0,y>=0,且(x+Xi),(y+Yi)均为T(在二进制下)的子集
求
(
x
,
y
)
(x,y)
(x,y)个数
注意到一点,
x
,
y
x,y
x,y是独立的
考虑对
x
x
x进行数位DP(玄妙)
由于 x + X i x+X_i x+Xi可能进位,所以由低到高转移比较合适
我们需要提前处理出来当前位,有几个 X k X_{k} Xk与 x x x相加会进位,记为 c c c,可以帮助下面的转移
从第i位进位到第i+1位,肯定是后i位最大的若干个 X k X_{k} Xk
设
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示
x
x
x的前i位,第
i
i
i位有
j
j
j个进位,满足条件的数量,用上面那个辅助转移即可
Step 6
考虑计算
T
<
=
R
T<=R
T<=R的总和
注意到
T
T
T的限制也和二进制位有关
将
T
,
x
,
y
T,x,y
T,x,y一起进行数位DP(三个数一起数位DP,我的头飞了)
设
D
P
[
i
]
[
j
]
[
k
]
[
0
/
1
]
表
示
从
低
到
高
前
i
位
,
有
j
个
X
产
生
进
位
,
k
个
Y
产
生
进
位
,
是
否
有
超
过
上
界
,
的
所
有
(
T
,
x
,
y
)
的
个
数
设DP[i][j][k][0/1]表示从低到高前i位,有j个X产生进位,k个Y产生进位,是否有超过上界,的所有(T,x,y)的个数
设DP[i][j][k][0/1]表示从低到高前i位,有j个X产生进位,k个Y产生进位,是否有超过上界,的所有(T,x,y)的个数
转移的时候枚举
T
,
x
,
y
T,x,y
T,x,y的下一位填啥
最后答案就是
D
P
[
l
e
n
]
[
0
]
[
0
]
[
0
]
DP[len][0][0][0]
DP[len][0][0][0]
时间复杂度
O
(
2
n
n
2
l
o
g
2
n
)
O(2^nn^2log_2n)
O(2nn2log2n)
code:
#include<bits/stdc++.h>
#define ll long long
#define mod 998244353
using namespace std;
const int INF = 1e9;
int xk[18], ok[18], id[18];
int cmp(int x, int y) {
return xk[x] > xk[y];
}
void pre(int n, int *x, int c[35][18][2][2]) {
for(int i = 1; i <= n; i ++) id[i] = i;
for(int t = 0; t <= 30; t ++) {
for(int i = 1; i <= n; i ++)
ok[i] = 0, xk[i] = x[i] & ((1 << t + 1) - 1);
for(int i = 0; i <= n; i ++) { // i ge jin wei
ok[id[i]] = 1;
for(int t0 = 0; t0 <= 1; t0 ++) // next t is t0
for(int x0 = 0; x0 <= 1; x0 ++) { // next x is x0
c[t][i][t0][x0] = 0;
for(int k = 1; k <= n; k ++) {
int val = xk[k] + (ok[k] << t) + (x0 << t);
if((val & (1 << t)) && ! t0) {c[t][i][t0][x0] = -1; break;}
if(val & (1 << t + 1)) c[t][i][t0][x0] ++;//, printf(" %d %d %d %d\n", t, i, t0, x0);
}
}
}
sort(id + 1, id + 1 + n, cmp);
}
}
int L, R, tx[35][18][2][2], ty[35][18][2][2], x[17], y[17], gs;
void init() {
int mx = - INF, my = - INF;
for(int i = 1; i <= gs; i ++) mx = max(mx, x[i]), my = max(my, y[i]);
for(int i = 1; i <= gs; i ++) x[i] = mx - x[i], y[i] = my - y[i];
pre(gs, x, tx), pre(gs, y, ty);
}
ll dp[35][18][18][2]; // T di i wei x jin j wei y jin k wei shi fou chao guo shang jie
int calcc(int T) { if(T < 0) return 0;
memset(dp, 0, sizeof dp);
dp[0][0][0][0] = 1;
for(int i = 0; i <= 30; i ++)
for(int j = 0; j <= gs; j ++)
for(int k = 0; k <= gs; k ++)
for(int l = 0; l <= 1; l ++) if(dp[i][j][k][l]) { dp[i][j][k][l] %= mod;
for(int t = 0; t <= 1; t ++)
for(int x = 0; x <= 1; x ++)
for(int y = 0; y <= 1; y ++) {
int jx = tx[i][j][t][x], jy = ty[i][k][t][y];
if(jx == -1 || jy == -1) continue;
dp[i + 1][jx][jy][l? t >= (T >> i & 1) : t > (T >> i & 1)] += dp[i][j][k][l];
}
}
return dp[31][0][0][0] % mod;
}
int calc() {
init();
return (calcc(R) - calcc(L - 1) + mod) % mod;
}
int pw[18], xx[18], yy[18], n;
int solve() {
pw[0] = 1;
for(int i = 1; i <= 15; i ++) pw[i] = 1ll * pw[i - 1] * (mod - 2) % mod;
int ans = 0;
// for(int i = 1; i <= n; i ++) printf(" %d %d\n", xx[i], yy[i]);
for(int S = 1; S < (1 << n); S ++) {
gs = 0;
for(int i = 1; i <= n; i ++)
if((S >> (i - 1)) & 1) x[++ gs] = xx[i], y[gs] = yy[i];
ans = (ans + 1ll * pw[gs - 1] * calc() % mod) % mod;
// printf("%d %d (%d %d)\n", calc(), gs, x[1], y[1]);
}
return ans;
}
int nn, lx[18], ly[18], type;
int main() {
scanf("%d%d%d%d", &nn, &L, &R, &type);
for(int i = 1; i <= nn; i ++) scanf("%d%d", &lx[i], &ly[i]);
for(int i = 1; i <= nn; i ++) {
if((lx[i] + ly[i]) & 1) {
xx[++ n] = (lx[i] + ly[i] + 1) / 2;
yy[n] = (ly[i] - lx[i] + 1) / 2;
}
}
int ans = solve(); n = 0; //printf("%d ", ans);
for(int i = 1; i <= nn; i ++) {
if((lx[i] + ly[i] + 1) & 1) {
xx[++ n] = (lx[i] + ly[i]) / 2;
yy[n] = (ly[i] - lx[i]) / 2;
}
}
ans = (ans + solve()) % mod;
printf("%d", ans);
return 0;
}
/*
3 0 3 2
0 -1
0 0
2 2
*/
还是做题太少