视频讲解:BV1pG411p7Cd
A. Mark the Photographer
题目大意
给定 2 n ( 1 ≤ n ≤ 100 ) 2n (1 \leq n\leq 100) 2n(1≤n≤100) 个人,第 i i i 个人身高为 h i h_i hi 。将他们以任意顺序成两排,每排 n n n 人。问能否实现 ∀ i ∈ [ 1 , n ] \forall i\in [1,n] ∀i∈[1,n] ,第二排的第 i i i 个人身高至少比第一排的第 i i i 个人高 x x x 。
题解
贪心。
将
n
n
n 个人从小到大排列,判断是否满足
∀
i
∈
[
1
,
n
]
,
h
i
+
x
≤
h
n
+
i
\forall i\in [1,n],h_i+x\leq h_{n+i}
∀i∈[1,n],hi+x≤hn+i 即可。
若存在
i
=
x
i=x
i=x 不满足条件,则
x
x
x 与更大或更小的元素交换位置,必定也不满足条件。若均满足条件,则得到一组合法解。
参考代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=220;
int a[MAXN];
int main()
{
int T,n,x,i,flag;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&x);
for(i=1;i<=2*n;i++)
scanf("%d",&a[i]);
sort(a+1,a+2*n+1);
flag=1;
for(i=1;i<=n;i++)
{
if(a[i]+x>a[i+n])
flag=0;
}
if(flag)
puts("YES");
else
puts("NO");
}
}
B. Mark the Dust Sweeper
题目大意
有 n ( 2 ≤ n ≤ 2 ⋅ 1 0 5 ) n(2 \leq n \leq 2 \cdot 10^5) n(2≤n≤2⋅105) 个房间需要清理,第 i i i 个房间有 a i ( 0 ≤ a i ≤ 1 0 9 ) a_i(0 \leq a_i \leq 10^9) ai(0≤ai≤109) 件垃圾。每次操作包含以下步骤
- 选择一对 i , j i,j i,j ,满足 1 ≤ < j ≤ n 1 \leq <j \leq n 1≤<j≤n 且 a i , a i + 1 , . . . , a j − 1 a_i,a_{i+1},...,a_{j-1} ai,ai+1,...,aj−1 均严格大于 0 0 0 ;
- a i a_i ai 变为 a i − 1 a_i-1 ai−1
- a j a_j aj 变为 a j + 1 a_j+1 aj+1
求使得 a 1 = a 2 = . . . = a n − 1 = 0 a_1=a_2=...=a_{n-1}=0 a1=a2=...=an−1=0 的最小操作数。
题解
对于
a
1
,
a
2
,
.
.
.
,
a
n
−
1
a_1,a_2,...,a_{n-1}
a1,a2,...,an−1 中的每一件垃圾,都需要花费
1
1
1 次操作清理。
左起第一个出现垃圾的房间到
a
n
−
1
a_{n-1}
an−1 ,每存在一个
a
i
=
0
a_i=0
ai=0 则需要额外操作
1
1
1 次。
以上两种操作数累加即可。
注意开long long。
参考代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=200200;
int a[MAXN];
int main()
{
int T,n,i;
ll ans;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%d",&a[i]);
ans=0;
for(i=1;i<n;i++)
{
ans+=a[i];
if(ans&&a[i]==0)
ans++;
}
printf("%lld\n",ans);
}
}
C. Mark and His Unfinished Essay
题目大意
给定初始长为
n
(
1
≤
n
≤
2
⋅
1
0
5
)
n(1 \leq n \leq 2 \cdot 10^5)
n(1≤n≤2⋅105) 的字符串
s
s
s ,进行
c
(
1
≤
c
≤
40
)
c(1 \leq c \leq 40)
c(1≤c≤40) 次复制,每次复制选择区间
s
[
l
,
r
]
(
1
≤
l
≤
r
≤
1
0
18
)
s[l,r](1 \leq l \leq r \leq 10^{18})
s[l,r](1≤l≤r≤1018) ,复制并粘贴到字符串末尾。
有
q
(
1
≤
q
≤
1
0
4
)
q(1 \leq q \leq 10^4)
q(1≤q≤104) 次询问,每次询问求
s
k
(
1
≤
k
≤
1
0
18
)
s_k(1 \leq k \leq 10^{18})
sk(1≤k≤1018) 。
题解
由于
c
c
c 不大,因此若
k
>
n
k>n
k>n 时,不断向前寻找当前的
k
k
k 来自于哪一次复制即可。
可以采用set+pair进行快速查找。
时间复杂度
O
(
n
+
q
c
log
(
c
)
)
O(n+qc\log (c))
O(n+qclog(c)) 。
参考代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=200200;
char s[MAXN];
set<pair<ll,ll> > st;
set<pair<ll,ll> >::iterator it;
int main()
{
int T,n,c,q;
ll l,r,x,len;
scanf("%d",&T);
while(T--)
{
scanf("%d%d%d",&n,&c,&q);
scanf("%s",s+1);
len=n;
st.clear();
while(c--)
{
scanf("%lld%lld",&l,&r);
st.insert(make_pair(len+1,l));
len+=r-l+1;
}
while(q--)
{
scanf("%lld",&x);
while(x>n)
{
it=st.lower_bound(make_pair(x,1ll<<60));
it--;
x-=it->first-it->second;
}
printf("%c\n",s[x]);
}
}
}
D. Mark and Lightbulbs
题目大意
给定长度均为 n ( 3 ≤ n ≤ 2 ⋅ 1 0 5 ) n(3 \leq n \leq 2 \cdot 10^5) n(3≤n≤2⋅105) 的01字符串 s , t s,t s,t 。每次操作包含以下步骤:
- 选择 i ∈ [ 2 , n − 1 ] i\in[2,n-1] i∈[2,n−1] 且 s i − 1 ≠ s i + 1 s_{i-1}\neq s_{i+1} si−1=si+1
- 切换 s i s_i si ,即变为 s i ⊕ 1 s_i\oplus 1 si⊕1
求最小操作数。
题解
发现每次操作时,都会将01分界线左移或者右移。
因此若初始
s
1
≠
t
1
s_1\neq t_1
s1=t1 或分界线数量不同,则无解。
另外注意到
s
→
t
s \rightarrow t
s→t 的操作数必定和
t
→
s
t \rightarrow s
t→s 操作数相同。
在有解的情况下,直接从左到右模拟每个分界线调整的所需操作数即可。
参考代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=200200;
char s[MAXN],t[MAXN];
int num1[MAXN],num2[MAXN];
int main()
{
int T,n,sum1,sum2,cnt1,cnt2,i;
ll ans;
scanf("%d",&T);
while(T--)
{
scanf("%d%s%s",&n,s+1,t+1);
sum1=sum2=0;
cnt1=cnt2=1;
for(i=1;i<n;i++)
{
if(s[i]!=s[i+1])
{
num1[sum1++]=cnt1;
cnt1=1;
}
else
cnt1++;
if(t[i]!=t[i+1])
{
num2[sum2++]=cnt2;
cnt2=1;
}
else
cnt2++;
}
if(sum1!=sum2||s[1]!=t[1])
{
puts("-1");
continue;
}
ans=0;
for(i=0;i<sum1;i++)
{
ans+=abs(num1[i]-num2[i]);
if(num1[i]>num2[i])
num1[i+1]+=num1[i]-num2[i];
else
num2[i+1]+=num2[i]-num1[i];
}
printf("%lld\n",ans);
}
}
E. Mark and Professor Koro
题目大意
有
n
(
2
≤
n
≤
2
⋅
1
0
5
)
n(2 \leq n \leq 2 \cdot 10^5)
n(2≤n≤2⋅105) 个数
a
1
,
a
2
,
.
.
.
,
a
n
(
1
≤
a
i
≤
2
⋅
1
0
5
)
a_1,a_2,...,a_n(1 \leq a_i \leq 2 \cdot 10^5)
a1,a2,...,an(1≤ai≤2⋅105) 。每次可以选择两个相同的数
x
x
x ,删去它们,并加入
x
+
1
x+1
x+1 。求可以得到的最大的数。
有
q
(
1
≤
q
≤
2
⋅
1
0
5
)
q(1 \leq q \leq 2 \cdot 10^5)
q(1≤q≤2⋅105) 次询问,每次询问会永久将
a
k
a_k
ak 变为
l
l
l 。
题解
如果将
a
i
a_i
ai 视为
2
a
i
2^{a_i}
2ai ,则这道题就变成了
N
N
N 位的高精度加减法,和求最高位的
1
1
1 的位置。
采用线段树+二分或类似珂朵莉树的set方法维护。
注意每次操作时间复杂度需要
O
(
log
(
n
)
)
O(\log(n))
O(log(n)) 。
参考代码(线段树+二分)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=200200;
int a[MAXN],num[MAXN];
struct Node
{
int l,r;
ll sum,lazy;
}tree[MAXN<<4];
void pushUp(int i)
{
tree[i].sum=tree[i<<1].sum+tree[i<<1|1].sum;
}
void pushDown(int i)
{
if(tree[i].lazy)
{
tree[i<<1].lazy+=tree[i].lazy;
tree[i<<1|1].lazy+=tree[i].lazy;
tree[i<<1].sum+=tree[i].lazy*(tree[i<<1].r-tree[i<<1].l+1);
tree[i<<1|1].sum+=tree[i].lazy*(tree[i<<1|1].r-tree[i<<1|1].l+1);
tree[i].lazy=0;
}
}
void build(int i,int l,int r)
{
tree[i].l=l;
tree[i].r=r;
tree[i].lazy=0;
if(l==r)
{
tree[i].sum=num[l];
return;
}
int mid=(l+r)>>1;
build(i<<1,l,mid);
build(i<<1|1,mid+1,r);
pushUp(i);
}
void update(int i,int l,int r,int c)
{
if(tree[i].l==l&&tree[i].r==r)
{
tree[i].sum+=c*(r-l+1);
tree[i].lazy+=c;
return;
}
pushDown(i);
int mid=(tree[i].l+tree[i].r)>>1;
if(r<=mid)
update(i<<1,l,r,c);
else if(l>mid)
update(i<<1|1,l,r,c);
else
{
update(i<<1,l,mid,c);
update(i<<1|1,mid+1,r,c);
}
pushUp(i);
}
int findsum(int i,int l,int r)
{
if(tree[i].l==tree[i].r)
return tree[i].sum;
pushDown(i);
int mid=(tree[i].l+tree[i].r)>>1;
if(r<=mid)
return findsum(i<<1,l,r);
else if(l>mid)
return findsum(i<<1|1,l,r);
else
return findsum(i<<1,l,mid)+findsum(i<<1|1,mid+1,r);
}
int find(int i,int x,int c)
{
if((c==1&&tree[i].sum==0)||(c==0&&tree[i].sum==tree[i].r-tree[i].l+1))
return 0;
if(tree[i].l==tree[i].r)
return tree[i].l;
pushDown(i);
int mid=(tree[i].l+tree[i].r)>>1;
if(x>mid||(c==1&&tree[i<<1].sum==0)||(c==0&&tree[i<<1].sum==tree[i<<1].r-tree[i<<1].l+1))
return find(i<<1|1,x,c);
int ret=find(i<<1,x,c);
if(ret)
return ret;
else
return find(i<<1|1,x,c);
}
void add(int x)
{
int l=find(1,x,0);
if(l>x)
update(1,x,l-1,-1);
update(1,l,l,1);
}
void sub(int x)
{
int l=find(1,x,1);
if(l>x)
update(1,x,l-1,1);
update(1,l,l,-1);
}
int query(int i)
{
if(tree[i].l==tree[i].r)
return tree[i].l;
pushDown(i);
if(tree[i<<1|1].sum)
return query(i<<1|1);
else
return query(i<<1);
}
int main()
{
int n,q,i,k,l;
scanf("%d%d",&n,&q);
for(i=1;i<=n;i++)
{
scanf("%d",&a[i]);
num[a[i]]++;
}
for(i=1;i<MAXN-1;i++)
{
num[i+1]+=num[i]/2;
num[i]%=2;
}
build(1,1,MAXN-1);
while(q--)
{
scanf("%d%d",&k,&l);
add(l);
sub(a[k]);
a[k]=l;
printf("%d\n",query(1));
}
}
F. Mark and the Online Exam
题目大意
交互题。
有
n
(
1
≤
n
≤
1000
)
n(1 \leq n \leq 1000)
n(1≤n≤1000) 道判断题,不知道答案,但每次询问可以询问某套答案的分数。
要求最多
675
675
675 次询问求解出正确答案。
题解
原题T183641 考试(2021 CoE III G)和P7848 「JZOI-2」填问卷,最优方法可以在 O ( n log n ) O(\frac{n}{\log n}) O(lognn) 次内查询得到答案。
本题只要求
2
n
3
\frac{2n}{3}
32n 次内查询得到答案,因此方法更多一些。
以下介绍官方题解的做法。
先查询 q 1 = T T T T . . . T q_1=TTTT...T q1=TTTT...T 和 q 2 = T F T F . . . T F q_2=TFTF...TF q2=TFTF...TF 的结果。
再查询 n 3 \frac{n}{3} 3n 次,第 i i i 次查询在 q 1 q_1 q1 的基础上将 2 i − 1 2i-1 2i−1 和 2 i 2i 2i 改为 F F F 后的结果,有以下几种情况:
- 若正确数 + 2 +2 +2 ,则这两位的答案均为 F F F
- 若正确数 − 2 -2 −2 ,则这两位的答案均为 T T T
- 若正确数不变,则这两位答案为 T F TF TF 或 F T FT FT
对于第三种情况,我们通过以下方式得到。
查询
n
3
\frac{n}{3}
3n 次,第
i
i
i 次查询,在
q
2
q_2
q2 的基础上将
2
i
−
1
,
2
i
,
i
+
2
⌊
n
3
⌋
2i-1,2i,i+2\lfloor \frac{n}{3} \rfloor
2i−1,2i,i+2⌊3n⌋ 翻转,即
q
2
=
T
F
T
F
.
.
.
T
F
.
.
.
T
.
.
.
T
F
q_2=TFTF...TF...T...TF
q2=TFTF...TF...T...TF
q ′ = T F T F . . . F T . . . F . . . T F q'=TFTF...FT...F...TF q′=TFTF...FT...F...TF
有以下几种情况:
- 若正确数 + 3 +3 +3 ,则这三位的答案为 F T FT FT 和 F F F
- 若正确数 − 3 -3 −3 ,则这三位的答案为 T F TF TF 和 T T T
- 若正确数 + 1 +1 +1 ,则这三位的答案为 F T FT FT 和 T T T
- 若正确数 − 1 -1 −1 ,则这三位的答案为 T F TF TF 和 F F F
因此在这
n
3
\frac{n}{3}
3n 次查询内,不光解决了之前是
T
F
TF
TF 还是
F
T
FT
FT 的问题,还得到了最后
n
3
\frac{n}{3}
3n 部分的答案。
若不需要考虑是
T
F
TF
TF 还是
F
T
FT
FT 的情况,则
2
i
−
1
,
2
i
2i-1,2i
2i−1,2i 不需要翻转,只需翻转
i
+
2
⌊
n
3
⌋
i+2\lfloor \frac{n}{3} \rfloor
i+2⌊3n⌋ 即可。
参考代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
string s1,s2,ans;
int query(string &s)
{
cout<<s<<endl;
cout.flush();
int ret;
cin>>ret;
if(ret==n)
exit(0);
return ret;
}
void rev(char &c)
{
if(c=='T')
c='F';
else
c='T';
}
char revc(char c)
{
if(c=='T')
return 'F';
else
return 'T';
}
int main()
{
int ret1,ret2,ret,tim,i,flag;
cin>>n;
s1=s2=ans=string(n,'T');
for(i=1;i<n;i+=2)
s2[i]='F';
ret1=query(s1);
ret2=query(s2);
tim=n/3;
for(i=0;i<tim;i++)
{
rev(s1[2*i]),rev(s1[2*i+1]);
ret=query(s1);
rev(s1[2*i]),rev(s1[2*i+1]);
flag=0;
if(ret-ret1==2)
ans[2*i]=ans[2*i+1]=revc(s1[2*i]);
else if(ret-ret1==-2)
ans[2*i]=ans[2*i+1]=s1[2*i];
else
flag=1;
rev(s2[2*tim+i]);
if(flag)
rev(s2[2*i]),rev(s2[2*i+1]);
ret=query(s2);
rev(s2[2*tim+i]);
if(flag)
rev(s2[2*i]),rev(s2[2*i+1]);
if(flag)
{
if(ret-ret2==-3)
{
ans[2*i]=s2[2*i];
ans[2*i+1]=s2[2*i+1];
ans[2*tim+i]=s2[2*tim+i];
}
else if(ret-ret2==3)
{
ans[2*i]=revc(s2[2*i]);
ans[2*i+1]=revc(s2[2*i+1]);
ans[2*tim+i]=revc(s2[2*tim+i]);
}
else if(ret-ret2==-1)
{
ans[2*i]=s2[2*i];
ans[2*i+1]=s2[2*i+1];
ans[2*tim+i]=revc(s2[2*tim+i]);
}
else
{
ans[2*i]=revc(s2[2*i]);
ans[2*i+1]=revc(s2[2*i+1]);
ans[2*tim+i]=s2[2*tim+i];
}
}
else
{
if(ret-ret2==1)
ans[2*tim+i]=revc(s2[2*tim+i]);
else
ans[2*tim+i]=s2[2*tim+i];
}
}
for(i=3*tim;i<n;i++)
{
rev(s1[i]);
ret=query(s1);
rev(s1[i]);
if(ret-ret1==1)
ans[i]=revc(s1[i]);
else
ans[i]=s1[i];
}
query(ans);
}