A.Raise Both Hands(思维)
题意:
高桥决定做章鱼小丸子并把它端给Snuke。高桥告诉Snuke,如果他想吃章鱼小丸子,只需举起左手,不想吃只需举起右手。
已知两个整数 L L L和 R R R用于表示Snuke正在举起的手是哪只手。当且仅当 L = 1 L=1 L=1时,他正在举起左手;当且仅当 R = 1 R=1 R=1时,他正在举起右手。他可能不遵循指示,同时抬起双手或根本不抬任何一只手。
如果Snuke只抬了一只手,请输出Yes
表示他想吃章鱼小丸子,输出No
表示不想吃。如果他同时抬双手或者没有抬任何一只手,请输出Invalid
。
假设Snuke只抬了一只手,则始终遵循指示。
分析:
按照题意,分三种情况判断即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=100;
const int MOD=1000000007;
void solve(){
int l,r;
cin>>l>>r;
if(l==1 && r==0)
cout<<"Yes"<<endl;
else if(l==0 && r==1)
cout<<"No"<<endl;
else
cout<<"Invalid"<<endl;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
B.Binary Alchemy(模拟)
题意:
有 N N N种编号为 1 , 2 , … , N 1,2,\ldots,N 1,2,…,N的元素。
元素之间可以相互组合。当元素 i i i和 j j j组合在一起时,如果 i ≥ j i\geq j i≥j则变为元素 A _ i , j A\_{i,j} A_i,j,如果 i < j i\lt j i<j则变为元素 A _ j , i A\_{j,i} A_j,i。
从元素 1 1 1开始,依次与元素 1 , 2 , … , N 1,2,\ldots,N 1,2,…,N结合。求最后得到的元素。
分析:
按照题意查表,模拟合成过程。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=100;
const int MOD=1000000007;
void solve(){
int n;
cin>>n;
vector<vector<int>>a(n);
for (int i=0;i<n;i++) {
a[i].resize(i+1);
for(auto& x:a[i]) {
cin>>x;
--x;
}
}
int tmp=0;
for(int i=0;i<n;++i) {
int x=tmp,y=i;
if(x<y)
swap(x,y);
tmp=a[x][y];
}
cout<<tmp+1<<endl;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
C.Word Ladder(贪心)
题意:
给你两个由小写英文字母组成的字符串 S S S和 T T T。其中, S S S和 T T T的长度相等。
设 X X X为空字符串,重复以下操作,直到 S S S等于 T T T:
- 更改 S S S中的一个字符,并将 S S S追加到 X X X的末尾。
找出这样得到的元素个数最少的字符串数组 X X X。如果有多个元素个数相同的数组,请找出其中字典序最小的一个。
分析:
对于总数只要 T _ i ≠ S _ i T\_i≠S\_i T_i=S_i那它就可以改,所以只要 T _ i ≠ S _ i T\_i≠S\_i T_i=S_i答案就加一。因此答案为 S S S和 T T T中 S _ i ≠ T _ i S\_i≠T\_i S_i=T_i的个数。
考虑操作的顺序。当 S _ i S\_i S_i被替换为 T _ i T\_i T_i时,如果是 S _ i < T _ i S\_i\lt T\_i S_i<T_i,那么字符串在字典序上会变大;如果是 S _ i > T _ i S\_i>T\_i S_i>T_i,那么字符串在字典序上会变小。
因此,我们先从头到尾遍历一遍 S S S和 T T T,如果 S _ i > T _ i S\_i>T\_i S_i>T_i那么就将 i i i存入数组。接着,我们从尾到头遍历一次,如果 S _ i < T _ i S\_i\lt T\_i S_i<T_i那么将 i i i存入数组。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=20010;
const int MOD=1000000007;
int a[N],cnt;
void solve(){
string sa,sb;
cin>>sa>>sb;
int n=sa.length();
for(int i=0;i<=n-1;i++){
if(sa[i]>sb[i])
a[++cnt]=i;
}
for(int i=n-1;i>=0;i--){
if(sa[i]<sb[i])
a[++cnt]=i;
}
cout<<cnt<<endl;
for(int i=1;i<=cnt;i++){
sa[a[i]]=sb[a[i]];
cout<<sa<<endl;
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
D.Cross Explosion(暴力)
题意:
有一个网格,网格中有 H H H行和 W W W列。 ( i , j ) (i,j) (i,j)表示从上往下第 i i i行和从左往上第 j j j列的单元格。
最初,每个单元格中都有一面墙。按照下面给出的顺序处理 Q Q Q个查询后,求剩余墙的数量。
在第 q q q次查询中,我们给出了两个整数 R _ q R\_q R_q和 C _ q C\_q C_q。 在 ( R _ q , C _ q ) (R\_q,C\_q) (R_q,C_q)处放置炸弹来摧毁墙壁。结果会发生以下情况:
-
如果 ( R _ q , C _ q ) (R\_q,C\_q) (R_q,C_q)处有一堵墙,则摧毁这堵墙并结束进程。
-
分类讨论:
-
如果存在 i < R _ q i\lt R\_q i<R_q,在所有 i < k < R _ q i\lt k\lt R\_q i<k<R_q中, ( i , C _ q ) (i,C\_q) (i,C_q)处有一堵墙,而 ( k , C _ q ) (k,C\_q) (k,C_q)处没有墙,则摧毁 ( i , C _ q ) (i,C\_q) (i,C_q)处的墙。
-
如果存在 i > R _ q i\gt R\_q i>R_q,使得在所有 R _ q < k < i R\_q\lt k\lt i R_q<k<i中, ( i , C _ q ) (i,C\_q) (i,C_q)处有一堵墙,而 ( k , C _ q ) (k,C\_q) (k,C_q)处没有墙,则破坏 ( i , C _ q ) (i,C\_q) (i,C_q)处的墙。
-
如果存在 j < C _ q j\lt C\_q j<C_q,使得在所有 j < k < C _ q j\lt k\lt C\_q j<k<C_q中, ( R _ q , j ) (R\_q,j) (R_q,j)处有一堵墙,而 ( R _ q , k ) (R\_q,k) (R_q,k)处没有墙,则破坏 ( R _ q , j ) (R\_q,j) (R_q,j)处的墙。
-
如果存在 j > C _ q j\gt C\_q j>C_q,使得在 ( R _ q , j ) (R\_q,j) (R_q,j)处有一堵墙,而在所有的 C _ q < k < j C\_q\lt k\lt j C_q<k<j中,在 ( R _ q , k ) (R\_q,k) (R_q,k)处没有墙,则破坏 ( R _ q , j ) (R\_q,j) (R_q,j)处的墙。
-
分析:
观察数据范围发现题目保证 h × w ≤ 4 × 1 0 5 h×w≤4×10^5 h×w≤4×105,所以可以将所有点都存起来。
对每一行和每一列开一个set
,存储这一行或这一列中墙的位置。用
h
_
i
h\_i
h_i表示第
i
i
i行的set
,用
w
_
i
w\_i
w_i表示第
i
i
i列的set
。
对于每次询问,可以查找 h _ x h\_x h_x中是否有存在 y y y,如果有 y y y,删掉 h _ x h\_x h_x中的 y y y,删掉 w _ y w\_y w_y中的 x x x。
如果没有 y y y,找到 h _ x h\_x h_x中第一个大于 y y y的数,删去这个数并删掉这个数前面的那个数。
同理,找到 w _ y w\_y w_y中第一个大于 x x x的数,删去这个数并删掉这个数前面的那个数。
最后剩下的墙的数量就是每一行set
的大小之和。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=4e5+10;
const int MOD=1000000007;
set <int> h[N], w[N];
int n,m,q;
void solve(){
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
h[i].insert(j);
w[j].insert(i);
}
cin>>q;
while(q--) {
int x,y;
cin>>x>>y;
if(h[x].find(y) != h[x].end()){
h[x].erase(y);
w[y].erase(x);
}
else{
auto i = h[x].lower_bound(y);
vector <int> v;
if(i!=h[x].end()) v.push_back(*i);
if(i!=h[x].begin()) v.push_back(*(--i));
for(auto j : v) {
h[x].erase (j);
w[j].erase (x);
}
v.clear();
i=w[y].lower_bound(x);
if(i!=w[y].end()) v.push_back(*i);
if(i!=w[y].begin()) v.push_back(*(--i));
for(auto j : v) {
w[y].erase(j);
h[j].erase(y);
}
}
}
int ans=0;
for(int i=1;i<=n;i++)
ans+=h[i].size();
cout<<ans<<endl;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
E.Avoid K Partition(动态规划)
题意:
给你一个长度为 N N N的序列 A = ( A _ 1 , A _ 2 , … , A _ N ) A=(A\_1,A\_2,\dots,A\_N) A=(A_1,A_2,…,A_N)和一个整数 K K K。 有 2 N − 1 2^{N-1} 2N−1种方法可以将 A A A分成几个连续的子序列。在这些分割中,有多少个子序列的元素之和不等于 K K K?答案对 998244353 998244353 998244353取模。
在这里,将A分成几个连续的子序列
意味着以下过程。
-
自由选择子序列的数量 k ( 1 ≤ k ≤ N ) k(1≤k≤N) k(1≤k≤N)和一个整数序列 ( i _ 1 , i _ 2 , … , i _ k , i _ k + 1 ) (i\_1,i\_2,…,i\_k,i\_{k+1}) (i_1,i_2,…,i_k,i_k+1),满足 1 = i _ 1 < i _ 2 < ⋯ < i _ k < i _ k + 1 = N + 1 1=i\_1\lt i\_2\lt ⋯\lt i\_k\lt i\_{k+1}=N+1 1=i_1<i_2<⋯<i_k<i_k+1=N+1。
-
对于每个 1 ≤ n ≤ k 1≤n≤k 1≤n≤k,第 n n n个子序列是通过取 A A A中第 i _ n i\_n i_n到 ( i _ n + 1 − 1 ) (i\_{n+1}−1) (i_n+1−1)的元素所形成的。保持其顺序不变。
下面是 A = ( 1 , 2 , 3 , 4 , 5 ) A=(1,2,3,4,5) A=(1,2,3,4,5)的一些分割示例:
-
( 1 , 2 , 3 ) , ( 4 ) , ( 5 ) (1,2,3),(4),(5) (1,2,3),(4),(5)
-
( 1 , 2 ) , ( 3 , 4 , 5 ) (1,2),(3,4,5) (1,2),(3,4,5)
-
( 1 , 2 , 3 , 4 , 5 ) (1,2,3,4,5) (1,2,3,4,5)
分析:
本题我们考虑动态规划,设 f _ i f\_i f_i表示前 i i i个数的答案。若不考虑不合法情况,显然 f _ i = ∑ _ j = 1 i − 1 f _ j + 1 f\_i=\sum \_{j=1}^{i-1}f\_j+1 f_i=∑_j=1i−1f_j+1。
但是如果出现 s _ i − k = s _ j s\_i−k=s\_j s_i−k=s_j( s s s表示前缀和),则 f _ i f\_i f_i不能从 f _ j f\_j f_j转移过来。
所以
f
_
i
f\_i
f_i是否转移与
s
_
i
s\_i
s_i有关。可以用map
储存每个
s
s
s对应的
f
_
i
f\_i
f_i的和,转移时减去。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL N=200010;
const LL MOD=998244353;
LL n,k,a[N],s,s2,f[N];
map<LL,LL>mp;
void solve(){
cin>>n>>k;
for(LL i=1;i<=n;i++){
cin>>a[i];
s+=a[i];
f[i]=((s!=k)+s2-mp[s-k]+MOD)%MOD;
s2=(s2+f[i])%MOD;
mp[s]=(mp[s]+f[i])%MOD;
}
cout<<f[n]<<endl;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
F.Cake Division(二分)
题意:
有一个圆形蛋糕,被切割线分割成 N N N块。每条切割线都是连接圆心和弧线上一点的线段。
蛋糕块和切割线按顺时针顺序编号为 1 , 2 , … , N 1,2,\ldots,N 1,2,…,N,蛋糕块 i i i的质量为 A _ i A\_i A_i。蛋糕块 1 1 1也被称为蛋糕块 N + 1 N+1 N+1。
切割线 i i i位于蛋糕块 i i i和 i + 1 i+1 i+1之间,它们按顺时针顺序排列为:蛋糕块 1 1 1,切割线 1 1 1,蛋糕块 2 2 2,切割线 2 2 2, … \ldots …,蛋糕块 N N N,切割线 N N N。
我们想把这个蛋糕分给 K K K个人,条件如下。设 w _ i w\_i w_i是 i i i这个人得到的蛋糕的质量总和。
-
每个人都会得到一块或多块连续的蛋糕。
-
不存在有人没得到蛋糕。
-
在上述两个条件下, min ( w _ 1 , w _ 2 , … , w _ K ) \min(w\_1,w\_2,\ldots,w\_K) min(w_1,w_2,…,w_K)最大。
求满足条件的划分中 min ( w _ 1 , w _ 2 , … , w _ K ) \min(w\_1,w\_2,\ldots,w\_K) min(w_1,w_2,…,w_K)的值,以及满足条件的划分中从未被切割的切割线的数量。如果 i i i和 i + 1 i+1 i+1被分给了不同的人,那么切割线 i i i就被认为是切割的。
分析:
首先将环断开为链。然后问题转化成了在链上有多少个节点满足 1 ≤ i ≤ n 1≤i≤n 1≤i≤n,且 i + 1 i+1 i+1到 i + n i+n i+n划分出来的段和的最小值最大。先考虑求一个询问 x x x的时候如何处理。很明显可以二分。
但对于每个断点都二分一次是无法通过的,因此考虑整体二分。对于当前答案区间 l , r l,r l,r,对于每个询问考虑从后往前跳,每次找到最近的一个满足当前段和大于等于 m i d mid mid的位置,并调到那个位置上。如果一个位置 i + n i+n i+n跳 k k k次后仍然位于KaTeX parse error: Can't use function '\]' in math mode at position 9: [i+1,i+n\̲]̲这个区间上,那么 i i i的答案就大于 m i d mid mid。每次对于当前可能贡献最优解的位置集合都判断一次,如果有答案大于 m i d mid mid的位置就往大的递归,否则往小的递归。
每次二分时,预处理出每个位置的最近的一个位置使得这一段的和大于等于 m i d mid mid,将这个最近的位置设置为父节点。然后用倍增预处理,那么查询时就可以做到 l o g _ 2 k log\_2k log_2k。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=1e6+5;
LL n,k;
LL pr[N];
LL a[N];
bool in[N];
int fat[N][21];
int sol1(int x,int y){
for(int i=20;i>=0;i--){
if(y&(1<<i)){
x=fat[x][i];
}
}return x;
}
void deal(LL l,LL r,vector<int>q){
LL mid=(l+r+1)>>1;
if(l==r){
cout<<l<<" ";
for(int i=0;i<q.size();i++){
in[q[i]]=true;
}
cout<<n-q.size();
return;
}
for(int i=1;i<=n*2;i++){
int L=1,R=i;
fat[i][0]=0;
if(pr[i]<mid){
continue;
}
while(L<R){
int mi=(L+R+1)>>1;
if(pr[i]-pr[mi-1]>=mid){
L=mi;
}else{
R=mi-1;
}
}
fat[i][0]=L-1;
}
for(int i=1;i<=20;i++){
for(int j=1;j<=2*n;j++){
fat[j][i]=fat[fat[j][i-1]][i-1];
}
}
vector<int>t1,t2;
for(int i=0;i<q.size();i++){
if(sol1(q[i]+n,k)>=q[i]){
t2.push_back(q[i]);
}else{
t1.push_back(q[i]);
}
}
if(t2.empty()){
deal(l,mid-1,t1);
}else{
deal(mid,r,t2);
}
}
void solve(){
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
a[i+n]=a[i];
}
for(int i=1;i<=n*2;i++)
pr[i]=pr[i-1]+a[i];
vector<int>t;
for(int i=1;i<=n;i++){
t.push_back(i);
}
deal(1,pr[n],t);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。