A.Raise Both Hands(思维)
题意:
高桥决定做章鱼小丸子并把它端给Snuke。高桥告诉Snuke,如果他想吃章鱼小丸子,只需举起左手,不想吃只需举起右手。
已知两个整数LLL和RRR用于表示Snuke正在举起的手是哪只手。当且仅当L=1L=1L=1时,他正在举起左手;当且仅当R=1R=1R=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(模拟)
题意:
有NNN种编号为1,2,…,N1,2,\ldots,N1,2,…,N的元素。
元素之间可以相互组合。当元素iii和jjj组合在一起时,如果i≥ji\geq ji≥j则变为元素A_i,jA\_{i,j}A_i,j,如果i<ji\lt ji<j则变为元素A_j,iA\_{j,i}A_j,i。
从元素111开始,依次与元素1,2,…,N1,2,\ldots,N1,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(贪心)
题意:
给你两个由小写英文字母组成的字符串SSS和TTT。其中,SSS和TTT的长度相等。
设XXX为空字符串,重复以下操作,直到SSS等于TTT:
- 更改SSS中的一个字符,并将SSS追加到XXX的末尾。
找出这样得到的元素个数最少的字符串数组XXX。如果有多个元素个数相同的数组,请找出其中字典序最小的一个。
分析:
对于总数只要T_i≠S_iT\_i≠S\_iT_i=S_i那它就可以改,所以只要T_i≠S_iT\_i≠S\_iT_i=S_i答案就加一。因此答案为SSS和TTT中S_i≠T_iS\_i≠T\_iS_i=T_i的个数。
考虑操作的顺序。当S_iS\_iS_i被替换为T_iT\_iT_i时,如果是S_i<T_iS\_i\lt T\_iS_i<T_i,那么字符串在字典序上会变大;如果是S_i>T_iS\_i>T\_iS_i>T_i,那么字符串在字典序上会变小。
因此,我们先从头到尾遍历一遍SSS和TTT,如果S_i>T_iS\_i>T\_iS_i>T_i那么就将iii存入数组。接着,我们从尾到头遍历一次,如果S_i<T_iS\_i\lt T\_iS_i<T_i那么将iii存入数组。
代码:
#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(暴力)
题意:
有一个网格,网格中有HHH行和WWW列。(i,j)(i,j)(i,j)表示从上往下第iii行和从左往上第jjj列的单元格。
最初,每个单元格中都有一面墙。按照下面给出的顺序处理QQQ个查询后,求剩余墙的数量。
在第qqq次查询中,我们给出了两个整数R_qR\_qR_q和C_qC\_qC_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_qi\lt R\_qi<R_q,在所有i<k<R_qi\lt k\lt R\_qi<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_qi\gt R\_qi>R_q,使得在所有R_q<k<iR\_q\lt k\lt iR_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_qj\lt C\_qj<C_q,使得在所有j<k<C_qj\lt k\lt C\_qj<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_qj\gt C\_qj>C_q,使得在(R_q,j)(R\_q,j)(R_q,j)处有一堵墙,而在所有的C_q<k<jC\_q\lt k\lt jC_q<k<j中,在(R_q,k)(R\_q,k)(R_q,k)处没有墙,则破坏(R_q,j)(R\_q,j)(R_q,j)处的墙。
-
分析:
观察数据范围发现题目保证h×w≤4×105h×w≤4×10^5h×w≤4×105,所以可以将所有点都存起来。
对每一行和每一列开一个set,存储这一行或这一列中墙的位置。用h_ih\_ih_i表示第iii行的set,用w_iw\_iw_i表示第iii列的set。
对于每次询问,可以查找h_xh\_xh_x中是否有存在yyy,如果有yyy,删掉h_xh\_xh_x中的yyy,删掉w_yw\_yw_y中的xxx。
如果没有yyy,找到h_xh\_xh_x中第一个大于yyy的数,删去这个数并删掉这个数前面的那个数。
同理,找到w_yw\_yw_y中第一个大于xxx的数,删去这个数并删掉这个数前面的那个数。
最后剩下的墙的数量就是每一行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(动态规划)
题意:
给你一个长度为NNN的序列A=(A_1,A_2,…,A_N)A=(A\_1,A\_2,\dots,A\_N)A=(A_1,A_2,…,A_N)和一个整数KKK。 有2N−12^{N-1}2N−1种方法可以将AAA分成几个连续的子序列。在这些分割中,有多少个子序列的元素之和不等于KKK?答案对998244353998244353998244353取模。
在这里,将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+11=i\_1\lt i\_2\lt ⋯\lt i\_k\lt i\_{k+1}=N+11=i_1<i_2<⋯<i_k<i_k+1=N+1。
-
对于每个1≤n≤k1≤n≤k1≤n≤k,第nnn个子序列是通过取AAA中第i_ni\_ni_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_if\_if_i表示前iii个数的答案。若不考虑不合法情况,显然f_i=∑_j=1i−1f_j+1f\_i=\sum \_{j=1}^{i-1}f\_j+1f_i=∑_j=1i−1f_j+1。
但是如果出现s_i−k=s_js\_i−k=s\_js_i−k=s_j(sss表示前缀和),则f_if\_if_i不能从f_jf\_jf_j转移过来。
所以f_if\_if_i是否转移与s_is\_is_i有关。可以用map储存每个sss对应的f_if\_if_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(二分)
题意:
有一个圆形蛋糕,被切割线分割成NNN块。每条切割线都是连接圆心和弧线上一点的线段。
蛋糕块和切割线按顺时针顺序编号为1,2,…,N1,2,\ldots,N1,2,…,N,蛋糕块iii的质量为A_iA\_iA_i。蛋糕块111也被称为蛋糕块N+1N+1N+1。
切割线iii位于蛋糕块iii和i+1i+1i+1之间,它们按顺时针顺序排列为:蛋糕块111,切割线111,蛋糕块222,切割线222,…\ldots…,蛋糕块NNN,切割线NNN。
我们想把这个蛋糕分给KKK个人,条件如下。设w_iw\_iw_i是iii这个人得到的蛋糕的质量总和。
-
每个人都会得到一块或多块连续的蛋糕。
-
不存在有人没得到蛋糕。
-
在上述两个条件下,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)的值,以及满足条件的划分中从未被切割的切割线的数量。如果iii和i+1i+1i+1被分给了不同的人,那么切割线iii就被认为是切割的。
分析:
首先将环断开为链。然后问题转化成了在链上有多少个节点满足1≤i≤n1≤i≤n1≤i≤n,且i+1i+1i+1到i+ni+ni+n划分出来的段和的最小值最大。先考虑求一个询问xxx的时候如何处理。很明显可以二分。
但对于每个断点都二分一次是无法通过的,因此考虑整体二分。对于当前答案区间l,rl,rl,r,对于每个询问考虑从后往前跳,每次找到最近的一个满足当前段和大于等于midmidmid的位置,并调到那个位置上。如果一个位置i+ni+ni+n跳kkk次后仍然位于KaTeX parse error: Can't use function '\]' in math mode at position 9: [i+1,i+n\̲]̲这个区间上,那么iii的答案就大于midmidmid。每次对于当前可能贡献最优解的位置集合都判断一次,如果有答案大于midmidmid的位置就往大的递归,否则往小的递归。
每次二分时,预处理出每个位置的最近的一个位置使得这一段的和大于等于midmidmid,将这个最近的位置设置为父节点。然后用倍增预处理,那么查询时就可以做到log_2klog\_2klog_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,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

被折叠的 条评论
为什么被折叠?



