Practice on Codeforces and Atcoder in March

cf1798d

题意:重排序列,使得其中连续子序列和的绝对值最大的最大值小于序列最大值减最小值,序列和为0

考虑这样一种构造方案:

正负数分类,0直接不管

然后记录当前和sum,当sum非负时,加上一个负数,当sum是负数时,加上一个正数即可

正确性证明:

显然前缀和都是合法的。考虑计算前缀和数组,满足性质:最小的sum小于最小值,最大的sum大于等于最大值。 那么显然合法

无解的情况就是全0

/*
分讨:
1. sum<mx-mn-->mx>0,mn<0-->sum<mx-mn
2. -sum<mx-mn->mx>0,mn<0-->sum>mn-mx
不妨思考一个更强的条件
mn<=sum<=mx
一种合理的构造方式是:
当前sum>0,加一个负数
当前sum<0,加一个正数
当前sum=0,随便加
Why?
由于和是0,则按照这样加sum最大为mx(0+mx),最小为mn(0+mn)
*/
signed main(){
	ios::sync_with_stdio(false);
	cin>>t;
	while(t--){
		int num1=0,tot=0,num2=0,mx=0,mn=0;cnt=0;
		cin>>n;
		for(int i=1;i<=n;i++){
			int x;cin>>x;
			mx=max(mx,x);mn=min(mn,x);
			if(x>0)a[++num1]=x;
			else if(x<0)b[++num2]=x;
			else ans[++tot]=0;
		}
		if(mx==mn){
			cout<<"No\n";continue;
		}
		int sum=0,l=1,r=1;
		while(l<=num1&&r<=num2){
			if(sum<0)sum+=a[l],ans[++tot]=a[l],l++;
			else sum+=b[r],ans[++tot]=b[r],r++;
		}
		while(l<=num1)ans[++tot]=a[l],l++;
		while(r<=num2)ans[++tot]=b[r],r++;
		cout<<"Yes\n";
		for(int i=1;i<=n;i++)cout<<ans[i]<<" ";
		cout<<"\n";
	}
} 

CF1805D

给定一颗树,求 \(ans_{k}(1\le k\le n)\)

\(ans_k\) 的含义为:在树上将所有 \(dis(u,v)\ge k\) 的点对连边后的连通块个数。

联系到树的直径,设直径长为 \(m\),端点为 \(s,t\)

\(ans_k=n(k>m)\)

引理:

\(k\le m\) 时,有且仅有 \(s,t\) 所在的连通块 \(siz>1\)

证明:

\(k=m\) 时,显然成立

现在我们证明了 \(k=a\),要证明 \(k=a-1\)

采用反证法

假设会形成一个 \(siz>1\) 的连通块,不与直径一起。那么设这个连通块的直径为 \(p\),则 \(p\ge a-1\) 对于 \(s,t\) 而言

直径不在这里,所以一定两端点到这个连通块的最大距离 \(\ge p+1\)。Why?假设它不大于,那么这个连通块的直径端点向 \(s,t\) 连边,就会形成一条 \(>dis(s,t)\) 的链,此时出现新的直径,于直径定义不符,所以不成立

引理成立。

所以可以以两个直径端点进行BFS,然后倒序处理删连通块即可

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<cstring>
#define N 300400
using namespace std;
int s,t,vis[N],dis[N][3],n,m,head[N],ver[N],nxt[N],tot,ans[N],f[N];
vector<int>cnt[N],cnt1[N];
void add(int u,int v){
	nxt[++tot]=head[u],ver[head[u]=tot]=v;
}
void bfs(int s,int id){
	queue<int>q;
	memset(vis,0,sizeof vis);
	vis[s]=1;
	q.push(s);
	dis[s][id]=0;
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=head[u];i;i=nxt[i]){
			int v=ver[i];
			if(vis[v])continue;
			dis[v][id]=dis[u][id]+1;
			q.push(v);vis[v]=1;
			if(id==0)cnt[dis[v][id]].push_back(v);
			if(id==1)cnt1[dis[v][id]].push_back(v);
		}
	}
}
int find(int x){
	return x==f[x]?x:f[x]=find(f[x]);
}
int main(){
//	ios::sync_with_stdio(false);
	cin>>n;
	for(int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		add(u,v);add(v,u);
	}
	bfs(1,2);s=-1;int mx=-1;
	for(int i=1;i<=n;i++){
		if(dis[i][2]>mx){
			mx=dis[i][2];s=i;
		}
	}
	bfs(s,1);
	mx=-1;
	for(int i=1;i<=n;i++){
		if(dis[i][1]>mx){
			mx=dis[i][1];t=i;
		}
	}
	bfs(t,0);int sum=n;
	for(int i=1;i<=n;i++)f[i]=i;
	//cout<<s<<" "<<t<<"\n";
	for(int i=mx;i;--i){
		for(int j=0;j<cnt1[i].size();j++){
			int u=cnt1[i][j];u=find(u);
			if(u!=find(s)){
				sum--;
				f[u]=find(s);
			}
		}
		for(int j=0;j<cnt[i].size();j++){
			int u=cnt[i][j];u=find(u);
			if(u==find(t))continue;
			f[u]=find(t);
			sum--;
		}
		ans[i]=sum; 
	}
	for(int i=1;i<=n;i++){
		if(i>mx)cout<<n<<" ";
		else cout<<ans[i]<<" ";
	}
	return 0;
}

CF1806C

首先,让我们来手推一下。

我们已知的条件是:

