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
cntb−cnta。若存在
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
cntb−cnta+1
B. Playing with GCD
思路
尝试构造,对任意 b i b_i bi ,它的约束为 a i a_i ai 和 a i − 1 a_{i-1} ai−1,即 b i b_i bi至少应当是 g c d ( a i , a i − 1 ) gcd(a_i,a_{i-1}) gcd(ai,ai−1) 的倍数,显然当 b i = L C M ( a i , a i − 1 ) b_i=LCM(a_i,a_{i-1}) bi=LCM(ai,ai−1) 时,会对后面数组元素的构造有利(贪心思想)。我们尝试一个一个构造数组 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,ai−1)=p1k1∗p2k2∗...∗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)=p1′k1′∗p2′k2′∗...∗pk′kk′!=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,i−a[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 i−l[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 i−l[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[i−1]+1,a[i]),为 O ( N ) O(N) O(N) 的复杂度。
对于困难版本,修改 p p p 点后,可以发现:
- p p p点前的答案是不受影响的,这里可以用前缀和 O ( 1 ) O(1) O(1)求得;
- l [ p ] = m i n ( l [ p − 1 ] + 1 , a [ p ] ) l[p]=min(l[p-1]+1,a[p]) l[p]=min(l[p−1]+1,a[p])更新后,显然会影响后面一段数组的求解;
- 若 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[i−1]+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[i−1]+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]+i−p,转化后即为 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[11−20]={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;
}