Codeforces Round 951 (Div. 2) 题解

本文网址:https://www.cnblogs.com/zsc985246/p/18236377 ,转载请注明出处。

好久没更新了,诈尸一下。

挑战最快题解。

B 题刚做完的时候网络爆炸了,掉了一点分。最终排名 22。

题目做起来很爽。大爱思维与构造!抵制数据结构!

2024/6/7update:修改了一些抽风表述(原谅赛时脑子不好使)。

2024/6/9update:更新 F 题解。

传送门

Codeforces Round 951 (Div. 2)

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;
}

尾声

如果有什么问题,可以直接评论!

posted @ 2024-06-07 00:44  zsc985246  阅读(1179)  评论(3编辑  收藏  举报