Codeforces #825 Div.2

这篇博客探讨了数组操作的问题,包括如何使两个数组元素1的个数相等,以及如何根据给定条件构造数组。文章通过举例和解析思路,阐述了在不同情况下的最优解法,涉及数组重排、最大公约数和最小公倍数的运用,以及等差数列的计算。此外,还介绍了如何处理数组子序列的特殊情况,涉及动态规划和线段树等数据结构的应用。
摘要由CSDN通过智能技术生成

A. Make A Equal to B

思路

显然,如果数组 a a a 和数组 b b b 中的1的个数满足 c n t a = c n t b cnta=cntb cnta=cntb,我们只需要对 a a a 数组重新排列就行了,答案为 1 1 1 。最好的情况下,数组 a a a 和数组 b b b 完全相同,这个时候不需要重排,答案为 0 0 0

在对数组 a a a 进行0变1或1变0的操作时,相当于对数组 b b b 做相反的操作。
c n t a ! = c n t b cnta!=cntb cnta!=cntb 时,在上面的结论下,我们将1更少的数组当作数组 a a a,并将满足 a i = 0 , b i = 1 a_i=0,b_i=1 ai=0,bi=1 a i a_i ai 变成1。最好情况下,变化完后数组 a a a b b b就相同了,这时答案为 c n t b − c n t a cntb-cnta cntbcnta。若存在 a i = 1 , b i ! = 1 a_i=1,b_i!=1 ai=1,bi!=1的情况,说明还需要重新进行排列,此时答案为 c n t b − c n t a + 1 cntb-cnta+1 cntbcnta+1

B. Playing with GCD

思路

尝试构造,对任意 b i b_i bi ,它的约束为 a i a_i ai a i − 1 a_{i-1} ai1,即 b i b_i bi至少应当是 g c d ( a i , a i − 1 ) gcd(a_i,a_{i-1}) gcd(ai,ai1) 的倍数,显然当 b i = L C M ( a i , a i − 1 ) b_i=LCM(a_i,a_{i-1}) bi=LCM(ai,ai1) 时,会对后面数组元素的构造有利(贪心思想)。我们尝试一个一个构造数组 b b b ,再返回去检查是否满足题设的约束条件 g c d ( b i , b i + 1 ) = a i gcd(b_i,b_{i+1})=a_i gcd(bi,bi+1)=ai。不满足则无法构造。

b i = L C M ( a i , a i − 1 ) = p 1 k 1 ∗ p 2 k 2 ∗ . . . ∗ p k k k b_i=LCM(a_i,a_{i-1})={p_1}^{k_1}*{p_2}^{k_2}*...*{p_k}^{k_k} bi=LCM(ai,ai1)=p1k1p2k2...pkkk为构造出来的 b i b_i bi,若 g c d ( b i , b i + 1 ) = p 1 ′ k 1 ′ ∗ p 2 ′ k 2 ′ ∗ . . . ∗ p k ′ k k ′ ! = a i gcd(b_i,b_{i+1})={p'_1}^{k'_1}*{p'_2}^{k'_2}*...*{p'_k}^{k'_k} != a_i gcd(bi,bi+1)=p1k1p2k2...pkkk!=ai,那么就会产生矛盾。

简单来说,构造出来的 b i b_i bi包含了至少应当包含的所有因子,不能包含更少的因子了,而 g c d ( b i , b i + 1 ) ! = a i gcd(b_i,b_{i+1})!=a_i gcd(bi,bi+1)!=ai说明因子包含多了,应该再少一点,就产生矛盾了。

#include <iostream>
#include <cstdio>
using namespace std;

int prime[10000],cnt=0;
int n;
int a[100010],b[100010];
bool vis[10010];

void read(int &x){
	char c;while(c=getchar(),c<'0'||c>'9'){}x=c^48;
	while(c=getchar(),c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48);
}

int gcd(int a,int b){
	return b==0 ? a : gcd(b,a%b);
}

int main(){
	int T;read(T);
	while(T--){
		read(n);
		for(int i=1;i<=n;++i) read(a[i]);
		b[1]=a[1];
		bool flag=true;
		for(int i=2;i<=n;++i){
			b[i]=a[i-1]*a[i]/gcd(a[i-1],a[i]);
			if(gcd(b[i],b[i-1])!=a[i-1]){
				flag=false;
				break;
			}
		}
		if(!flag) puts("NO");
		else puts("YES");
	} 
	
	return 0;
}

C. Good Subarrays

思路

简单版本的题就是想办法求出 a i a_i ai作为右端点所能到达的最左边的端点,记为 l [ i ] l[i] l[i]

