【题解】Codeforces Round 890(CF1856)

发布时间 2023-08-06 21:22:15作者: linyihdfj

赛时过了 A-E1,rk195
可惜是 E2 傻逼了不会背包优化了,直接连普及组水平都不到了。

A.Tales of a Sort

题目描述:

给定长度为 \(n\) 的序列 \(a\),每次操作为对于所有 \(i\)\(a_i\) 变为 \(\max(a_i-1,0)\),询问最少多少次操作之后可以让序列 \(a\) 单调不降。

题目分析:

唯一能改变元素大小关系的就是将数变成 \(0\),所以可以考虑找到结尾的最长不降序列,然后将其前面的全部变成 \(0\) 就好了。
答案就是这些数里的最大值。

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5+5;
int a[N];
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int T;scanf("%lld",&T);
	while(T--){
		int n;scanf("%lld",&n);
		for(int i=1; i<=n; i++)	scanf("%lld",&a[i]);
		int pos = 0;
		for(int i=n; i>=1; i--){
			if(a[i-1] > a[i]){
				pos = i;break;
			}
		}
		int ans = 0;
		for(int i=1; i<pos; i++)	ans = max(ans,a[i]);
		printf("%lld\n",ans);
	}
	return 0;
}

B.Good Arrays

题目描述:

给定一个长度为 \(n\) 的序列 \(a\),询问是否存在一个长度为 \(n\) 的序列 \(b\) 满足:

  • \(\forall i\in [1,n]\) \(a_i \not= b_i\)
  • \(\sum_{i=1}^n a_i = \sum_{i=1}^n b_i\)

题目分析:

考虑调整,一定是一些数变大另一些数变小。
所以直接将所有 \(a_i = 1\) 的位置的 \(b_i\) 设为 \(2\) 其余部分的 \(b_i\) 设为 \(1\),显然这一定是一个和最小的方案。
如果此时 \(\sum_i^n b_i > \sum_{i}^n a_i\),那么也就无解了。
否则如果 \(\sum_i^n b_i \le \sum_i^n a_i\),就可以通过调整 \(a_i = 1\) 的位置对应的 \(b_i\) 的值使得和满足条件。

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5+5;
int a[N],b[N];
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int T;scanf("%lld",&T);
	while(T--){
		int n;scanf("%lld",&n);
		int sum = 0;
		for(int i=1; i<=n; i++)	scanf("%lld",&a[i]),sum += a[i];
		if(n == 1){
			printf("NO\n");
			continue;
		}
		sort(a+1,a+n+1);
		int tmp = 0;
		for(int i=1; i<=n; i++){
			if(a[i] == 1)	b[i] = 2;
			else	b[i] = 1;
			tmp += b[i];
		}
		if(tmp <= sum){
			printf("YES\n");
			continue;
		}
		if(tmp > sum){
			printf("NO\n");
			continue;
		}
	}
	return 0;
}

C.To Become Max

题目描述:

给定一个长度为 \(n\) 的序列 \(a\) 你可以执行至多 \(k\) 次操作,一次操作的定义如下:

  • 选择一个位置 \(i \in [1,n-1]\) 满足 \(a_i \le a_{i+1}\),将 \(a_i\)\(1\)

询问进行操作后的最大的 \(\max_{i=1}^n a_i\) 的值。
\(n \le 1000,k \le 10^8\)

题目分析:

