cf Round #772(Div. 2)

B

Description

给定一个长度为n的序列,可以选择任意位置修改成任意数。求最少需要修改多少次能使序列不存在i满足\(a_i>a_{i-1}\)\(a_i>a_{i+1}\)

Solution

对于一个三元对(\(a_{i-1},a_i,a_{i+1}\)),如果\(a_i>a_{i-1}\)\(a_i>a_{i+1}\),那么势必要消除三元对中的一个才可满足条件。
从左往右依次寻找这样的三元对,此时会对后续产生影响的只有\(a_{i+1}\),破坏该三元对需要使\(a_{i+1}\)变大到\(a_i\),我们希望此次修改能够使\((a_{i+1},a_{i+2},a_{i+3})\)也不会是这样的三元对,那么如果\(a_{i+2}>a_i\),还需将\(a_{i+1}\)提到\(a_{i+2}\)才能同时规避\((a_{i+1},a_{i+2},a_{i+3})\)的风险。即\(a_{i+1}=max\{a_i,a_{i+2}\}\)

#include<bits/stdc++.h>
using namespace std;
const int N=200005;
int a[N],n,ans;
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		for(int i=1;i<=n;++i)
			scanf("%d",&a[i]);
		ans=0;a[0]=a[1];a[n+1]=a[n];
		for(int i=1;i<=n;++i)
			if(a[i]>a[i-1]&&a[i]>a[i+1]){
				++ans;
				a[i+1]=max(a[i],a[i+2]);
			}
		printf("%d\n",ans);
		for(int i=1;i<=n;++i)
			printf("%d ",a[i]);
		printf("\n"); 
	}
	return 0;
}

C

Description

给定一个长度为n的序列,可以进行不多于n次的操作:每次选择\(1\leq x<y<z\leq n\)使\(a_x=a_y-a_z\)。求是否存在方案使得\(a_1\leq a_2\leq...\leq a_n\)

Solution

就硬构造。
首先,最后两位是无法改变的,所以需要\(a_{n-1}<a_n\)
对于\(a_n\geq0\)的情况,\(a_{n-1}-a_n\leq a_{n-1}<a_n\),对于前n-2个数均执行这种操作即可。
对于\(a_n<0\)的情况,\(a_{n-1}<a_n<0\),如果\(a_{n-2}>a_{n-1}\),无法通过\(a_{n-2}=a_{n-1}-a_n>a_{n-1}\)的操作使序列合法。即需要\(a_{n-2}\leq a_{n-1}\leq a_n\)。通过数学归纳法可得,需要满足条件\(a_1\leq a_2\leq...\leq a_n\)序列才有可能合法。

#include<bits/stdc++.h>
using namespace std;
const int N=200005;
int a[N],n,ans;
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		for(int i=1;i<=n;++i)
			scanf("%d",&a[i]);
		if(a[n-1]>a[n]){
			puts("-1");
			continue;
		}
		if(a[n]<0){
			bool flag=true;
			for(int i=1;i<n;++i)
				if(a[i]>a[i+1]){
					flag=false;break;
				}
				if(flag) puts("0");
				else puts("-1");
		}
		else{
			printf("%d\n",n-2);
			for(int i=1;i<=n-2;++i)
				printf("%d %d %d\n",i,n-1,n);
		}
	}
	return 0;
}

D

Description

给定一个集合的初始状态。如果集合中存在x,那么4x和2x+1也必须在集合中。在集合中所有数<\(2^p\)的前提下,求集合的最终大小。

Solution

可以证明,任意两个不相等的数,经过若干次操作后不会相等。
那么我们可以把问题转化成求每个数\(a_i\)一共可以扩展出多少个数(记为\(t_i\))以及它们能通过若干次操作到达哪些数\(a_j\)。那么最终集合大小为\(\sum_i(t_i-\sum_jt_j)\)
问题转化为如何求\(t_i\)和满足上述关系的(i,j)。

对于操作4x和2x+1,我们可以用二进制操作来表示它们:

  • 4x:x左移2位,即末尾多2个0
  • 2x+1:x左移1位,末尾变为1,即末尾多1个1
  • <\(2^p\):二进制位数\(\leq\)p

