Codeforces Round #890 Div.2
题号:1856A~E2
A
题面:
给定一个正整数 \(n\) 和一个长度为 \(n\) 的序列 \(a\),重复执行以下操作直至 \(a\) 序列单调不减:
- \(\forall 1 \le i \le n\),\(a_i = \max(a_i - 1, 0)\)。注意这个算一次操作。
求一共需要执行多少次操作。
多测,共 \(t\) 组数据。
对于所有数据,保证 \(1 \le t \le 500\),\(2 \le n \le 50\),\(1 \le a_i \le 10 ^ 9\)。
考虑原操作只会使得 \(a\) 单调不减,所以我们只需要考虑将 \(a\) 排序后从后往前第一个失配的位置即可。
read(n);for(int i=1;i<=n;i++)read(a[i]),b[i]=a[i];
sort(b+1,b+n+1);int tag=0;
for(int i=n;i;--i){
if(a[i]!=b[i]){
tag=1;
cout<<b[i]<<"\n";break;
}
}
if(!tag)cout<<"0\n";
B
题面:
给定一个正整数 \(n\) 和一个长度为 \(n\) 的序列 \(a\),问是否存在正整数序列 \(b\) 满足以下条件:
\(\forall 1 \le i \le n\),\(a_i \ne b_i\)。
\(\sum \limits_{i = 1} ^ {n} a_i = \sum \limits_{i = 1} ^ {n} b_i\)。
若存在,输出
YES
,否则输出NO
。多测,共 \(t\) 组数据。
对于所有数据,保证 \(1 \le t \le 10 ^ 4\),\(1 \le n, \sum n \le 10 ^ 5\),\(1 \le a_i \le 10 ^ 9\)。
对于这种构造与原序列不同,我们显然是选择 调整法,在本题中微调就可以了。
考虑对于 \(a_i=1\),我们将其微调为 \(2\),因为它已经不能够再减去了。那么我们统计 \(k=\sum_{i=1}^n[a_i=1]\),则我们再找若干个数减掉 \(k\) 即可。那么一个数可以被减掉 \(a_i-1\),故设 \(d=\sum_{i=1}^n(a_i-1)\),只需要判断是否 \(d\ge k\) 即可。注意 \(n=1\) 需要特判,这显然不合法。
read(n);for(int i=1;i<=n;i++)read(a[i]);
int k=0,d=0;
for(int i=1;i<=n;i++){
k+=(a[i]==1);d+=a[i]-1;
}
if(d>=k&&n!=1)cout<<"Yes\n";
else cout<<"No\n";
C
给定两个正整数 \(n\) 和 \(k\),以及一个长度为 \(n\) 的序列 \(a\),你可以进行不超过 \(k\) 次如下操作(以下两个步骤合称一次操作):
选择一个 \(i\) 满足 \(1 \le i < n\),且 \(a_i \le a_{i + 1}\)。
将 \(a_i\) 增加 \(1\)。
求操作完成后,\(\max \limits_{i = 1} ^ {n} a_i\) 的最大可以是多少。
多测,共 \(t\) 组数据。
对于所有数据,保证 \(1 \le t \le 100\),\(1 \le n, \sum n \le 1000\),\(1 \le k, a_i \le 10 ^ 8\)。
我们想想一个数是最大值满足什么条件,设最优局面为 \(b\)。若最大值为 \(b_i\) ,则说明什么?
面对此类构造性问题,我们可以从最优局面的角度出发,反推回原本局面寻找性质,类似于双向搜索。运用从后往前推的方法。
说明 \(b_{i+1}\ge b_i-1,b_{i+2}\ge b_i-2\dots\),直到第一个不需要从 \(a_i\) 上增加的数为止。
所以这个最大值必然形成了一段单减序列,且相邻差为一。
那么假设我们已经知道了 \(b_i\) 的值,怎么计算操作次数?同样的,设 \(k\) 为第一个 \(k>i,a_k\ge b_i-(k-i)\),则这个代价显然是:\(\sum_{j=i}^{k-1}(b_j-a_j)\),而其余数字全部没有动过。
由于最大操作次数是已知的,且答案显然具有单调性,故可对于每一个数来二分答案改为判定。我们只需计算上述代价即可。同时需要注意这个 \(k\) 如果不存在也是非法的。
bool check(int id,int x){
int s=k,tag=0;
for(int i=1;i<=n;i++)b[i]=a[i];
s-=x-b[id];b[id]=x;
for(int i=id+1;i<=n;++i){
if(b[i]<b[i-1]-1){
s-=(b[i-1]-1-b[i]);
b[i]=b[i-1]-1;
}
else {
tag=1;break;
}
}
return s>=0&&tag;
}
signed main(){
int t;read(t);
while(t--){
read(n);read(k);for(int i=1;i<=n;i++)read(a[i]);
int ans=0;
for(int i=1;i<=n;i++){
int l=a[i],r=k+a[i];
while(l<r){
int mid=l+r+1>>1;
if(check(i,mid))l=mid;
else r=mid-1;
}
ans=max(ans,l);
}
cout<<ans<<"\n";
}
}
D
本题交互。
有一个长为 \(n\) 的序列 \(a\),最初不知道 \(a\) 的任何一个值。
一次询问区间 \([l,r]\) 的逆序对个数,代价为 \((r-l)^2\)。
你需要进行若干次询问,求出 \(a\) 的最大值所在的位置,并使得询问总代价 \(w\le 5n^2\)。
多测,\(1\le t\le 100,1\le n\le 2000\)。所有数据中 \(n\) 的总和不超过 \(2000\)。
对于限定区间代价的问题,我们先判断分开区间判断更优还是合并区间判断更优
看这个代价,对于长为 \(a+1\),\(b+1\) 的两段区间,将其分开询问的代价为 \(a^2+b^2\),将其合并询问的代价为 \((a+b+1)^2>a^2+b^2\)
所以显然是询问小段更优,这启发我们自底向上确定最大值,设询问 \([l,r]\) 的答案为 \(f(l,r)\)。
我们要找最大值位置,怎么判断一个区间的最大值?设这个最大值在位置 \(x\),则显然是 \(f(x,r)-f(x+1,r)=r-x\)。
考虑到我们以后必然是要合并若干区间的答案的,则我们对于区间 \([l,r]\),若已知 \([l,k]\) 的最大值位置为 \(x\) ,\([k+1,r]\) 的最大值位置为 \(y\),则若 \(f(x,y)-f(x+1,y)=y-x\),就有 \(a_x>a_y\)。
故可以分治解决该问题,代价?
考虑当 \(n=2^k\) 的时候,\(n\le 2000\),则 \(k\) 取11。\(n\neq 2^k\) 显然优于为2的次幂的情况。
计算代价,对于 \(len=2^a\),有 \(2^{k-a}\) 个区间,最坏情况下代价为 \(2^{k-a}((2^a)^2+(2^a-1)^2)=2^{k-a}(2^{2a+1}-2^{a+1}+1)\)
也即代价为 \(2^{a+1}2^{k}-2·2^k+2^{k-a}\),\(k\) 取11,分开求和,原代价为:
所以是一个可行的方案。
int ask(int l,int r){
if(l==r)return 0;
cout<<"? "<<l<<" "<<r<<"\n";cout.flush();
int x;cin>>x;return x;
}
int solve(int l,int r){
if(l==r)return l;
int mid=l+r>>1;
int x=solve(l,mid),y=solve(mid+1,r);
int a=ask(x,y),b=ask(x+1,y);
if(a-b==y-x)return x;
return y;
}
int main(){
ios::sync_with_stdio(false);
int t;cin>>t;
while(t--){
int n;cin>>n;int ans=solve(1,n);
cout<<"! "<<ans<<"\n";
}
}
事实上分治法在确定位置的问题上是常用的。但为什么我没想到呢?
确定一个最大值必然需要确定两段区间的最大值再比较谁更大啊。
E2
这是问题的困难版本,两个版本之间的唯一区别是 \(n\) 的范围和时间限制的不同。
给定一棵以 \(1\) 为根的有根树,你需要给出一个 \(1\) 到 \(n\) 的排列 \(a\),最大化二元组 \((u,v)\) 的数量,满足 \(a_u < a_{\rm{lca(a_u,a_v)}} < a_v\),输出这个最大值。
\(2 \leq n \leq 10^6\)。
先考虑一个简化版问题,这很像二叉树的中序遍历。亦或者说是中序遍历版dfs序。
扩展一下,原问题就变为确定一个中序遍历的顺序,使满足要求的点对尽量多。
那么,假设进入子树 \(v_1\sim v_x\) 后,确定 \(u\) 的值,然后进入子树 \(v_{x+1}\sim v_m\),则带来的收益为:\((siz_{v_1}+\dots +siz_{v_x})(siz_{v_{x+1}}+\dots +siz_{v_m})\)。
由于 \(\sum siz_{v}=siz_u-1\) 为定值,由均值不等式,当 \(|(siz_{v_1}+\dots +siz_{v_x})-(siz_{v_{x+1}}+\dots +siz_{v_m})|\) 取得最小值时,收益最大。
则问题化为:给定 \(siz_{v_1}\sim siz_{v_m}\),求从中选出若干个数字使得凑出的和最接近 \(\frac{siz_{u}}{2}\)。
这是一个经典的动态规划求解可行性的问题,可以使用01背包在 \(O(n)\) 中求解。
对于每一颗子树做这样的动态规划,其复杂度是 \(O(n^2)\)。因为每一个节点只会被当作子节点更新一次。
这时候我们就可以通过E1的 \(n\le 5000\),但怎么扩展到 E2 的 \(n\le 10^6\) 呢?
先用一个启发式,若 \(\max siz_v\ge \frac{siz_u}{2}\),直接将 \(\max siz_v(siz_u-\max siz_v-1)\) 计入答案,不进行DP。
在不久之前的一把CF里有一个去重后使用倍数法保障复杂度 \(O(n\log n)\) 的题目,在这里,我们 在算法上优化似乎无路可走,也可以从值域的角度上考虑问题,而从值域上考虑问题的经典套路是看去重后不同数字的个数。类比mex的计算,注意到 \(\sum siz_v<n\),所以这些数去重后最多只有 \(2\sqrt n\) 个相同。
重要性质:
\(\sum a=n\implies\) \(a\) 的不同值最多只有 \(2\sqrt n\) 个相同。因为 \(1+\dots +n=\frac{n(n+1)}{2}\)
推论:
\(\sum a^2=n\implies\) \(a\) 的不同值最多只有 \(t\) 个,其中\(t\) 为 \(\frac{t(t+1)(2t+1)}{6} \ge n\) 中最小的 \(t\),显然正实数根只有这一个,可以提前预处理亦或者二分求解。
\(\sum a^3=n\implies\) \(a\) 的不同值最多只有 \(t\) 个,其中 \(t\) 为满足 \(t^2(t+1)^2\ge 4n\) 中最小的 \(t\),这可以直接定为 \(\sqrt[4]{n}+d\),\(d\) 为一个自己设置的参数,一般二三四吧。
那么我们可以通过统计个数,去重做多重背包,用余数分组可以做到 \(O(n\sqrt n)\),用二进制拆分可以做到 \(O(n\sqrt n\log n)\),听说还有多项式黑科技可以做到 \(O(n\log^3 n)\)。第三种做法理论复杂度最优,但常数过大。
二进制拆分的做法可以使用 bitset
优化,复杂度为 \(O(\frac{n\sqrt n\log n}{w})\)。当 \(n=10^6\) 时,已经接近 \(O(n\log^2 n)\),是最优的做法,且远远跑不满,常数极小。
但问题是,即使不加bitset也可以在1902ms内跑过去。。。。。。
加bitset是一个实现难点,因为我们无法使用不定长bitset,此时就自己手写,亦或者把2的幂次的bitset全部定义一遍,一共20个,再写20个if判断即可。
void dfs(int u,int fa){
for(int i=head[u];i;i=nxt[i]){
int v=ver[i];
if(v==fa)continue;
dfs(v,u);siz[u]+=siz[v];
}
siz[u]++;int mx=0;cnt=num=0;
for(int i=head[u];i;i=nxt[i]){
int v=ver[i];
if(v==fa)continue;
mx=max(mx,siz[v]);a[++cnt]=siz[v];
}
if(mx>=siz[u]/2){
ans+=(siz[u]-mx-1)*mx;return;
}
sort(a+1,a+cnt+1);
for(int i=1;i<=cnt;i++){
if(a[i]!=a[i-1])w[++num]=a[i],c[num]=0;
++c[num];
}
f[0]=1;
cnt=0;
for(int i=1;i<=num;i++){
int x=c[i];
for(int j=0;j>=0;++j){
if(x<(1<<j)){
a[++cnt]=x*w[i];break;
}
x-=(1<<j);
a[++cnt]=(1<<j)*w[i];
if(!x)break;
}
}
for(int i=1;i<=cnt;i++){
for(int j=siz[u]/2;j>=a[i];j--)f[j]|=f[j-a[i]];
}
int s=0;
for(int i=1;i<=siz[u]/2;++i)if(f[i]){
s=max(s,i*(siz[u]-i-1));f[i]=0;
}ans+=s;
}