Codeforces Round #890 Div.2

link

题号: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,分开求和,原代价为:

\[\sum_{a=1}^k(2^k2^{a+1}-2·2^k+2^{k-a})=2^k(2^{k+2}-4)-2^{k+1}k+2^k-1=2^{2k+2}-2^{k+2}-2^{k+1}k+2^{k}-1<5\times 2^{2k} \]

所以是一个可行的方案。

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}\)

推论:

  1. \(\sum a^2=n\implies\) \(a\) 的不同值最多只有 \(t\) 个,其中\(t\)\(\frac{t(t+1)(2t+1)}{6} \ge n\) 中最小的 \(t\),显然正实数根只有这一个,可以提前预处理亦或者二分求解。

  2. \(\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;
}
posted @ 2023-08-07 11:38  spdarkle  阅读(55)  评论(0编辑  收藏  举报