那么对于一个数\(a_j\),我们可以通过判断末尾还原一步步操作,得到可能的\(a_i\)
对于1个数,实际上两种操作就是分别将二进制位数+1或+2,那么每个数导致的最终大小是和位数相关的。
f[i]表示集合初始值只有一个二进制位数为i时的集合最终大小:f[i]=f[i+1]+f[i+2]。
按上述思路容斥即可。

#include<bits/stdc++.h>
using namespace std;

const int N=200005,M=(1e9+7);
int a[N],f[N],t[N],n,p,ans;
int bits(int x){
	int ret=0;
	while(x){
		++ret;x>>=1;
	}
	return ret;
}
void RemoveDuplicate(int i){
	int x=a[i];
	while(x){
		if((x&1)&&x>1){
			x=x>>1;
			int j=lower_bound(a+1,a+i,x)-a;
			if(a[j]==x) t[j]-=t[i];
			if(t[j]<0) t[j]+=M; 
		}
		else if(!(x&3)&&x>=4){
			x=x>>2;
			int j=lower_bound(a+1,a+i,x)-a;
			if(a[j]==x) t[j]-=t[i];
			if(t[j]<0) t[j]+=M; 
		}
		else break;
	}
}
int main(){
	scanf("%d%d",&n,&p);
	for(int i=1;i<=n;++i)
		scanf("%d",&a[i]);
	sort(a+1,a+1+n);
	f[p]=1;
	for(int i=p-1;i;--i){
		f[i]=f[i+1]+1;
		if(i<p-1) f[i]+=f[i+2];
		f[i]%=M;
	}
	for(int i=n,j,k;i;--i){
		k=bits(a[i]);
		if(k-1<=p) t[i]+=f[k];
		else continue;
		t[i]%=M;
		ans=(ans+t[i])%M;
		RemoveDuplicate(i);
	}
	printf("%d\n",ans);
	return 0;
}

E

Description

数轴上有n个坐落在不同位置的点,每个点有自己的朝向,给出m个约束条件:点x和点y是相向还是背向。求满足条件的n个点的坐标和朝向。

Solution

  • 相向的点对:朝右 朝左
  • 相反的点对:朝左 朝右
  1. 按约束条件建无向图,则有解的必要条件是边的两端朝向不同。bfs染色即可判定。
  2. 然后再按约束条件的左右关系建边,有解的充要条件是该图存在包含所有点的拓扑序。
    不用纠结1中染色时哪个颜色代表左边,哪个颜色代表右边,因为将数轴的正负方向翻转之后约束条件仍成立。
#include<bits/stdc++.h>
using namespace std;
const int N=200005;
struct edge{
	int l,r,t;
}ed[N];
struct graph{
	int nxt,to;
}e[N<<1];
int g[N],col[N],x[N],deg[N],n,m,cnt;
void addedge(int x,int y){
	e[++cnt].nxt=g[x];g[x]=cnt;e[cnt].to=y;
}
void adde(int x,int y){
	addedge(x,y);addedge(y,x);
}
queue<int> q;
bool bfs(int u){
	q.push(u);col[u]=2;//L:2   R:3
	while(!q.empty()){
		u=q.front();q.pop();
		for(int i=g[u];i;i=e[i].nxt)
			if(!col[e[i].to]){
				col[e[i].to]=col[u]^1;
				q.push(e[i].to);
			}
			else if(!(col[u]^col[e[i].to]))
				return false;
	}
	return true;
}
void topo(int u){
	q.push(u);
		while(!q.empty()){
		u=q.front();q.pop();x[u]=++cnt;
		for(int i=g[u];i;i=e[i].nxt)
			if(!(--deg[e[i].to])){
				q.push(e[i].to);
			}
	}
}
bool topo(){
	cnt=0;
	for(int i=1;i<=n;++i)
		if(!deg[i]&&!x[i]) topo(i);
	for(int i=1;i<=n;++i)
		if(!x[i]) return false;
	return true;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;++i){
		scanf("%d%d%d",&ed[i].t,&ed[i].l,&ed[i].r);
		adde(ed[i].l,ed[i].r);
	}
	bool flag=true;
	for(int i=1;i<=n;++i)
		if(!col[i]){
			if(!bfs(i)){
				flag=false;break;
			}
		}
	if(!flag){
		puts("NO");
		return 0;
	}
	memset(g,0,sizeof(g));cnt=0;
	for(int i=1;i<=m;++i)
		if(ed[i].t&1){
			if(col[ed[i].l]&1) swap(ed[i].l,ed[i].r);
			addedge(ed[i].l,ed[i].r);++deg[ed[i].r];//L R
		}
		else{
			if(col[ed[i].l]&1) swap(ed[i].l,ed[i].r);
			addedge(ed[i].r,ed[i].l);++deg[ed[i].l];//R L
		}
	if(topo()){
		puts("Yes");
		for(int i=1;i<=n;++i)
			printf("%c %d\n",(col[i]&1)?'R':'L',x[i]);
	}
	else puts("NO");
	return 0;
}