\[\left\{ \begin{aligned} x_1x_2x_3…x_n&=x_{n+1}+x_{n+2}+x_{n+3}+…+x_{2n}\\ x_2x_3x_4…x_{n+1}&=x_1+x_{n+2}+x_{n+3}+…+x_{2n}\\ x_3…x_nx_{n+1}x_{n+2}&=x_1+x_{n+2}+x_{n+3}+…+x_{2n}\\ … \end{aligned} \right. \]

以前两个式子为例,有:

\(x_2x_3…x_n(x_1-x_n)=x_n-x_1\)

这个方程有两种可能:

  1. \(x_2x_3……x_n=-1\implies x_i=1\)\(-1,i\in[2,n]\)
  2. \(x_1-x_n=0\implies x_1=x_n\)

由于式子一共有 \(2n\choose n\) 个,可以推出任意的关系。

进而对于第一种情况,我们可以确定出当 \(n\) 为偶数的时候,好的数组是 \((-1,-1,-1,n,-1,-1…)\),以及它的全排列 (只有一个 \(n\) )。

对于第二种情况,所有值都相等,则有: \(x^n=xn\implies x_1=0,x_2=\sqrt[n-1]{n}\)

显然,对于 \(x_2\),当且仅当 \(n=2\) 时为整数。

需要注意,当 \(n=1\) 的时候,好的数组是 \((x,x)(x\in \mathbb{Z})\)

综上所述,有:

  1. 对于 \(n=1\) ,这个最优的 \(q\)\((p_1,p_1),(p_2,p_2)\)两种可能,距离都是 \(|p_1-p_2|\)
  2. 对于 \(n=2\),好的数组有且仅有:\((0,0,0,0),(2,2,2,2),(-1,-1,2,-1)\) 及其排列
  3. 对于 \(n\) 为偶数且 \(n>2\),好的数组有且仅有 \((0,0,0,0…,0),(-1,-1,n,-1,…,-1)\) 及其排列
  4. 对于 \(n\) 为奇数且 \(n>2\),好的数组有且仅有 \((0,0,0,0,……,0)\)

对于 \((-1,-1,n,-1,…,-1)\) 及其排列的情况,即为 \(\sum_{i=1}^{2n}|p_i+1|-\min_{1\le i\le 2n}\lbrace |p_i-n|-|p_i+1|\rbrace\)

#include<bits/stdc++.h>
#define int long long
using namespace std;
int a[2060606],n,T;
signed main(){
	ios::sync_with_stdio(false);
	cin>>T;
	while(T--){
		cin>>n;
		for(int i=1;i<=n<<1;i++)cin>>a[i];
		if(n==1){
			cout<<abs(a[1]-a[2])<<"\n";
		}
		else if(n==2){
			int ans1=0;
			for(int i=1;i<=n<<1;i++)ans1+=abs(a[i]-2);
			int ans2=0;
			for(int i=1;i<=n<<1;i++)ans2+=abs(a[i]);
			int ans3=0;
			for(int i=1;i<=n<<1;i++)ans3+=abs(a[i]+1);
			int mx=0x3f3f3f3f3f3f;
			for(int i=1;i<=n<<1;i++)mx=min(mx,abs(a[i]-n)-abs(a[i]+1));
			ans3+=mx;
			ans2=min(ans2,ans3);
			cout<<min(ans1,ans2)<<"\n";
		}
		else if((n&1)==0){
			int ans2=0;
			for(int i=1;i<=n<<1;i++)ans2+=abs(a[i]);
			int ans3=0;
			for(int i=1;i<=n<<1;i++)ans3+=abs(a[i]+1);
			int mx=0x3f3f3f3f3f3f;
			for(int i=1;i<=n<<1;i++)mx=min(mx,abs(a[i]-n)-abs(a[i]+1));
			ans3+=mx;
			cout<<min(ans2,ans3)<<"\n";
		}
		else {
			int ans2=0;
			for(int i=1;i<=n<<1;i++)ans2+=abs(a[i]);
			cout<<ans2<<"\n";
		}
	}
} 

CF1810D

不等式缩放即可

一组 \(a,b,n\) 可以确定长度 \(h\)\((a-b)(n-1)+1\le h\le (a-b)(n-1)+a\)

相同的,询问时仅需要判断在这个范围内唯一确定即可

设询问 \(a,b\),则设当前不等式为 \(mn\le h\le mx\)

则最大的 \(n\)\(\left\lceil\frac{(mx-b)}{a-b}\right\rceil\)

然后就只需要判断这个 \(n\) 可以确定的最小值与 \(mn\) 的关系即可

可能越界的就是 \((n-2)(a-b)+a\ge mn\) 这样就没了

注意特判 \(n=1\)

signed main(){
	cin>>t;
	while(t--){
		int q;cin>>q;
		int mn=0,mx=1e18,num=0;
		while(q--){
			int opt,a,b,n;
			cin>>opt>>a>>b;
			if(opt==1){
				cin>>n;
				int new_mn,new_mx;
				if(n>1)new_mn=a*(n-1)-b*(n-2)+1;
				else new_mn=0;
				new_mx=a*n-b*(n-1);
				if(new_mx>=mn&&new_mn<=mx){
					mx=min(mx,new_mx);
					mn=max(new_mn,mn);
					f[++num]=1;continue;
				}
				f[++num]=0;
			} 
			else {
				n=(mx-b+a-b-1)/(a-b);n=max(n,1ll);
				if((n-1)*a-(n-2)*b>=mn&&n!=1)f[++num]=-1;
				else f[++num]=n;
			}
		}
		for(int i=1;i<=num;i++)cout<<f[i]<<" ";
		cout<<"\n";
	}
} 
posted @ 2023-05-02 18:43  spdarkle  阅读(7)  评论(0编辑  收藏  举报