T
T
T 组样例,每组给你一个
c
,
d
c,d
c,d 一开始你有
a
=
b
=
0
a=b=0
a=b=0 每一次操作,你可以选择一个正整数
k
k
k,然后做下述操作之一 (1)两个数都加
k
k
k (2)
a
a
a 加
k
k
k,
b
b
b 减
k
k
k (3)
a
a
a 减
k
k
k,
b
b
b 加
k
k
k 问你最少步数让
a
=
c
,
b
=
d
a=c,b=d
a=c,b=d 若无法做到则输出
−
1
-1
−1
1
≤
T
≤
1
0
4
1\le T\le 10^4
1≤T≤104
0
≤
c
,
d
≤
1
0
9
0\le c,d\le 10^9
0≤c,d≤109
思路
若
c
=
d
=
0
c=d=0
c=d=0,则步数为
0
0
0 若
c
=
d
>
0
c=d>0
c=d>0,则步数为
1
1
1 若
a
b
s
(
c
−
d
)
abs(c-d)
abs(c−d) 是奇数,则无法做到,因为两数同加或者一加一减,两数的差的奇偶性是不变的。 若
a
b
s
(
c
−
d
)
abs(c-d)
abs(c−d) 是偶数,则两步即可,第一步都加到平均数,第二步一加一减即可。
代码
时间复杂度:
O
(
T
)
O(T)
O(T)
#include<bits/stdc++.h>
using namespace std;voidshow(){std::cerr << endl;}template<typename T,typename... Args>voidshow(T x,Args... args){std::cerr <<"[ "<< x <<" ] , ";show(args...);}#definelllonglongconstint MAX =2e6+50;intmain(){int t;scanf("%d",&t);while(t--){int ta,tb;scanf("%d%d",&ta,&tb);if(ta > tb)swap(ta,tb);if((tb - ta)%2){puts("-1");}else{if(ta == tb && ta ==0)puts("0");elseif(ta == tb)puts("1");elseputs("2");}}return0;}
B:Take Your Places!
题意
一开始给你一个长度为
n
n
n 的序列
a
n
a_n
an 你每次操作,可以选择相邻的两个下标
i
,
j
i,j
i,j,满足
∣
i
−
j
∣
=
1
|i-j|=1
∣i−j∣=1,然后交换
a
i
,
a
j
a_i,a_j
ai,aj 问你最少操作次数,满足相邻两个数的奇偶性都不同 若无法做到,则输出
−
1
-1
−1
思路
一共有四种最终的可能性:
J
O
J
⋯
O
J
O
J
O
⋯
J
O
J
O
⋯
J
O
O
J
⋯
O
J
JOJ\cdots OJ\\ OJO\cdots JO\\\ \\ JO\cdots JO\\ OJ\cdots OJ\\
JOJ⋯OJOJO⋯JOJO⋯JOOJ⋯OJ 我们枚举每一种情况。 现在假设第一个数从
J
J
J 开始 我们遍历数组,找到第一个
J
J
J 的位置,那么第一个奇数移动到位置
1
1
1 一定是最少次数的。 然后同理。第
i
i
i 个奇数移动到位置
2
i
−
1
2i-1
2i−1 是最优的 第一个数是
O
O
O 也是同理。答案取最小值即可
代码
时间复杂度:
O
(
n
)
O(n)
O(n)
#include<bits/stdc++.h>
using namespace std;voidshow(){std::cerr << endl;}template<typename T,typename... Args>voidshow(T x,Args... args){std::cerr <<"[ "<< x <<" ] , ";show(args...);}#definelllonglongconstint MAX =1e5+50;constint INF =0x3f3f3f3f;const ll LINF =0x3f3f3f3f3f3f3f3f;int aa[MAX];intmain(){int t;scanf("%d",&t);while(t--){int n;scanf("%d",&n);int even =0,odd =0;for(int i =1;i <= n;++i){scanf("%d",&aa[i]);if(aa[i]&1)odd++;else even++;}if(abs(even-odd)>1)puts("-1");else{
ll ans = LINF;if((n+1)/2== odd){// odd even ...int fir =1;
ll tmp =0;for(int i =1;i <= n;++i){if(aa[i]%2==1){
tmp +=abs(i-fir);
fir +=2;}}
ans =min(ans,tmp);}if((n+1)/2== even){// even odd ...int fir =1;
ll tmp =0;for(int i =1;i <= n;++i){if(aa[i]%2==0){
tmp +=abs(i-fir);
fir +=2;}}
ans =min(ans,tmp);}printf("%lld\n",ans);}}return0;}
C:Compressed Bracket Sequence
题意
给你一个压缩过的括号序列
c
n
c_n
cn 其中奇数位置
c
i
c_i
ci 表示有
c
i
c_i
ci 个左括号 其中偶数位置
c
i
c_i
ci 表示有
c
i
c_i
ci 个右括号 现在问你,有多少个子区间是合法的括号序列(子区间是解压后的子区间)
1
≤
n
≤
1000
1\le n\le 1000
1≤n≤1000
1
≤
c
i
≤
1
0
9
1\le c_i\le 10^9
1≤ci≤109
思路
看到范围肯定默认
O
(
n
2
)
O(n^2)
O(n2) 去做。 我们肯定考虑有多少个子区间的左端点是在
c
i
c_i
ci 这里开始的,然后暴力
f
o
r
for
for 过去。 设
c
i
=
z
u
o
c_i=zuo
ci=zuo ,我们枚举
j
=
i
+
1
t
o
n
j=i+1\ to\ n
j=i+1ton 设
d
u
o
=
0
duo=0
duo=0
如果
j
j
j 是奇数,那么我们
d
u
o
=
d
u
o
+
c
j
duo=duo+c_j
duo=duo+cj,表示我们必须要把这些多的左括号匹配完之后,才能多一些合法的子区间满足左端点是在
c
i
c_i
ci 这里取到的
如果
j
j
j 是偶数,那么我们要优先把
d
u
o
duo
duo 的左括号匹配完。 如果
d
u
o
duo
duo 的左括号匹配完了,那么就把
z
u
o
zuo
zuo 与剩下的
c
j
c_j
cj 去匹配。 如果
d
u
o
duo
duo 的左括号匹配完了,且
j
≠
i
+
1
j\ne i+1
j=i+1,那么方案数要多一个 (想一想为什么) 如果
z
u
o
zuo
zuo 的左括号匹配完了,且
a
j
a_j
aj 的有括号还有剩余,那么后面都是非法的情况了。
代码
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
#include<bits/stdc++.h>
using namespace std;voidshow(){std::cerr << endl;}template<typename T,typename... Args>voidshow(T x,Args... args){std::cerr <<"[ "<< x <<" ] , ";show(args...);}#definelllonglongconstint MAX =1e3+50;constint INF =0x3f3f3f3f;const ll LINF =0x3f3f3f3f3f3f3f3f;int aa[MAX];intmain(){int n;scanf("%d",&n);for(int i =1;i <= n;++i)scanf("%d",&aa[i]);
ll ans =0;for(int i =1;i <= n;i+=2){
ll zuo = aa[i];
ll duo =0;for(int j = i+1;j <= n;++j){if(j&1){
duo += aa[j];// 多余的左括号要匹配完}else{
ll tmp = aa[j];
ll mn =min(tmp,duo);// 优先匹配多余的左括号
tmp -= mn;duo -= mn;if(duo ==0){
mn =min(tmp,zuo);
ans += mn;// 然后拿 a_i 的左括号与剩下的 a_j 的右括号匹配if(j != i+1)ans++;// 想一想,为什么
zuo -= mn;
tmp -= mn;if(tmp)break;// 右括号多了,肯定后面都是非法子区间了}}}}printf("%lld",ans);return0;}
D:Take a Guess
题意
给你一个
n
,
k
n,k
n,k 他有一个隐藏的序列
a
n
a_n
an,但是不告诉你,你需要猜出其中的第
k
k
k 小的数是多少 你最多有
2
n
2n
2n 次操作,每次可以询问: (1)
a
n
d
i
j
and\ i\ j
andij,系统给你返回
a
i
&
a
j
a_i\&a_j
ai&aj,即位与运算 (2)
o
r
i
j
or\ i\ j
orij,系统给你返回
a
i
∣
a
j
a_i|a_j
ai∣aj,即位或运算 最后你需回答
f
i
n
i
s
h
x
finish\ x
finishx 表示第
k
k
k 小的数字是
x
x
x
3
≤
n
≤
1
0
4
3\le n\le 10^4
3≤n≤104
1
≤
k
≤
n
1\le k\le n
1≤k≤n
0
≤
a
i
≤
1
0
9
0\le a_i\le 10^9
0≤ai≤109
思路
首先观察到,如果有两个不知道的数字,我们知道了他们的与,或的结果是解不出他们的值的 然后也注意到
n
≥
3
n\ge 3
n≥3,说明我们至少要三个数字去搞。
首先,如果
a
i
&
a
j
=
x
a_i\&a_j=x
ai&aj=x,那么表示
a
i
,
a
j
a_i,a_j
ai,aj 本身肯定是
x
x
x 的超集,我们直接把做下述操作即可:
a
i
∣
=
x
a_i|=x
ai∣=x
a
j
∣
=
x
a_j|=x
aj∣=x 然后考虑这四种情况中,情况
1
,
3
,
4
1,3,4
1,3,4 通过这个处理已经是处理对了,只剩下情况
2
2
2 还没有对 什么时候是情况
2
2
2 ?就是这一位有
2
2
2 个或运算为
1
1
1 的时候。此时根据哪两组数或运算为
1
1
1,我们便可以解出其中那个为
1
1
1 的数字
我们想一下。假设我们已经获得了一个数字
x
x
x ,现在想知道另一个未知数字
y
y
y 。 且我们知道这两个数字的 与和或运算值,我们是否能获得那个位置数字? 假设与运算答案为
c
c
c,那么根据前面的思考,当然要先令
y
=
c
y=c
y=c 假设或运算答案为
d
d
d,我们仍然拆位去做。 假设第
i
i
i 位,数字
x
,
y
x,y
x,y 的值是这样子的: (1)
0
0
0\ 0
00 (2)
0
1
0\ 1
01 (3)
1
0
1\ 0
10 (4)
1
1
1\ 1
11 容易想到,对于情况
4
4
4,我们答案就已经对了。对于情况
1
,
3
1,3
1,3,我们不需要任何操作。 只有情况
2
2
2 是特殊的,就是这一位的与为
0
0
0,且这一位的或为
1
1
1,且
x
x
x 的这一位不是
1
1
1,那么我们令
y
y
y 的这一位为
1
1
1
这样我们就可以通过两次运算,得到一个未知数字。 所以
n
n
n 个未知数字只需要
2
n
2n
2n 次查询即可。
代码
时间复杂度:
O
(
n
log
n
)
O(n\log n)
O(nlogn) (要排个序)
#include<bits/stdc++.h>
using namespace std;voidshow(){std::cerr << endl;}template<typename T,typename... Args>voidshow(T x,Args... args){std::cerr <<"[ "<< x <<" ] , ";show(args...);}#definelllonglongconstint MAX =1e4+50;constint INF =0x3f3f3f3f;const ll LINF =0x3f3f3f3f3f3f3f3f;int aa[MAX];int tmp[5];int idx =0;intmain(){int n,k;scanf("%d%d",&n,&k);for(int i =1;i <=3;++i){for(int j = i +1;j <=3;++j){// 先知道前面三个数字printf("and %d %d\n",i,j);fflush(stdout);int t;scanf("%d",&t);
aa[i]|= t;
aa[j]|= t;}}for(int i =1;i <=3;++i){for(int j = i +1;j <=3;++j){printf("or %d %d\n",i,j);fflush(stdout);int t;scanf("%d",&tmp[++idx]);}}for(int i =0;i <=30;++i){int now =(1<<i);int num =0;int no =0;for(int j =1;j <=3;++j){if(tmp[j]& now)num++;else no = j;}if(num ==2){if(no ==1)aa[3]|= now;elseif(no ==2)aa[2]|= now;elseif(no ==3)aa[1]|= now;}}for(int i =4;i <= n;++i){// 后面每个数字都可以拿第一个数字去比对int ad,huo;printf("and %d %d\n",1,i);fflush(stdout);scanf("%d",&ad);printf("or %d %d\n",1,i);fflush(stdout);scanf("%d",&huo);for(int j =0;j <=30;++j){int now =(1<<j);if(ad & now) aa[i]|= now;elseif((huo & now)&&!(aa[1]& now)) aa[i]|= now;}}// for(int i = 1;i <= n;++i){// show(aa[i]);// }sort(aa+1,aa+1+n);printf("finish %d",aa[k]);return0;}
E:Equilibrium
题意
给你两个长度为
n
n
n 的序列
a
n
,
b
n
a_n,b_n
an,bn 有
Q
Q
Q 个查询,每次问你让
[
L
,
R
]
[L,R]
[L,R] 子区间变平衡的最少操作次数
平衡 是指
∀
i
∈
[
L
,
R
]
\forall i\in[L,R]
∀i∈[L,R],都有
a
i
=
b
i
a_i=b_i
ai=bi
操作一次 是指选择这个子串范围内的一个长度为偶数的子序列
L
≤
p
o
s
1
<
p
o
s
2
<
⋯
<
p
o
s
k
≤
R
L\le pos_1<pos_2<\cdots<pos_k\le R
L≤pos1<pos2<⋯<posk≤R 然后,若
i
i
i 是奇数,则令
a
p
o
s
i
a_{pos_i}
aposi 自增
1
1
1 然后,若
i
i
i 是偶数,则令
b
p
o
s
i
b_{pos_i}
bposi 自增
1
1
1
2
≤
n
,
q
≤
1
0
5
2\le n,q\le 10^5
2≤n,q≤105
0
≤
a
i
,
b
i
≤
1
0
9
0\le a_i,b_i\le 10^9
0≤ai,bi≤109
思路
多次区间查询,大概率是用线段树等数据结构去做。
我们一开始没什么思路,只好去手膜样例:
我们第一个发现:如果
a
L
>
b
L
a_L>b_L
aL>bL,由于子序列的奇数位置
a
a
a 自增,子序列的偶数位置
b
b
b 自增,那么
b
L
b_L
bL 自然是无法自增的,就无解了。
第二个发现:如果
∑
i
=
L
p
a
i
>
∑
i
=
L
p
b
i
\sum_{i=L}^p a_i > \sum_{i=L}^p b_i
∑i=Lpai>∑i=Lpbi,那么也是无解的,原因同上。
第三个发现:如果
∑
i
=
L
R
a
i
≠
∑
i
=
L
R
b
i
\sum_{i=L}^R a_i \ne \sum_{i=L}^R b_i
∑i=LRai=∑i=LRbi,那么也是无解的 因为每次操作完之后
∑
i
=
L
R
a
i
−
∑
i
=
L
R
b
i
\sum_{i=L}^R a_i-\sum_{i=L}^R b_i
∑i=LRai−∑i=LRbi 是永远不变的。最终要求都相同,自然差值为
0
0
0
考虑到上述的很多发现都是一个
a
a
a 的从
L
L
L 开始的前缀和和
b
b
b 从
L
L
L 开始的前缀和的要求 我们自然想到去令
p
r
e
[
i
]
=
p
r
e
[
i
−
1
]
+
b
[
i
]
−
a
[
i
]
pre[i]=pre[i-1]+b[i]-a[i]
pre[i]=pre[i−1]+b[i]−a[i],做一个从
0
0
0 开始的前缀和 如果我们想获取从
L
L
L 开始的前缀和,我们只要区间操作,即
p
r
e
[
L
]
∼
p
r
e
[
n
]
pre[L]\sim pre[n]
pre[L]∼pre[n] 都加上
p
r
e
[
L
−
1
]
pre[L-1]
pre[L−1] 这是一个区间加,我们自然使用线段树去做。 这样,第二个发现我们可以转变为区间
[
L
,
R
]
[L,R]
[L,R] 的最小值必须
>
0
>0
>0,即区间最小值。 注意每次操作完,都要撤销上述区间加的操作。
现在还有一个问题,就是怎么求最少操作次数。 想到,由于我们是选择的子序列,下标可以不连续。 如果
p
r
e
[
x
]
=
c
(
c
>
0
)
pre[x]=c(c> 0)
pre[x]=c(c>0),那么就说明至少要有
c
c
c 次操作,来让
a
x
=
b
x
a_x=b_x
ax=bx 所以,我们最少的操作次数就是
max
i
=
L
t
o
R
{
p
r
e
[
i
]
}
\max_{i=L\ to\ R}\{pre[i]\}
maxi=LtoR{pre[i]},就是一个区间最大值。
代码
时间复杂度:
O
(
(
n
+
q
)
log
n
)
O((n+q)\log n)
O((n+q)logn)