一、前言
自从 上次 CF 怒涨百分 之后,我的大号(Timmyliuyunxi)终于又一次迎来了大爆发,怒涨两百余分,这可能是我之前和一年之后的最好的一次表现了,甚至封存在了 CF vlog 中,真是倍感荣幸。
我发现自己的表现异常不稳定, rating 如同波浪般跌宕起伏,涨分全靠 Rp,掉分积攒 Rp。至于这次的 Rp,可能是模拟赛爆零,期末考试没有考好,亦或是因迟到获得处分......
咳咳,我们言归正传。比赛链接。
二、题解
第 A 题 Table with Numbers
本题题目有些细节,我差点因为读错题而耽误时间。
题意为给定一些数 n u m 1 , n u m 2 , ... , n u m x num_1,num_2,\dots,num_x num1,num2,...,numx和 n , m n,m n,m,问最多能组成多少个数对 ( a , b ) (a,b) (a,b),使得 a ≤ n a\le n a≤n 且 b ≤ m b\le m b≤m。
考虑贪心,我们不妨设 n ≤ m n\le m n≤m(否则交换数对顺序),则我们的 ( a , b ) (a,b) (a,b) 也满足 a ≤ b a\le b a≤b。
此时我们的限制就是 a , b ≤ m a,b\le m a,b≤m 和 a ≤ n a\le n a≤n。
前者限制条件为 a n s ≤ ∑ [ n u m x ≤ m ] 2 ans\le \frac{\sum [num_x\le m]}{2} ans≤2∑[numx≤m];后者限制条件为 a n s ≤ ∑ [ n u m x ≤ n ] ans\le\sum[num_x\le n] ans≤∑[numx≤n]。
取最小值输出即可。
代码:
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int t; cin>>t;
while (t--){
int n,x,y,a=0,b=0; cin>>n>>x>>y;
for (int i=1; i<=n; i++){
int val; cin>>val;
if (val<=min(x,y)) a++,b++;
else if (val<=max(x,y)) a++;
}
cout<<min(a/2,b)<<"\n";
}
}
第 B 题 The Curse of the Frog
题意为给你一只青蛙,它有 n n n 种移动方法,第 i i i 种是向前跳不超过 a i a_i ai 步,但每 b i b_i bi 次会被击退 c i c_i ci 步,问至少击退几次可以走超过 m m m 步。
仍旧贪心。我们先把每种移动方式移动 b i − 1 b_i-1 bi−1 次,此时没有被击退。
接下来青蛙可以用一次击退的代价来前进 a i × b i − c i a_i\times b_i-c_i ai×bi−ci 步,贪心选择最大的走即可。 注意一下无解是所有的前进步数都为负且初始并未到达目标。
代码:
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int t; cin>>t;
while (t--){
int n,m,a=0,b=-1000000000; cin>>n>>m;
for (int i=1; i<=n; i++){
int x,y,z; cin>>x>>y>>z;
a+=x*(y-1); b=max(b,x*y-z);
}
if (a>=m) cout<<"0\n";
else if (b<=0) cout<<"-1\n";
else cout<<(m-a+b-1)/b<<"\n";
}
}
第 C1 题 XOR Convenience (Easy Version)
小清新构造。题意简单,这里就不说了。
我们观察题目的限制,若 a n = 1 a_n=1 an=1 则更易成功。
我们尝试构造满足 a i ⊕ i = 1 a_i \oplus i=1 ai⊕i=1 的 a a a(首、尾)除外。
易得 a i = i ⊕ 1 a_i =i\oplus 1 ai=i⊕1 ,我们发现这个 i ⊕ 1 i\oplus 1 i⊕1 在 2 ≤ i < n 2\le i<n 2≤i<n 时,它的值在 [ 2 , n ] [2,n] [2,n] 之间且不会重复。直接构造或分奇偶性构造即可。
代码:
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int t; cin>>t;
while (t--){
int n; cin>>n;
if (n==1){
cout<<1<<"\n";
continue;
}
if (n%2==0){
cout<<n<<" ";
for (int i=2; i<n; i+=2){
cout<<i+1<<" "<<i<<" ";
}
cout<<1<<"\n";
}
else{
cout<<n-1<<" ";
for (int i=2; i<n-1; i+=2){
cout<<i+1<<" "<<i<<" ";
}
cout<<n<<" 1\n";
}
}
}
第 C2 题 XOR Convenience (Hard Version)
小清新构造。题意简单,这里就不说了。
考虑 C1 的想法,但是这里出现了第一个数的限制,也就是说第一个数我们也要满足条件。 观察第一个数,由于 a 1 ⊕ 1 ≠ a 1 ≠ 0 a_1\oplus 1\neq a_1\neq 0 a1⊕1=a1=0,所以我们只需要保证 a 1 ⊕ 1 ≠ n + 1 a_1\oplus 1\neq n+1 a1⊕1=n+1 即可。
我们首先构造出一组 C1 的解,设为 a a a。
那么若第一个数不满足条件,则交换 a 1 a_1 a1 和 a x a_x ax 使得 a x = a 1 & ( a 1 − 1 ) a_x=a_1 \& (a_1-1) ax=a1&(a1−1)。
那么我们这次操作会让 1 1 1 脱离危险, a x ⊕ x a_x\oplus x ax⊕x 增加 a 1 − a 1 & ( a 1 − 1 ) a_1-a_1\&(a_1-1) a1−a1&(a1−1),易得其大于 x x x,那么容易发现合法。
特殊的,当 n = 2 i n=2^i n=2i 时,无解,因为若其放置在最后一个位置,那么倒数第二个位置非法,否则这个位置非法。
代码:
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int t; cin>>t;
while (t--){
int n; cin>>n;
if (n==1){
cout<<1<<"\n";
continue;
}
if (n%2==0){
int val=(n&(n-1));
if (val==0) cout<<"-1\n";
else{
int x=min(val,n-val);
cout<<x<<" ";
for (int i=2; i<n; i+=2){
cout<<(i+1==x?n:i+1)<<" "<<(i==x?n:i)<<" ";
}
cout<<1<<"\n";
}
}
else{
cout<<n-1<<" ";
for (int i=2; i<n-1; i+=2){
cout<<i+1<<" "<<i<<" ";
}
cout<<n<<" 1\n";
}
}
}
第 D1 题 Little String (Easy Version)
小清新计数,题意简单,就不多说了。
考虑动态插入点。那么我们发现若 s i = 0 s_i=0 si=0,则 i + 1 i+1 i+1 处于 1 1 1 到 n n n 最左侧的最右侧之间。否则不在它们之间。
若 s i = 0 s_i=0 si=0,则答案乘 i i i,否则乘 2 2 2。 特殊的 s n s_n sn 一定为 1 1 1,否则不合法。
模拟即可,注意答案不是模 c c c 而是 998244353 998244353 998244353。
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define M 1000000007
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int t; cin>>t;
while (t--){
int n,m; cin>>n>>m;
int ans=1,ans2=1;
string s; cin>>s;
if (s[0]!='1'||s[n-1]!='1'){
cout<<"-1\n";
continue;
}
for (int i=0; i<n-1; i++){
if (s[i]=='1') ans=ans*2%m,ans2=ans2*2%M;
else ans=ans*i%m,ans2=ans2*i%M;
}
ans%=m;
if (ans==0) cout<<"-1\n";
else cout<<ans2<<"\n";
}
}
第 D2 题 Little String (Hard Version)
考虑 D1,那么我们的任务是补全 ? 使得其不是 c c c 的倍数。
设 c = 2 a × b c=2^a\times b c=2a×b,我们已经填的乘积为 x x x,还有 y y y 个数。则我们分一些情况讨论。若 x × 2 y m o d c ≠ 0 x\times2^y \mod c\neq 0 x×2ymodc=0,则我们都填 2 2 2。
否则我们先尽可能填 2 2 2,填不动后尝试填小的奇数,容易发现这是正确的。
代码:
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define M 1000000007
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int t; cin>>t;
while (t--){
int n,m; cin>>n>>m;
int ans=1,ans2=1;
string s; cin>>s;
if (s[0]=='0'||s[n-1]=='0'){
cout<<"-1\n";
continue;
}
if (s[0]=='?') s[0]='1';
for (int i=0; i<n-1; i++){
if (s[i]=='1') ans=ans*2%m,ans2=ans2*2%M;
else if (s[i]=='0') ans=ans*i%m,ans2=ans2*i%M;
}
for (int i=0; i<n-1; i+=2){
if (s[i]=='?') ans=ans*2%m,ans2=ans2*2%M;
}
ans%=m;
if (ans==0) cout<<"-1\n";
else{
for (int i=(n-2)-(n%2==0); i>1; i-=2){
if (s[i]!='?') continue;
if (ans*2%m!=0) ans=ans*2%m,ans2=ans2*2%M;
else ans=ans*i%m,ans2=ans2*i%M;
}
cout<<ans2<<"\n";
}
}
}
第 E 题 Majority Wins?
大粪套,但是题目题意较为简单。
考虑多种情况。
Case 0 ∣ s ∣ = 1 |s|=1 ∣s∣=1
参见样例。
Case 1 无解
没有 1 1 1,否则我们可以将左右两边缩成 0 0 0,然后左右分别将 01 01 01 变为 1 1 1 即可,这时代价为 n + 1 n+1 n+1。
Case 2 答案为 n n n
只能操作整个串,只需要满足整个字符串中 1 1 1 的个数不少于 0 0 0 的个数。
Case 3 答案为 n + 1 n+1 n+1
只可能操作一个前后缀,然后操作整个串,前缀和即可。
Case 4 答案为 n + 2 n+2 n+2
如果某一个前后缀能变成 1 1 1,那么剩下的一段变为 0 0 0,合并即可。
还有一种情况 (这是我唯一一次罚时的地方)就是两个 1 1 1,左边合一下,右边合一下,最后将这个 0110 0110 0110 合并即可。
Case 5 剩余情况
根据 Case1 的构造,这时答案为 n + 3 n+3 n+3。
代码:
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
int pre[5000010],suf[5000010];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int t; cin>>t;
while (t--){
int n; cin>>n;
string s; cin>>s; s=" "+s;
for (int i=1; i<=n; i++){
pre[i]=pre[i-1]+(s[i]=='1'?1:-1);
}
suf[n+1]=0;
for (int i=n; i>=1; i--){
suf[i]=suf[i+1]+(s[i]=='1'?1:-1);
}
if (n==1&&pre[n]==1) cout<<"0\n";
else if (pre[n]==-n) cout<<"-1\n";
else if (pre[n]>=0) cout<<n<<"\n";
else{
bool ok0=0;
for (int i=1; i<n; i++){
if ((pre[i]<0?-1:1)+suf[i+1]>=0) ok0=1;
}
for (int i=2; i<=n; i++){
if ((suf[i]<0?-1:1)+pre[i-1]>=0) ok0=1;
}
bool ok1=0;
for (int i=1; i<=n; i++){
if (pre[i]>=0||suf[i]>=0||(s[i]=='1'&&s[i-1]=='1')) ok1=1;
}
if (ok0==1) cout<<n+1<<"\n";
else if (ok1) cout<<n+2<<"\n";
else cout<<n+3<<"\n";
}
}
}
三、后记
题目以贪心、DP、思维为主,都是我擅长的,下次考个别的我就不会了。
顺便推荐一下 LYXOI(洛谷 92018)我们会举办 SFFC(春节解密赛),欢迎各位抽出宝贵的时间参加。 LYXOI 链接。