F

Description

给定x个点的坐标及它们的权值,保证坐标升序。q次询问,每次求区间[l,r]之间的加权点距\(|x_i-x_j|(w_i+w_j)\)的最小值。

Solution

显然对于每个点i,它贡献的最小加权点距为\(min\{|x_i-x_j|(w_i+w_{j_x})\}\)\(j_x\)是i左边/右边第一个\(w_j\)\(w_i\)大/小的下标。即只有这些点对有意义。
设有意义的点对为(x,y),
问题转化成求完全落在区间[l,r]内的点对(x,y)的加权点距的最小值。
线段树或树状数组实现即可。

#include<bits/stdc++.h>
using namespace std;
const int N=300005;
struct query{
	int l,r,i;
	friend bool operator < (const query a,const query b){
		if(a.l!=b.l) return a.l>b.l;
	}
}qry[N];
struct segment{
	int l,r;
	friend bool operator < (const segment a,const segment b){
		if(a.l!=b.l) return a.l>b.l;
	}
}a[N<<2];
int x[N],w[N],n,q,cnt;
long long bit[N],ans[N];
int s1[N],s2[N],t1,t2;
int lowbit(int x){
	return x&(-x);
}
void change(int x,long long k){
	for(int i=x;i<=n;i+=lowbit(i))
		if(bit[i]<0||k<bit[i]) bit[i]=k;
}
long long  ask(int x){
	long long ret=-1;
	for(int i=x;i;i-=lowbit(i))
		if(ret<0||(bit[i]>=0&&bit[i]<ret)) ret=bit[i];
	return ret;
}
int main(){
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;++i)
		scanf("%d%d",&x[i],&w[i]);
	for(int i=1;i<=q;++i){
		scanf("%d%d",&qry[i].l,&qry[i].r);
		qry[i].i=i;
	}
	for(int i=1;i<=n;++i){
		//左边第一个比w[i]小 
		while(t1&&w[i]<w[s1[t1]]) --t1;
		if(t1) a[++cnt]=(segment){s1[t1],i};
		s1[++t1]=i;
		//左边第一个比w[i]大 
		while(t2&&w[i]>w[s2[t2]]) --t2;
		if(t2) a[++cnt]=(segment){s2[t2],i};
		s2[++t2]=i;
	}
	t1=t2=0;
	for(int i=n;i>=1;--i){
		//右边第一个比w[i]小 
		while(t1&&w[i]<w[s1[t1]]) --t1;
		if(t1) a[++cnt]=(segment){i,s1[t1]};
		s1[++t1]=i;
		//右边第一个比w[i]大 
		while(t2&&w[i]>w[s2[t2]]) --t2;
		if(t2) a[++cnt]=(segment){i,s2[t2]};
		s2[++t2]=i;
	} 
	sort(a+1,a+1+cnt);
	sort(qry+1,qry+1+q);
	fill(bit+1,bit+1+n,-1);
	for(int i=1,j=1;i<=q;++i){
		while(j<=cnt&&a[j].l>=qry[i].l){
			change(a[j].r,1ll*(x[a[j].r]-x[a[j].l])*(w[a[j].r]+w[a[j].l]));
			++j;
		}
		ans[qry[i].i]=ask(qry[i].r);
	}
	for(int i=1;i<=q;++i)
		printf("%lld\n",ans[i]);
	return 0;
}
posted @ 2022-02-28 20:54  Aireen_Ye  阅读(20)  评论(0编辑  收藏  举报
底部 顶部 留言板 归档 标签
Der Erfolg kommt nicht zu dir, du musst auf den Erfolg zugehen.