【题解】Codeforces Round 890(CF1856)
赛时过了 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
考虑优化就是对于点 \(u\),\(\sum_{v \in son_u} sz_v \le n\),所以其本质不同的 \(sz_i\) 只有 \(O(\sqrt{n})\) 个,这样就可以将这个问题转化为多重背包,这样的好处就是可以将物品数量优化到 \(O(\sqrt{m})\) 级别,那么我们使用多重背包的经典优化二进制分组 + bitset 这个题就可以做到 \(O(\dfrac{n\sqrt{n}\log n}{w})\) 的复杂度,实现的好一些加一些剪枝之类的就可以轻松跑过了。