显然初始时,① l [ i ] = m a x ( 1 , i − a [ i ] + 1 ) l[i]=max(1,i-a[i]+1) l[i]=max(1,ia[i]+1),但是由于左边的数可能不能支持当前数延伸这么长,因此还应有约束② l [ i ] = m a x { l [ j ] } , l [ i ] < = j < = i l[i]=max\{l[j]\}, l[i]<=j<=i l[i]=max{l[j]},l[i]<=j<=i,此时 l [ i ] l[i] l[i]即为所能到达的最左端点,而该点到当前点的长度 i − l [ i ] + 1 i-l[i]+1 il[i]+1即为 a i a_i ai作为右端点所能产生的答案的数目。累加即可。

比如 2 , 4 , 1 , 4 2,4,1,4 2,4,1,4,初始时 l = { 1 , 1 , 3 , 1 } l=\{1,1,3,1\} l={1,1,3,1},添加约束后,即有 l = { 1 , 1 , 3 , 3 } l=\{1,1,3,3\} l={1,1,3,3}

有许多方法可以快速求解约束②,比如线段树。这是 O ( N l o g N ) O(NlogN) O(NlogN) 的复杂度。

我们尝试直接将答案结合进数组 l l l中(因为线段树在处理约束②时是能直接将答案 i − l [ i ] + 1 i-l[i]+1 il[i]+1处理进去的),可以发现在上面的例子中, l = { 1 , 2 , 1 , 2 } l=\{1,2,1,2\} l={1,2,1,2},玩一些更大的样例,可以发现每一段都为一个连续段如7,8,9,10或者2,3,4,5的等差数列。观察发现, l [ i ] = m i n ( l [ i − 1 ] + 1 , a [ i ] ) l[i]=min(l[i-1]+1,a[i]) l[i]=min(l[i1]+1,a[i]),为 O ( N ) O(N) O(N) 的复杂度。

对于困难版本,修改 p p p 点后,可以发现:

  1. p p p点前的答案是不受影响的,这里可以用前缀和 O ( 1 ) O(1) O(1)求得;
  2. l [ p ] = m i n ( l [ p − 1 ] + 1 , a [ p ] ) l[p]=min(l[p-1]+1,a[p]) l[p]=min(l[p1]+1,a[p])更新后,显然会影响后面一段数组的求解;
  3. p p p 点后存在一个很小的值,比如说 a i = 1 a_i=1 ai=1,那么在 i i i点后的答案是不会受影响的。

由于更新公式为 l [ i ] = m i n ( l [ i − 1 ] + 1 , a [ i ] ) l[i]=min(l[i-1]+1,a[i]) l[i]=min(l[i1]+1,a[i]),如果 l [ i ] l[i] l[i]更新为 a [ i ] a[i] a[i],那么可以猜测这是那个很小的点,因为在 p p p点后, q q q点前的点都受到 a [ p ] a[p] a[p]的约束更新成 l [ i − 1 ] + 1 l[i-1]+1 l[i1]+1了。如果没有,那么这个很小的点就会在前面找到。显然 a p a_p ap 影响的这一段是一个等差数列,知道长度后,可以用公式快速求解。

因此问题就变成如何快速找到受 a p a_p ap影响的最右边的点。

显然受其影响的点均符合约束 a [ i ] > = a [ p ] + i − p a[i]>=a[p]+i-p a[i]>=a[p]+ip,转化后即为 a [ i ] − i > = a [ p ] − p a[i]-i>=a[p]-p a[i]i>=a[p]p,其中左边 a [ i ] − i a[i]-i a[i]i是明显可以用线段树处理的。

受其影响的点存在一个边界,边界左边受影响满足约束,右边不满足,因此尝试用二分方法求该边界,记为 q q q点。

本来我以为后面的点直接用前缀和算就行了,但是错了。用暴力对拍,发现当 a p a_p ap变大时,在 q q q点右边,可能会存在一些点,他们的左边界由于 a p a_p ap变大,在修改后能越过 p p p点,使后面一个等差数列答案增大。说明在 q q q点后还存在受 p p p点影响的点。

看了下官方题解,发现一个track数组,它的定义是,if l [ i ] = a [ i ] l[i]=a[i] l[i]=a[i] t r a c k [ i ] = ∑ i < = j < = n l [ j ] track[i]=\sum_{i<=j<=n}{l[j]} track[i]=i<=j<=nl[j]

这个数组的意思是,假设 l [ i ] = a [ i ] l[i]=a[i] l[i]=a[i](这个不一定是成立的,我们强制它成立),求后面每一段等差数列的和。

比方说,对于数据:
20
3 17 20 10 8 20 10 9 4 10 7 20 2 7 14 15 16 13 16 5

