【题解】Codeforces Round 890(CF1856)
赛时过了 A-E1,rk195
可惜是 E2 傻逼了不会背包优化了,直接连普及组水平都不到了。
A.Tales of a Sort
题目描述:
给定长度为 的序列 ,每次操作为对于所有 将 变为 ,询问最少多少次操作之后可以让序列 单调不降。
题目分析:
唯一能改变元素大小关系的就是将数变成 ,所以可以考虑找到结尾的最长不降序列,然后将其前面的全部变成 就好了。
答案就是这些数里的最大值。
代码:
点击查看代码
#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
题目描述:
给定一个长度为 的序列 ,询问是否存在一个长度为 的序列 满足:
题目分析:
考虑调整,一定是一些数变大另一些数变小。
所以直接将所有 的位置的 设为 其余部分的 设为 ,显然这一定是一个和最小的方案。
如果此时 ,那么也就无解了。
否则如果 ,就可以通过调整 的位置对应的 的值使得和满足条件。
代码:
点击查看代码
#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
题目描述:
给定一个长度为 的序列 你可以执行至多 次操作,一次操作的定义如下:
- 选择一个位置 满足 ,将 加 。
询问进行操作后的最大的 的值。
题目分析:
考虑我们若最大值的位置为 且其变成了 ,那么 的序列大概会长成 的样子。
所以可以直接二分答案,然后枚举钦定哪个位置为最大值,然后计算一下需要多少次操作。
但是其实我们没有必要每一次都计算到 ,假设位置 处应该为 但是 那么就后面其实就没必要算了,直接从这个位置向左扩展就好了。
代码:
点击查看代码
#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
题目描述:
交互题。
有一个长度为 的排列 。
我们可以进行询问操作 交互库会返回 的逆序对个数,且这次操作的花费为 。
要求花费不超过 ,找到最大值的位置。
题目分析:
考虑最大值在逆序对里满足什么性质。
就是最大值在结尾时不会对逆序对产生贡献,也就是设 的最大值为 ,则 的逆序对数等于 的逆序对数。
考虑这个 很类似归并排序最后的花费,也就是对于一个区间 询问 和 的最大值的位置,然后根据最大值的性质就可以判断区间 的最大值的位置。
根据等比数列求和这样的花费大概是 的可以过。
代码:
点击查看代码
#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
题目描述:
给定一个 个节点的树,你要对每个点给定一个点权 ,要满足 为一个排列。
点对 有贡献,当且仅当 ,询问最大贡献和是多少。
题目分析:
对于根节点,我们显然可以给它的子树定一个大小关系,假设 也就是说子树 内的最大值小于子树 内的最小值。
可以发现这样定完之后,在这个点的贡献就相当于两个子树大小的乘积就很好算了。
考虑如果不满足这个条件,那么分讨一下发现将不满足条件的交换回去答案一定不劣。
因为我们存在 必须位于两个子树的权值之间,所以我们本质上就是将子树划分为两个集合,造成的贡献就是两个集合的大小乘积,要让乘积最大肯定就是两个集合的大小尽可能接近。
这个东西任何的贪心都是假的,只能 ,所以直接暴力背包复杂度就是 就可以通过 easy version
考虑优化就是对于点 ,,所以其本质不同的 只有 个,这样就可以将这个问题转化为多重背包,这样的好处就是可以将物品数量优化到 级别,那么我们使用多重背包的经典优化二进制分组 + bitset 这个题就可以做到 的复杂度,实现的好一些加一些剪枝之类的就可以轻松跑过了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律