视频讲解:BV1Hy4y1M74u
A. Arithmetic Array
题目大意
给定一个包含 n n n 个整数的数组 a a a ,求最少需要向数组 a a a 中添加多少个非负整数,使得数组的算术平均数恰好等于 1 1 1 。
题解
设 s u m = ∑ i = 1 n a i sum=\sum_{i=1}^{n}{a_i} sum=∑i=1nai ,有以下两种情况:
- 若 s u m < n sum < n sum<n ,则再添加一个数值为 n − s u m + 1 n-sum+1 n−sum+1 的元素,即可成立;
- 若 s u m ≥ n sum \geq n sum≥n ,则再添加 s u m − n sum-n sum−n 个 0 0 0 元素,即可成立;
参考代码
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
int main()
{
int T,n,x,sum,i;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
sum=0;
for(i=1;i<=n;i++)
{
scanf("%d",&x);
sum+=x;
}
if(sum-n<0)
printf("1\n");
else
printf("%d\n",sum-n);
}
}
B. Bad Boy
题目大意
在一个
n
n
n 行
m
m
m 列的网格上,初始在
(
i
,
j
)
(i,j)
(i,j) 位置,每次可以向上下左右四个方向之一移动一格。
现在在网格上任意位置放置两个悠悠球,求如何放置这两个悠悠球,使得从
(
i
,
j
)
(i,j)
(i,j) 出发,捡到两个悠悠球,再回到初始位置的路径最长。
题解
两个悠悠球在对角的两个角落,即 ( 1 , 1 ) (1,1) (1,1) 和 ( n , m ) (n,m) (n,m) 位置,或 ( 1 , m ) (1,m) (1,m) 和 ( n , 1 ) (n,1) (n,1) 位置,总路程最长,均为 2 n + 2 m 2n+2m 2n+2m 。输出任意解即可。
参考代码
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
int main()
{
int T,n,m,i,j;
scanf("%d",&T);
while(T--)
{
scanf("%d%d%d%d",&n,&m,&i,&j);
printf("%d %d %d %d\n",1,1,n,m);
}
}
C. Challenging Cliffs
题目大意
给定
n
n
n 座山,每座山的高度为
h
i
h_i
hi 。将其按任意顺序排列在一条直线上,从左到右编号为
1
1
1 到
n
n
n 。
求
∣
h
1
−
h
n
∣
|h_1-h_n|
∣h1−hn∣ 最小时,满足条件
h
i
<
h
i
+
1
(
1
≤
i
<
n
)
h_i < h_{i+1}(1 \leq i < n)
hi<hi+1(1≤i<n) 的
i
i
i 最多时的排列方案。
题解
将
h
i
h_i
hi 递增排序后,必定有最多的
i
i
i 满足
h
i
<
h
i
+
1
(
1
≤
i
<
n
)
h_i < h_{i+1}(1 \leq i < n)
hi<hi+1(1≤i<n) 。
接下来考虑使得
∣
h
1
−
h
n
∣
|h_1-h_n|
∣h1−hn∣ 最小,找到最小的
h
j
+
1
−
h
j
h_{j+1}-h_j
hj+1−hj ,将第
j
+
1
j+1
j+1 到
n
n
n 座山全部移到第
1
1
1 座山前面即可。
注意如果只有两座山时,由于代码写法不同,可能需要特殊考虑。
参考代码
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int MAXN=200200;
int h[MAXN];
int main()
{
int T,n,i,mn,id;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%d",&h[i]);
sort(h+1,h+n+1);
mn=h[n]-h[1];
id=n;
for(i=1;i<n;i++)
{
if(h[i+1]-h[i]<mn)
{
mn=h[i+1]-h[i];
id=i;
}
}
for(i=id+1;i<=n;i++)
printf("%d ",h[i]);
for(i=1;i<=id;i++)
printf("%d ",h[i]);
puts("");
}
}
D. Deleting Divisors
题目大意
Alice 和 Bob 进行博弈游戏。
初始给定一个正整数
n
n
n ,每轮将其减去
x
x
x ,
x
x
x 为
n
n
n 的因数且不为
1
1
1 或
n
n
n ,无法操作时失败,Alice先手。
双方均采用最优策略,求谁获胜。
题解
n n n 有以下三种状态:
- 奇数;
- 不为 2 2 2 的整数次幂的偶数;
- 2 2 2 的整数次幂;
对于奇数,其只能变成不为 2 2 2 的整数次幂的偶数。证明:
- 设 n = x y n=xy n=xy ,减去一个合法因数 x x x 后, n − x = x ( y − 1 ) n-x=x(y-1) n−x=x(y−1) ,其中 x x x 为奇数,因此 n − x = x ( y − 1 ) n-x=x(y-1) n−x=x(y−1) 必定是不为 2 2 2 的整数次幂的偶数。
对于不为 2 2 2 的整数次幂的偶数,有以下两种变化方案:
- 减去一个奇因数 x 1 x1 x1 ,变成奇数;
- 减去一个偶因数 x 2 x2 x2 ,变为偶数;
若执行方案1,减去一个奇因数
x
1
x1
x1 ,变成奇数后,则对手只能将其再减去一个奇数变回不为
2
2
2 的整数次幂的偶数,或直接面对一个奇素数的必败状态。
因此不为
2
2
2 的整数次幂的偶数是必胜状态,奇数是必败状态。
对于 2 2 2 的整数次幂 2 k 2^k 2k,有以下两种变化方案:
- 减去 2 p ( 1 ≤ p ≤ k − 2 ) 2^p(1 \leq p \leq k-2) 2p(1≤p≤k−2) ,变为不为 2 2 2 的整数次幂的偶数 2 p ∗ ( 2 k − p − 1 ) 2^p*(2^{k-p}-1) 2p∗(2k−p−1) ,即必胜状态;
- 减去 2 k − 1 2^{k-1} 2k−1 ,变为 2 2 2 的整数次幂 2 k − 1 2^{k-1} 2k−1 ;
由于方案1等于将必胜状态交给对手,因此不会选择方案1。
对于方案2,由于
k
=
1
k=1
k=1 时
2
1
2^1
21 为必败状态,因此若
k
k
k 为偶数是必胜状态,
k
k
k 为奇数是必败状态。
参考代码
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
int main()
{
int T,n,num;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
if(n%2)
{
printf("Bob\n");
continue;
}
num=0;
while(n%2==0)
{
n/=2;
num++;
}
if(n>1||num%2==0)
printf("Alice\n");
else
printf("Bob\n");
}
}
E1+E2. Erase and Extend
题目大意
给定一个长度为 n n n 的字符串 s s s ,可以对其进行任意次数和顺序的下述两种操作:
- 删除末尾的一个字符;
- 拷贝字符串,即 s = s + s s=s+s s=s+s ;
求可以变成的字典序最小的长度为 k k k 的字符串。
对于 Easy Version,
1
≤
n
,
k
≤
5
⋅
1
0
3
1 \leq n,k \leq 5 \cdot 10^3
1≤n,k≤5⋅103 。
对于 Hard Version,
1
≤
n
,
k
≤
5
⋅
1
0
5
1 \leq n,k \leq 5 \cdot 10^5
1≤n,k≤5⋅105 。
题解
答案必定是字符串
s
s
s 的某一前缀
A
A
A 的若干次重复结果
A
A
A
.
.
.
AAA...
AAA... ,即先删除若干次,再拷贝若干次,再删除若干次。、
可以用反证法证明:
- 假设最优解的拷贝过程中存在若干次删除操作,即存在 A . . . B . . . A . . . A...B...A... A...B...A... 比 A A A . . . AAA... AAA... 更优,那么 B B B . . . BBB... BBB... 是比 A . . . B . . . A . . . A...B...A... A...B...A... 更优的结果,存在矛盾。
因此对于 Easy Version,直接枚举所有前缀构造字符串,找出最优解即可。复杂度为 O ( n k ) O(nk) O(nk) 。
对于 Hard Version,可以记录历史最优前缀的长度为 p p p ,从左到右遍历字符 s i s_i si ,有以下几种可能:
- s i > s i % p s_i > s_{i\%p} si>si%p ,则不存在更优的前缀,其长度大于 i i i 。因此后续不用再考虑;
- s i = s i % p s_i = s_{i\%p} si=si%p ,则表示 s [ 0 , i ] s[0,i] s[0,i] 可以视为 s [ 0 , p − 1 ] s[0,p-1] s[0,p−1] 的若干次重复,前缀 s [ 0 , i ] s[0,i] s[0,i] 不会比前缀 s [ 0 , p − 1 ] s[0,p-1] s[0,p−1] 更优,跳过;
-
- 证明:假设前缀 s [ 0 , i ] = A A . . A B s[0,i]=AA..AB s[0,i]=AA..AB 比前缀 s [ 0 , p − 1 ] = A s[0,p-1]=A s[0,p−1]=A 更优,则前缀 s [ 0 , i % p ] = B s[0,i\%p]=B s[0,i%p]=B 是比前缀 s [ 0 , p − 1 ] = A s[0,p-1]=A s[0,p−1]=A 更优的解,存在矛盾。
- s i < s i % p s_i < s_{i\%p} si<si%p ,则表示 s [ 0 , i ] s[0,i] s[0,i] 比 s [ 0 , p − 1 ] s[0,p-1] s[0,p−1] 的若干次重复的字典序更小,是更优解,需要将 p p p 更新为 i + 1 i+1 i+1 ;
这样就可以在 O ( n ) O(n) O(n) 复杂度内找出最优解。
参考代码
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int MAXN=500500;
char s[MAXN];
int main()
{
int n,k,p,i;
scanf("%d%d",&n,&k);
scanf("%s",&s);
p=1;
for(i=1;i<n;i++){
if(s[i%p]<s[i])
break;
if(s[i%p]>s[i])
p=i+1;
}
for(i=0;i<k;i++)
printf("%c",s[i%p]);
puts("");
}
F. Figure Fixing
题目大意
给定一张包含
n
n
n 个点
m
m
m 条边的无向连通图,第
i
i
i 个节点有当前权值
v
i
v_i
vi 和目标权值
t
i
t_i
ti 两种属性。
每次可以选择一条边
(
i
,
j
)
(i,j)
(i,j) ,将
v
i
v_i
vi 和
v
j
v_j
vj 都增加任意整数
k
k
k 。可以执行任意次上述操作。
判断能否将所有节点的
v
i
v_i
vi 都变为
t
i
t_i
ti 。
题解
由于每次操作,对于整张图的权值总和会增加 2 k 2k 2k ,因此若 ∑ t i − v i \sum{t_i-v_i} ∑ti−vi 不为偶数,则无解。
如果图是二分图,考虑将每个节点染为红色或蓝色,每个红色节点的相邻节点都是蓝色节点,每个蓝色节点的相邻颜色都是红色节点。
每次操作等价于将所有红色节点的
s
u
m
1
=
∑
t
i
−
v
i
sum1=\sum{t_i-v_i}
sum1=∑ti−vi 和蓝色节点的
s
u
m
2
=
∑
t
i
−
v
i
sum2=\sum{t_i-v_i}
sum2=∑ti−vi 同时增加
k
k
k 。因此若初始
s
u
m
1
≠
s
u
m
2
sum1 \neq sum2
sum1=sum2 ,也无解。反之有解。
若图不是二分图,则至少存在一条连接两个同色节点的边,即可以改变一种颜色的 s u m sum sum 值时不改变另一种颜色的 s u m sum sum 值,因此总是有解的。
参考代码
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int MAXN=200200;
int t[MAXN],v[MAXN],col[MAXN];
ll sum[5];
vector<int> e[MAXN];
bool check()
{
queue<int> q;
q.push(1);
col[1]=1;
sum[1]=sum[2]=0;
while(!q.empty())
{
int now=q.front();
q.pop();
sum[col[now]]+=t[now]-v[now];
for(int i=0;i<e[now].size();i++)
{
int nxt=e[now][i];
if(col[nxt]==col[now])
return true;
if(col[nxt])
continue;
col[nxt]=3-col[now];
q.push(nxt);
}
}
return sum[1]==sum[2];
}
int main()
{
int T,n,m,i,a,b;
ll all;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
scanf("%d",&v[i]);
for(i=1;i<=n;i++)
scanf("%d",&t[i]);
for(i=1;i<=n;i++)
{
e[i].clear();
col[i]=0;
}
while(m--)
{
scanf("%d%d",&a,&b);
e[a].push_back(b);
e[b].push_back(a);
}
all=0;
for(i=1;i<=n;i++)
all+=t[i]-v[i];
if(all%2||!check())
printf("NO\n");
else
printf("YES\n");
}
}