我们把 a 9 = 4 a_9=4 a9=4改成18后,找到的 q q q点是 a 10 = 10 a_{10}=10 a10=10 t r a c k [ 11 − 20 ] = { 55 , 60 , 40 , 62 , 77 , 63 , 48 , 32 , 21 , 5 } track[11-20]=\{55 ,60,40,62,77,63,48,32,21,5\} track[1120]={55,60,40,62,77,63,48,32,21,5}

如果看了我上面对等差数列的说明,可以按照数据对着我输出来的结果手玩一下。

因此 q q q点之后的答案直接加上 t r a c k [ q + 1 ] track[q+1] track[q+1]即可。

值得一说的是,我写的常规线段树虽然优化了一下常数,加了读入优化和输出优化,但是在cf上,C++14还是超时了,C++17不会超时。

官方给的题解是ZKW线段树,我已经忘了咋写了,等抽空复习一下。

#include <iostream>
#include <cstdio>
using namespace std;
#define ll long long

const int maxn=2e5+10;
ll n,q;
ll a[maxn],f[maxn],pre[maxn],bac[maxn];
ll tree[maxn<<2];

inline void read(ll &x){
	char c;while(c=getchar(),c<'0'||c>'9'){}x=c^48;
	while(c=getchar(),c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48);
}

void build(ll l,ll &r,ll num){
	if(l==r){
		tree[num]=a[l]-l;
		return;
	}
	ll mid=(l+r)>>1;
	ll lson=num<<1,rson=num<<1|1;
	build(l,mid,lson);
	build(mid+1,r,rson);
	tree[num]=min(tree[lson],tree[rson]);
}

ll ask(ll l,ll r,ll st,ll &ed,ll num){
	if(st<=l&&ed>=r) return tree[num];
	ll mid=(l+r)>>1;
	if(mid<st) return ask(mid+1,r,st,ed,num<<1|1);
	else if(mid>=ed) return ask(l,mid,st,ed,num<<1);
	else return min(ask(mid+1,r,st,ed,num<<1|1),ask(l,mid,st,ed,num<<1));
}

ll getl(ll p,ll val){
	ll l=p,r=n+1,mid;
	if(a[p+1]==1) return l;
	while(l+1<r){
		mid=(l+r)>>1;
		if(ask(1,n,p+1,mid,1)>=val) l=mid;
		else r=mid;
	}
	return l;
}

void write(ll x){
	if(x>=10) write(x/10);
	putchar('0'+x%10);
}

int main(){
	read(n);
	for(ll i=1;i<=n;++i){
		read(a[i]);
		f[i]=min(f[i-1]+1,a[i]);
		pre[i]=pre[i-1]+f[i];
	}
	build(1,n,1);
	ll l;
	bac[n]=a[n];
	for(ll i=n-1;i;--i){
		l=getl(i,a[i]-i);
		bac[i]=bac[l+1]+(a[i]*2+l-i)*(l-i+1)/2;
	}
	read(q);
	ll p,x,val;
	while(q--){
		read(p);read(x);
		val=min(f[p-1]+1,x);
		l=getl(p,val-p);
		write(pre[p-1]+(val*2+l-p)*(l-p+1)/2+bac[l+1]);
		putchar('\n');
	}
	
	return 0;
}

D. Equal Binary Subsequences

思路

我写的时候想到了应该从奇队列和偶队列去选,但是没想到到底该怎么旋转。感觉官方给的题解很清晰明了了,我就直接贴个代码叭。

#include <iostream>
#include <cstdio>
using namespace std;

const int maxn=1e5+10;
int n;
int a[maxn<<1],p[maxn],q[maxn];

void read(int &x){
	char c;while(c=getchar(),c<'0'||c>'9'){}x=c^48;
	while(c=getchar(),c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48);
}

int main(){
	int T;read(T);
	while(T--){
		read(n);
		int cnt=0;
		for(int i=1;i<=2*n;++i){
			scanf("%1d",a+i);
			if(a[i]==1) ++cnt;
		}
		if(cnt&1){
			printf("-1\n");
			continue;
		}
		bool op=0;cnt=0;
		for(int i=1;i<=2*n;i+=2){
			if(a[i]==a[i+1]){
				p[i/2+1]=i;
				q[i/2+1]=i+1;
			}else{
				++cnt;
				if(op){
					p[i/2+1]= a[i]==0 ? i:i+1;
					q[i/2+1]= a[i]==0 ? i+1:i;
				}else{
					p[i/2+1]= a[i]==0 ? i+1:i;
					q[i/2+1]= a[i]==0 ? i:i+1;
				}
				op^=1;
			}
		}
		printf("%d ",cnt);
		for(int i=1;i<=n;++i) if(a[p[i]]!=a[q[i]]) printf("%d ",q[i]);
		putchar('\n');
		for(int i=1;i<=n;++i) printf("%d ",p[i]);
		putchar('\n');
	}
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值