Codeforces Round 951 (Div. 2) 题解
本文网址:https://www.cnblogs.com/zsc985246/p/18236377 ,转载请注明出处。
好久没更新了,诈尸一下。
挑战最快题解。
B 题刚做完的时候网络爆炸了,掉了一点分。最终排名 22。
题目做起来很爽。大爱思维与构造!抵制数据结构!
2024/6/7update:修改了一些抽风表述(原谅赛时脑子不好使)。
2024/6/9update:更新 F 题解。
传送门
A.Guess the Maximum
题目大意
给定一个长度为 \(n\) 的数组 \(a\),求一个最大的数 \(x\),使得任意一个长度不小于 \(2\) 的连续子串的最大值大于 \(x\)。
多组测试,\(2 \le n \le 5 \times 10^4, 1 \le a_i \le 10^9, T \le 10^4, \sum n \le 5 \times 10^4\)。
思路
最大值尽可能小,那么选择的连续子串越短越优。
枚举所有长度为 \(2\) 的连续子串,求出它们最大值的最小值,然后减去 \(1\) 输出即可。
代码实现
#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=1e6+10;
using namespace std;
ll n,m,k;
ll a[N],b[N];
void mian(){
ll ans=1e9;
scanf("%lld",&n);
For(i,1,n){
scanf("%lld",&a[i]);
if(i>1)ans=min(ans,max(a[i],a[i-1]));
}
printf("%lld\n",ans-1);
}
int main(){
int T=1;
scanf("%d",&T);
while(T--)mian();
return 0;
}
B.XOR Sequences
题目大意
给定两个数 \(x,y\),构造无限长度序列 \(a_n = n \oplus x , b_n = n \oplus y\)。求两个序列的最长公共子序列长度。
多组测试,\(0 \le x,y \le 10^9, x \neq y, T \le 10^4\)。
思路
考虑公共子序列如何形成。
假设现在满足 \(x \oplus n = y \oplus m\)。如果想要 \(n,m\) 自增之后仍然满足条件,那么在自增过程中,\(n,m\) 的每一个二进制位要么同时改变,要么同时不变。
然后考虑 \(n,m\) 的一个满足条件的解:\(n=x,m=y\)。我们将 \(n,m\) 所有相同的位全部变为 \(0\),得到 \(n,m\) 的最小解。
此时 \(n,m\) 能够自增的次数显然是最多的。如果此时 \(n,m\) 的二进制低位连续的 \(0\) 的个数为 \(t_1,t_2\),那么答案就是 \(2^{\min\{t_1,t_2\}}\)。
也就是说,令 \(x,y\) 从最低位开始的连续相同二进制位的个数为 \(t\),那么答案即为 \(2^t\)。
代码实现
#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=1e6+10;
using namespace std;
ll n,m,k;
ll a[N],b[N];
void mian(){
ll ans=0;
scanf("%lld%lld",&n,&m);
while(((n>>ans)&1)==((m>>ans)&1))++ans;
printf("%lld\n",1ll<<ans);
}
int main(){
int T=1;
scanf("%d",&T);
while(T--)mian();
return 0;
}
C.Earning on Bets
题目大意
给定一个长度为 \(n\) 的序列 \(a\),你需要构造一个长度也为 \(n\) 的序列 \(b\),满足 \(\forall i,a_i b_i > \sum_{j=1}^{n} b_j\)。
无解输出 \(-1\)。
多组测试,\(1 \le n \le 50, 2 \le a_i \le 20, T \le 10^4, \sum n \le 2 \times 10^5\)。
思路
显然让所有的 \(a_i b_i\) 都相等时最优。
求出 \(a_i\) 的最大公倍数,然后计算验证是否合法即可。
代码实现
#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=1e6+10;
using namespace std;
ll n,m,k;
ll a[N],b[N];
void mian(){
ll ans=0;
scanf("%lld",&n);
For(i,1,n){
scanf("%lld",&a[i]);
if(i==1)ans=a[i];
else ans=ans/__gcd(ans,a[i])*a[i];
}
ll s=0;
For(i,1,n){
b[i]=ans/a[i];
s+=b[i];
}
if(ans<=s){
printf("-1\n");
return;
}
For(i,1,n){
printf("%lld ",b[i]);
}
printf("\n");
}
int main(){
int T=1;
scanf("%d",&T);
while(T--)mian();
return 0;
}
D.Fixing a Binary String
题目大意
给定一个长度为 \(n\) 的 01 串 \(s\) 和一个整数 \(k\),你需要恰好进行一次操作:
- 选择一个 \(1 \le p \le n\),将序列变为 \(s_{p+1} s_{p+2} \dots s_{n} s_p s_{p-1} \dots s_1\)。
定义一个 01 串 \(s\) 是好的当且仅当满足以下条件:
-
\(s_1=s_2=\dots=s_k\)。
-
\(\forall 1 \le i \le n-k,s_i \neq s_{i+k}\)。
求是否能将 \(s\) 变为好串。如果可以,输出 \(p\),否则输出 \(-1\)。
多组测试,\(2 \le n \le 10^5, 1 \le k \le n, T \le 10^4, \sum n \le 2 \times 10^5\),\(n\) 是 \(k\) 的倍数。
思路
发现操作后 \(s_1\) 总在最后,结合序列的最终条件,考虑从这里入手。
令 \(s[l,r]\) 表示 \(s_l s_{l+1} \dots s_r\)。
为了满足序列的格式,\(s[1,\lceil\frac{p}{k}\rceil \times k]\) 以及 \(s[p+1,\lceil\frac{n-p}{k}\rceil \times k+p]\) 必定是好的 01 串,因为这些地方是操作无法影响到的。
分类讨论。
从前往后看,找到第一个使好串条件不成立的位置 \(tmp\),\(s_{tmp-1}\) 为 \(t\)。
-
\(tmp \not\equiv 1 (\bmod k)\),此时的连续 \(t\) 的个数没有到达 \(k\) 个,从中间划开必定不符合条件,只能选择 \(p=tmp-1\)。
-
\(tmp \equiv 1 (\bmod k)\),此时连续 \(t\) 的个数超过 \(k\) 个,必须从中间划开,所以求出 \(s\) 结尾的连续 \(t\) 个数 \(tot\),然后选择 \(p=tmp-tot-1\)。
代码实现
#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=1e6+10;
using namespace std;
ll n,m,k;
ll a[N],b[N];
ll check(ll ans){//验证b数组是否是好的
ll t=b[1],cnt=1;
For(i,2,n){
if(b[i]==t&&cnt<m)++cnt;
else if(b[i]!=t&&cnt==m)t=b[i],cnt=1;
else return -1;
}
return ans;
}
void mian(){
ll ans=0;
scanf("%lld%lld",&n,&m);
For(i,1,n){
scanf("%1lld",&a[i]);
}
ll t=a[1],cnt=1,tmp=0;//t为当前数,cnt为连续出现次数,tmp为第一个不满足好串的点
For(i,2,n){
if(a[i]==t&&cnt<m)++cnt;
else if(a[i]!=t&&cnt==m)t=a[i],cnt=1;
else{
tmp=i;
break;
}
}
if(tmp==0){//原串就是好的
printf("%lld\n",n);
return;
}
if(cnt!=m){//只能选择p=tmp-1
For(i,tmp,n)b[i-tmp+1]=a[i];
Rep(i,tmp-1,1)b[n-i+1]=a[i];
printf("%lld\n",check(tmp-1));
return;
}
ll tot=0;//统计结尾的连续长度
Rep(i,n,1){
if(a[i]==a[tmp-1])++tot;
else break;
}
if(tot>m){//已经超过了要求数量
printf("-1\n");
return;
}
tmp-=tot;//用m-tot个与结尾拼合
For(i,tmp,n)b[i-tmp+1]=a[i];
Rep(i,tmp-1,1)b[n-i+1]=a[i];
printf("%lld\n",check(tmp-1));
}
int main(){
int T=1;
scanf("%d",&T);
while(T--)mian();
return 0;
}
E.Manhattan Triangle
题目大意
对于两个点 \((x_1,y_1),(x_2,y_2)\),曼哈顿距离为 \(|x_1-x_2|+|y_1-y_2|\)。
给定平面上 \(n\) 个点 \((x_i,y_i)\) 和一个整数 \(k\)。定义三个点形成好的三角形当且仅当任意两点的曼哈顿距离都为 \(k\)。
求出给定的点中的一个好的三角形。若存在,输出三个点的编号;若不存在,输出三个 \(0\)。
多组测试,\(3 \le n \le 10^5, 2 \le k \le 4 \times 10^5, -10^5 \le x_i,y_i \le 10^5, T \le 10^4, \sum n \le 2 \times 10^5\),\(k\) 是偶数。
思路
曼哈顿距离有一个特点:到一个点的曼哈顿距离相同的点组成一个菱形。
根据这个特点可以发现,一个好的三角形,至少有一条过两个点的直线的倾斜角为 \(45^\circ\) 或 \(135^\circ\)。
我们不妨枚举这条直线,从而确定两个点。
倾斜角 \(45^\circ\) 和 \(135^\circ\) 的情况可以分开考虑,只需要让所有点关于 \(y\) 轴对称后重新计算即可。
将处于同一条直线的点用 vector 记录下来,并按照 \(x\) 坐标排好序。
双指针找到曼哈顿距离为 \(k\) 的直线上的两点(它们的 \(x\) 坐标相差 \(\frac{k}{2}\)),然后在离这条直线水平距离为 \(k\) 的两条直线上分别二分找到三角形的第三个点。
复杂度 \(O(n \log n)\)。
代码实现
注意下标加上一个较大的数防止变成负数。
#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
#define pb push_back
const ll N=1e6+10;
using namespace std;
ll BIG=300000;//一个较大的数
ll n,k;
ll a[N],b[N];
ll m,f[N];
vector<ll>t[N];
struct node{
ll a,b,c;
}ans;
bool cmp(ll x,ll y){
return a[x]<a[y];
}
ll find(ll pos,ll x,ll y){//二分
if(pos<100000||pos>500000)return 0;//防止下标溢出
ll l=0,r=(ll)t[pos].size()-1,res=0;
while(l<=r){
ll mid=(l+r)>>1;
if(a[t[pos][mid]]>=x)res=t[pos][mid],r=mid-1;
else l=mid+1;
}
if(res&&a[res]<=y)return res;
else return 0;
}
void calc(){
m=0;
For(i,1,n){
f[++m]=a[i]-b[i]+BIG;
t[a[i]-b[i]+BIG].pb(i);
}
//离散化
sort(f+1,f+m+1);
m=unique(f+1,f+m+1)-f-1;
For(i,1,m)sort(t[f[i]].begin(),t[f[i]].end(),cmp);//按x坐标排序
For(i,1,m){
ll y=0;
For(x,0,(ll)t[f[i]].size()-1){
while(y<(ll)t[f[i]].size()&&a[t[f[i]][y]]-a[t[f[i]][x]]<k/2)++y;
if(y>=(ll)t[f[i]].size())break;
if(a[t[f[i]][y]]-a[t[f[i]][x]]==k/2){
ll t1=find(f[i]-k,a[t[f[i]][x]]*2-a[t[f[i]][y]],a[t[f[i]][x]]);
ll t2=find(f[i]+k,a[t[f[i]][y]],a[t[f[i]][y]]*2-a[t[f[i]][x]]);
if(t1)ans=(node){t[f[i]][x],t[f[i]][y],t1};
if(t2)ans=(node){t[f[i]][x],t[f[i]][y],t2};
}
}
}
For(i,1,m)t[f[i]].clear();
}
void mian(){
ans=(node){0,0,0};
scanf("%lld%lld",&n,&k);
For(i,1,n){
scanf("%lld%lld",&a[i],&b[i]);
}
calc();
For(i,1,n)a[i]=-a[i];
calc();
printf("%lld %lld %lld\n",ans.a,ans.b,ans.c);
}
int main(){
int T=1;
scanf("%d",&T);
while(T--)mian();
return 0;
}
F.Kostyanych's Theorem
题目大意
交互题。
交互库会有一个 \(n\) 个点的完全无向图,并从中删除恰好 \(n-2\) 条边。现在给定 \(n\),你需要进行不超过 \(n\) 次以下询问:
-
"? \(d\)":交互库会找到编号最小的度数不小于 \(d\) 的节点 \(x\),并找到编号最小的不与 \(x\) 直接相连的节点 \(y\)。
-
如果 \(x\) 不存在,则返回 \(0\ 0\);
-
如果 \(x\) 存在但 \(y\) 不存在,从图中删除点 \(x\),返回 \(x\ 0\);
-
否则从图中删除点 \(x\),返回 \(x\ y\)。
-
你需要找到一条路径,经过原图上的每个点各一次。输出格式如下:
- "! \(x_1\) \(x_2\) \(\dots\) \(x_n\)":\(x_i\) 表示这条路径依次经过的点。
多组测试,\(2 \le n \le 10^5, T \le 10^4, \sum n \le 10^5\)。
思路
由于我们的查询次数有限,所以我们必须尽可能保证不做无效查询(返回 \(0\ 0\) 的查询)。
要做到这一点,我们需要知道图中节点的最大度数最小是多少。
我们知道图的边数是 \(m=\frac{n(n-1)}{2}-(n-2)=\frac{n^2-3n+4}{2}\)。
假设每个节点的度数都为 \(t\),那么图的边数为 \(m_1=\frac{nt}{2}\)。
当 \(m_1<m\) 时,\(t\) 最大为 \(n-3\),也就是说至少有一个节点的度数大于 \(n-3\),也至少一个节点的度数小于等于 \(n-3\)。
接下来考虑如何构造路径。
由于每次查询之后,交互库会将点 \(x\) 删除,考虑递归处理这个问题。边界条件为 \(n=1\) 或 \(n=2\)。我们用双端队列记录路径。
尝试查询 \(d=n-2\)。分类讨论。
-
\(y=0\):
没有度数为 \(n-2\) 的点,此时返回的 \(x\) 度数为 \(n-1\)。
那么最优考虑下,我们找到一个度数最小的点 \(z\),并将 \(z \to x \to\) 加入路径。
也就是说我们再进行查询 \(d'=0\) 即可。
但此时是不是变成了子问题呢?我们验证一下。
由于图中至少一个点的度数小于等于 \(n-3\),点 \(z\) 度数一定小于等于 \(n-3\)。
那么此时边数至少为 \(m'=m-(n-1)-(n-3)+1=\frac{n^2-7n+14}{2}=\frac{(n-2)^2-3(n-2)+4}{2}\)。
说明这是一个子问题。
-
\(y \neq 0\):
返回的 \(x\) 度数为 \(n-2\)。
此时我们知道 \(x\) 仅与 \(y\) 之间没有连边。
而路径有两个端点,我们将 \(x\) 插入到有连边的一端即可。
由于此时 \(n \ge 3\),递归返回的路径长度 \(l \ge 2\),所以端点必然不相同。
同样可以证明这是一个子问题。
最后输出路径就可以了。
代码实现
#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=1e6+10;
using namespace std;
ll n;
deque<ll>ans;
pair<ll,ll> ask(ll d){//查询
printf("? %lld\n",d);fflush(stdout);
ll x,y;
scanf("%lld %lld",&x,&y);
return {x,y};
}
void calc(ll n){//递归处理
if(n==1){
ans.push_back(ask(0).first);
return;
}
if(n==2){
ans.push_back(ask(0).first);
ans.push_back(ask(0).first);
return;
}
pair<ll,ll>t=ask(n-2);
ll x=t.first,y=t.second;
if(y==0){
ll z=ask(0).first;
calc(n-2);
ans.push_front(x);
ans.push_front(z);
}else{
calc(n-1);
if(y==ans.front())ans.push_back(x);
else ans.push_front(x);
}
}
void mian(){
scanf("%lld",&n);
calc(n);
printf("! ");
while(ans.size()){
printf("%lld ",ans.front());
ans.pop_front();
}
printf("\n");
fflush(stdout);
}
int main(){
int T=1;
scanf("%d",&T);
while(T--)mian();
return 0;
}
尾声
如果有什么问题,可以直接评论!