第
i
i
i 行障碍物的位置为
a
i
a_i
ai 把一个障碍物水平方向移动一格花费
v
v
v,垂直移动一格花费
u
u
u 问你能从
(
1
,
0
)
(1,0)
(1,0) 移动到
(
n
,
1
0
6
+
1
)
(n,10^6+1)
(n,106+1) 的最小障碍物移动花费为多少。
【范围】
2
≤
n
≤
100
2\le n\le 100
2≤n≤100
1
≤
a
i
≤
1
0
6
1\le a_i\le 10^6
1≤ai≤106
【思路】赛内 经过分类讨论 (wa过一遍),能得出比较简洁的结论: (1)如果
a
b
s
(
a
i
−
a
i
−
1
)
>
1
abs(a_i-a_{i-1})>1
abs(ai−ai−1)>1,表示路是通的,花费为
0
0
0 (2)如果每个障碍物的纵坐标都相同,那么花费为
min
(
2
v
,
u
+
v
)
\min(2v,u+v)
min(2v,u+v) (3)否则,花费为
min
(
u
,
v
)
\min(u,v)
min(u,v) 第一个结论易得。 第二个结论,只能把其中一行水平方向移动两次,或者横一次竖一次即可。 第三个结论,那么一定有一个障碍物是凸出来的,只用水平一次或者竖直一次即可。
【代码】 略
C: Pekora and Trampoline |
d
p
dp
dp
【题意】
n
n
n 个蹦床,第
i
i
i 个的强度为
S
i
S_i
Si 小P 每轮可以自己选择一个位置开始蹦。 如果目前位置的蹦床强度为
S
S
S,那么他蹦到的下一个位置为
i
+
S
i+S
i+S,然后
S
S
S 变成
max
(
1
,
S
−
1
)
\max(1,S-1)
max(1,S−1) 只有他的位置
>
n
>n
>n ,那么他才能停下来,开始下一轮的蹦床。 问你:最少几轮,才可以使得所有蹦床的强度都为
1
1
1
【范围】
1
≤
n
≤
5000
1\le n\le 5000
1≤n≤5000
1
≤
S
i
≤
1
0
9
1\le S_i\le 10^9
1≤Si≤109
【思路】赛内 容易得到,第一个强度非
1
1
1 的蹦床一定要从该位置开始蹦(当然也可以从前面开始蹦,但是前面的强度都是
1
1
1,于是和该位置开始蹦等价)。 该位置需要至少蹦
S
i
−
1
S_i-1
Si−1才能把该位置的强度变为
1
1
1。 但是由于
S
i
S_i
Si 可以很大,大到
1
e
9
1e9
1e9,不可能一次一次去模拟蹦。我们想到了
d
p
dp
dp 设
d
p
[
i
]
dp[i]
dp[i] 表示蹦到该位置的次数。 于是对于每一个位置,我们需要多蹦的次数为:
max
(
0
,
S
i
−
1
−
d
p
[
i
]
)
\max(0,S_i-1-dp[i])
max(0,Si−1−dp[i]) 然后我们开始蹦。如果
S
i
=
3
S_i=3
Si=3,也就是我们要让
d
p
[
i
+
3
]
+
+
,
d
p
[
i
+
2
]
+
+
dp[i+3]++,dp[i+2]++
dp[i+3]++,dp[i+2]++ 那么剩下的次数?当然是都给
d
p
[
i
+
1
]
dp[i+1]
dp[i+1] 了。 还有一个注意点,就是如果
S
i
S_i
Si 特别大,大多数都蹦到外面去了,我们和边界取小即可。
【代码】 时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
46
/
2000
M
s
46/2000Ms
46/2000Ms
【题意】 有无穷个顶点,第
i
i
i 个点的值为
i
i
i
u
u
u 向
u
+
v
u+v
u+v 连一条有向边 当且仅当
u
&
v
=
v
u\&v=v
u&v=v 有
q
q
q 个询问,每次问你
u
i
u_i
ui 是否能通过有向边走到
v
i
v_i
vi
【范围】
1
≤
q
≤
1
0
5
1\le q\le 10^5
1≤q≤105
1
≤
u
i
,
v
i
<
2
0
30
1\le u_i,v_i<20^{30}
1≤ui,vi<2030
【思路】赛内 按照连边规则,我们容易想到直接打个表,但是
i
i
i 能走到的顶点集合有些比较复杂。 比较容易得到的是:如果
v
=
u
v=u
v=u,那么
u
u
u 能连向
2
u
2u
2u,同理
2
u
2u
2u 能连向
4
u
4u
4u ,也就是
u
u
u 能走到
2
k
⋅
u
2^k\cdot u
2k⋅u,其中
k
∈
N
k\in \mathbb{N}
k∈N 但是有一些连通比较奇怪。比如
5
5
5 能走到
18
18
18,我们看一下他们的二进制表示:
5
0010
1
b
9
0100
1
b
18
1001
0
b
\begin{matrix} 5&00101_b\\ 9&01001_b\\ 18&10010_b \end{matrix}
591800101b01001b10010b 我们选择
10
1
b
101_b
101b 的子集
10
0
b
100_b
100b,然后给累加其中,得到了
100
1
b
1001_b
1001b 然后每一位都左移一格,得到了
1001
0
b
10010_b
10010b 我们感觉,好像把一些
1
1
1 向左移动就是能走通的情况。 继续看这个例子:
5
010
1
b
6
011
0
b
8
100
0
b
\begin{matrix} 5&0101_b\\ 6&0110_b\\ 8&1000_b \end{matrix}
5680101b0110b1000b 这里,
5
→
6
5\rightarrow6
5→6就是把末尾
1
1
1 向左移动一格的例子。 对于
6
→
8
6\rightarrow8
6→8,我们选择
011
0
b
0110_b
0110b 的子集
1
0
b
10_b
10b,然后给累加其中,得到了
100
0
b
1000_b
1000b 也就是说,如果有多个
1
1
1 在一起,我们左移的抉择是可以把多余的
1
1
1 给吞掉的。 于是,题目就转变为:
u
u
u 的二进制表示和
v
v
v 的二进制表示给定,能否只运用以上两条规则将
u
u
u 变成
v
v
v。 我们只要从右往左计算可用的
1
1
1 的数量。如果
u
u
u 的末
i
i
i 位为
1
1
1 ,那么可用
1
1
1 的数量增加。如果
v
v
v 的末
i
i
i 位为
1
1
1,那么可用
1
1
1 的数量减少。 如果全部处理完,每一步可用
1
1
1 的数量非负,那么即是可以走通的。否则无法走通。
【代码】 时间复杂度:
O
(
30
q
)
O(30q)
O(30q)
61
/
3000
M
s
61/3000Ms
61/3000Ms
/*
_ __ __ _ _
| | \ \ / / | | (_)
| |__ _ _ \ V /__ _ _ __ | | ___ _
| '_ \| | | | \ // _` | '_ \| | / _ \ |
| |_) | |_| | | | (_| | | | | |___| __/ |
|_.__/ \__, | \_/\__,_|_| |_\_____/\___|_|
__/ |
|___/
*/int aa[MAX],bb[MAX];intmain(){int T =read();while(T--){int s1 =0,s2 =0;
ll a =read_ll();
ll b =read_ll();if(a > b){puts("NO");continue;}if(a == b){puts("YES");continue;}for(int i =1;i <=30;++i){
aa[i]= bb[i]=0;}while(a){if(a&1)aa[++s1]=1;else aa[++s1]=0;
a /=2;}while(b){if(b&1)bb[++s2]=1;else bb[++s2]=0;
b /=2;}int shu =0;
bool can = true;for(int i =1;i <= s2;++i){
shu += aa[i];
shu -= bb[i];if(shu <0){
can = false;break;}}puts(can ?"YES":"NO");}return0;}
E:Fib-tree | 树 + 搜索
【题意】
F
k
F_k
Fk 表示第
k
k
k 个斐波那契数。 一颗树是斐波那契树,首先该数的节点个数是某个斐波那契数。其次,还得满足一下两个条件之一: (1)该树的节点个数为
1
1
1。 (2)通过割断一条边,把它分为两个子树,这两个子树都是斐波那契树。 给你一棵树,问你该树是不是斐波那契树。
【思路】参考题解 如果该树的节点个数为
F
k
F_k
Fk,那么你割边的唯一合法要求是:割完后两边的子树节点个数为
F
k
−
1
F_{k-1}
Fk−1 与
F
k
−
2
F_{k-2}
Fk−2 然后一直递归割下去,直到某次无法割或者节点个数为
1
1
1 返回即可。 正确性可以参考
t
u
t
o
r
i
a
l
tutorial
tutorial 那么问题来了,就是代码怎么敲了。这里需要多次计算子树大小
s
z
sz
sz ,如果按一般
d
f
s
dfs
dfs 的做法,去暂时保存
s
z
[
]
sz[]
sz[] 数组,那么会超时…我这里使用
s
z
[
x
]
[
c
e
]
sz[x][ce]
sz[x][ce] 表示第
c
e
ce
ce 次不同迭代时候的
x
x
x 的子树大小(包括自己)。 我们目前的树的大小为
F
k
F_k
Fk,我们搜索到某一个
x
x
x 的儿子节点
y
y
y 满足
∣
y
∣
=
F
k
−
1
|y|=F_{k-1}
∣y∣=Fk−1 或
∣
y
∣
=
F
k
−
2
|y|=F_{k-2}
∣y∣=Fk−2,那么我们就把
x
→
y
x\rightarrow y
x→y 的边给割掉,然后两边分别递归。
【代码】 时间复杂度:
O
(
n
log
φ
n
)
O(n\log_{\varphi} n)
O(nlogφn) 空间复杂度:
O
(
n
log
φ
n
)
O(n\log_{\varphi} n)
O(nlogφn)
218
/
1000
M
s
218/1000Ms
218/1000Ms
56
/
256
M
b
56/256Mb
56/256Mb
/*
_ __ __ _ _
| | \ \ / / | | (_)
| |__ _ _ \ V /__ _ _ __ | | ___ _
| '_ \| | | | \ // _` | '_ \| | / _ \ |
| |_) | |_| | | | (_| | | | | |___| __/ |
|_.__/ \__, | \_/\__,_|_| |_\_____/\___|_|
__/ |
|___/
*/int fib[100];
vector<pair<int,int>>V[MAX];
bool del[MAX];int sz[MAX][60];int n;
bool dfs(int,int,int,int,int);
bool solve(int,int,int,int,int);int cnt;/// 目前树大小 Fib(a) ,目前访问 x 节点,父亲为 f ,第 ce 次迭代
bool dfs(int a,int b,int c,int x,int f,int ce){
sz[x][ce]=1;
bool can = false;for(auto it : V[x]){int y = it.first;int w = it.second;if(y == f || del[w])continue;/// 如果边删掉了就别访问了
can |=dfs(a,b,c,y,x,ce);if(del[w])return can;/// 立即终止,表示这条边在之前的搜索中删掉了if(sz[y][ce]== fib[b]){/// 找到了,继续搜索
del[w]= true;
can = true;
can &=solve(b,b-1,b-2,y,ce);/// 必须两个子树都符合,用逻辑与操作
can &=solve(c,c-1,c-2,x,ce);return can;}if(sz[y][ce]== fib[c]){
del[w]= true;
can = true;
can &=solve(b,b-1,b-2,x,ce);
can &=solve(c,c-1,c-2,y,ce);return can;}
sz[x][ce]+= sz[y][ce];if(can)break;}return can;}
bool solve(int a,int b,int c,int x,int ce){if(fib[a]==1)return true;/// 这个特判一下
cnt++;/// 迭代层数增加
bool can =dfs(a,b,c,x,x,cnt);return can;}intmain(){
n =read();for(int i =1;i < n;++i){int ta,tb;ta =read();tb =read();
V[ta].push_back(make_pair(tb,i));
V[tb].push_back(make_pair(ta,i));}int st =1;
fib[0]= fib[1]=1;while(fib[st]< n){
fib[st+1]= fib[st]+ fib[st-1];
st++;}if(fib[st]!= n)puts("NO");else{
bool can =solve(st,st-1,st-2,1,0);puts(can ?"YES":"NO");}return0;}
【解题报告】Codeforces Global Round 13A:K-th Largest Value | QDB:Minimal Cost | 思维C: Pekora and Trampoline | dpdpdpD:Zookeeper and The Infinite Zoo | 位运算E:Fib-tree | 树 + 搜索Codeforces Global Round 13A:K-th Largest Value | QD…B:Minimal Cost | 思维【题意】第 i