考虑我们若最大值的位置为 \(i\) 且其变成了 \(x\),那么 \([i,i+n]\) 的序列大概会长成 \(x,x-1,x-2,...\) 的样子。
所以可以直接二分答案,然后枚举钦定哪个位置为最大值,然后计算一下需要多少次操作。
但是其实我们没有必要每一次都计算到 \(n\),假设位置 \(j\) 处应该为 \(p\) 但是 \(a_j \ge p\) 那么就后面其实就没必要算了,直接从这个位置向左扩展就好了。

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5+5;
int n,a[N],b[N],k;
bool chk(int x){
	for(int i=1; i<=n; i++){  //将这个位置改为 x
//		if(a[i] >= x)	return true; 
		int tmp = 0;
		bool flag = false;
		for(int j=i; j<=n; j++){
			//j : x - (j - i)
			tmp += max(0ll,(x - (j - i)) - a[j]);
			if(a[j] >= (x - (j - i))){
				flag = true;break;
			}
		}
		if(tmp <= k && flag)	return true;
	}
	return false;
}
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int T;scanf("%lld",&T);
	while(T--){
		scanf("%lld%lld",&n,&k);
		for(int i=1; i<=n; i++)	scanf("%lld",&a[i]);
		int l = 1,r = 2e8,ans = 0;
		while(l <= r){
			int mid = (l + r)>>1;
			if(chk(mid)){
				ans = mid,l = mid + 1;
			}
			else	r = mid - 1;
		}
		printf("%lld\n",ans);
	}
	return 0;
}

D.More Wrong

题目描述:

交互题。
有一个长度为 \(n\) 的排列 \(p\)
我们可以进行询问操作 \([l,r]\) 交互库会返回 \(p_l,p_{l+1},...,p_r\) 的逆序对个数,且这次操作的花费为 \((r-l)^2\)
要求花费不超过 \(5 \cdot n^2\),找到最大值的位置。

题目分析:

考虑最大值在逆序对里满足什么性质。
就是最大值在结尾时不会对逆序对产生贡献,也就是设 \([l,r]\) 的最大值为 \(p_r\),则 \([l,r]\) 的逆序对数等于 \([l,r-1]\) 的逆序对数。
考虑这个 \(5 \cdot n^2\) 很类似归并排序最后的花费,也就是对于一个区间 \([l,r]\) 询问 \([l,mid]\)\([mid+1,r]\) 的最大值的位置,然后根据最大值的性质就可以判断区间 \([l,r]\) 的最大值的位置。
根据等比数列求和这样的花费大概是 \(4\cdot n^2\) 的可以过。

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int query(int l,int r){
	if(l == r)	return 0;
	cout<<'?'<<' '<<l<<' '<<r<<endl;cout.flush();
	int tmp;cin>>tmp;
	return tmp;
}
int solve(int l,int r){
	if(l == r)	return l;
	int mid = (l + r)>>1;
	int pos1 = solve(l,mid);
	int pos2 = solve(mid+1,r);
	if(pos1 > pos2)	swap(pos1,pos2);
	int ans1 = query(pos1,pos2);
	int ans2 = query(pos1,pos2-1);
	if(ans1 == ans2)	return pos2;
	return pos1;
}
signed main(){
	int T;cin>>T;
	while(T--){
		int n;cin>>n;
		int pos = solve(1,n);
		cout<<'!'<<' '<<pos<<endl;cout.flush();
	}
	return 0;
}

E.PermuTree

题目描述:

给定一个 \(n\) 个节点的树,你要对每个点给定一个点权 \(a_i\),要满足 \(a\) 为一个排列。
点对 \((x,y)\) 有贡献,当且仅当 \(a_x < a_{lca(x,y)} < a_y\),询问最大贡献和是多少。

题目分析:

对于根节点,我们显然可以给它的子树定一个大小关系,假设 \(1 < 2\) 也就是说子树 \(1\) 内的最大值小于子树 \(2\) 内的最小值。
可以发现这样定完之后,在这个点的贡献就相当于两个子树大小的乘积就很好算了。
考虑如果不满足这个条件,那么分讨一下发现将不满足条件的交换回去答案一定不劣。
因为我们存在 \(a_{lca(x,y)}\) 必须位于两个子树的权值之间,所以我们本质上就是将子树划分为两个集合,造成的贡献就是两个集合的大小乘积,要让乘积最大肯定就是两个集合的大小尽可能接近。
这个东西任何的贪心都是假的,只能 \(dp\),所以直接暴力背包复杂度就是 \(O(n^2)\) 就可以通过 easy version