Codeforces Round 943 (Div. 3)(A,B,C,D,E,F,G1,G2)

2024-05-11 15:20
文章标签 codeforces round div g2 g1 943

本文主要是介绍Codeforces Round 943 (Div. 3)(A,B,C,D,E,F,G1,G2),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

比赛链接

这场比较有意思,可惜最近太忙了没法仔细研究,只能看看别人的题解然后补掉了。这场还是比较难的。

C,E是构造,D是个模拟,F需要先推出一个结论,之后二分查找,G题是字符串的题,需要先用拓展kmp预处理一下,然后做法也很多,有根号分治,分治+剪枝,还有一个按顺序加数然后二分的做法。FG比较难,解题思路确实很妙。


A. Maximize?

题意:

给你一个整数 x x x 。你的任务是找出任意一个整数 y y y ,使得 gcd ⁡ ( x , y ) + y \gcd(x,y)+y gcd(x,y)+y 最大。 ( 1 ≤ y < x ) (1\le y\lt x) (1y<x) 使得 gcd ⁡ ( x , y ) + y \gcd(x,y)+y gcd(x,y)+y 最大。

注意,如果有一个以上的 y y y 满足该语句,那么你可以找出任何一个。

gcd ⁡ ( a , b ) \gcd(a,b) gcd(a,b) a a a b b b 的最大公约数。例如, gcd ⁡ ( 6 , 4 ) = 2 \gcd(6,4)=2 gcd(6,4)=2

思路:

直接暴力枚举

code:

#include <iostream>
#include <cstdio>
using namespace std;int T,x;int gcd(int a,int b){while(b)b^=a^=b^=a%=b;return a;
}int main(){cin>>T;while(T--){cin>>x;int ans=0,maxx=0;for(int y=1,d;y<x;y++){d=gcd(x,y);if(d+y>maxx)ans=y,maxx=d+y;}cout<<ans<<endl;}return 0;
}

B. Prefiquence

题意:

给你两个二进制字符串 a a a b b b 。二进制字符串是由字符 "0 "和 "1 "组成的字符串。

您的任务是确定最大可能的数字 k k k ,使得长度为 k k k 的字符串 a a a 的前缀是字符串 b b b 的子序列。

如果 a a a 可以从 b b b 中删除几个(可能是零个或全部)元素,那么序列 a a a 就是序列 b b b 的子序列。

思路:

按顺序枚举 a a a 的每个字符,找到 b b b 串中可以与它对应的字符,对应起来。模拟一遍这个过程即可知道 a a a 最多能对应到长度为几的前缀了。

code:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;int T,n,m;
string a,b;int main(){cin>>T;while(T--){cin>>n>>m>>a>>b;a=" "+a;b=" "+b;int i=0,j=0;while(i+1<=n && j+1<=m){while(j+1<=m && a[i+1]!=b[j+1])j++;if(i+1<=n && j+1<=m)i++,j++;}cout<<i<<endl;}return 0;
}

C. Assembly via Remainders

题意:

给你一个数组 x 2 , x 3 , … , x n x_2,x_3,\dots,x_n x2,x3,,xn 。你的任务是找出任意一个数组 a 1 , … , a n a_1,\dots,a_n a1,,an , 其中:

  • 1 ≤ a i ≤ 1 0 9 1\le a_i \le 10^9 1ai109 代表所有的 1 ≤ i ≤ n 1\le i\le n 1in
  • x i = a i m o d a i − 1 x_i=a_i \bmod a_{i-1} xi=aimodai1 代表所有 2 ≤ i ≤ n 2\le i\le n 2in

这里的 c m o d d c\bmod d cmodd 表示整数 c c c 除以整数 d d d 的余数。例如 5 m o d 2 = 1 5 \bmod 2 = 1 5mod2=1 , 72 m o d 3 = 0 72 \bmod 3 = 0 72mod3=0 , 143 m o d 14 = 3 143 \bmod 14 = 3 143mod14=3

注意,如果有一个以上的 a a a 满足该语句的要求,你可以找到任何一个。

思路:

x i = a i m o d a i − 1 x_i=a_i \bmod a_{i-1} xi=aimodai1,如果 a i − 1 a_{i-1} ai1 是个很大的数的话,那么 a i a_i ai 的取值就很比较少,为了让 a i a_i ai 也保持一个很大的状态,方便后面的数来用,那么我们可以令 a i = a i − 1 + x i a_i=a_{i-1}+x_i ai=ai1+xi,因为 a i − 1 a_{i-1} ai1 很大,比 x i x_i xi 大得多,所以我们这样可以保证模出来就是 x i x_i xi

每个位置都是这样,就可以从第一个数直接算出后面 n n n 个数是什么了,不过因为规定 a i ≤ 1 0 9 a_i\le 10^9 ai109,所以我们不好确定第一个数填什么,我们可以反过来想,直接规定 a n = 1 0 9 a_n=10^9 an=109,然后往前推就行了。

code:

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=505;int T,n;
int a[maxn],x[maxn];int main(){cin>>T;while(T--){cin>>n;for(int i=2;i<=n;i++)cin>>x[i];a[n]=1e9;for(int i=n-1;i>=1;i--){a[i]=a[i+1]-x[i+1];}for(int i=1;i<=n;i++)cout<<a[i]<<" \n"[i==n];}return 0;
}

D. Permutation Game

题意:

Bodya 和 Sasha 发现了一个排列 p 1 , … , p n p_1,\dots,p_n p1,,pn 和一个数组 a 1 , … , a n a_1,\dots,a_n a1,,an 。他们决定玩一个著名的 “排列游戏”。

长度为 n n n 的排列是由 n n n 个不同的整数组成的数组,这些整数从 1 1 1 n n n 按任意顺序排列。例如, [ 2 , 3 , 1 , 5 , 4 ] [2,3,1,5,4] [2,3,1,5,4] 是一个排列,但 [ 1 , 2 , 2 ] [1,2,2] [1,2,2] 不是一个排列( 2 2 2 在数组中出现了两次), [ 1 , 3 , 4 ] [1,3,4] [1,3,4] 也不是一个排列( n = 3 n=3 n=3 ,但数组中有 4 4 4 )。

它们都在排列中选择了一个起始位置。

对局持续了 k k k 个回合。棋手同时下棋。在每个回合中,每个棋手都会发生两件事:

  • 如果棋手当前的位置是 x x x ,他的得分就会增加 a x a_x ax
  • 然后棋手要么停留在当前位置 x x x ,要么从 x x x 移动到 p x p_x px

在整整 k k k 个回合后,得分较高的一方即为获胜者。

已知博迪娅的起始位置 P B P_B PB 和萨沙的起始位置 P S P_S PS ,如果双方都想获胜,请判断谁会赢得对局。

思路:

如果我们在 i i i,选择移动的话,就会移动到 a i a_i ai 位置,我们可以看作是一条 i → a i i\rightarrow a_i iai 的单向边,我们停留在第 i i i 个位置,就相当于停留在编号为 i i i 的点上。如果 a a a 数组是一个排列的话,这个图就会呈现出一些非常美妙的性质。

因为每个点都会作为出发点一次,所以每个点的出度为 1 1 1,因为 a a a 数组是一个排列,因此每个点的入度也为 1 1 1,因此这个图只存在环(自环也看作是一个环), n n n 个点就组成了若干的互不干扰的若干个环,每个点 在且仅在 一个环上。

在这个题里,我们想要分数尽可能大,那么我们可以选择向着环上的下一个位置进发,也可以停留在这个位置,下一回合继续拿这里的分。显然我们想要停在分数尽可能大的点上,不过一路走过去也可能还不如一直在原点呆着吃分。

但是我们如果决定了留在某个点上,分数的计算是相当简单的,我们只需要用一路上的分数加上剩余回合数乘上这个点的分数即可。因此我们可以一路走完这个环上的所有点,算出停留在环上任意一点的分数,保留最大值即可。(不过不知道的话也不影响做出来这题,无非是改成向下走 n n n 次而已)

两个人都这样算一下,然后比较即可。

code:

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=2e5+5;
typedef long long ll;int T,n,k,p1,p2;
int p[maxn],a[maxn];int main(){cin>>T;while(T--){cin>>n>>k>>p1>>p2;for(int i=1;i<=n;i++)cin>>p[i];for(int i=1;i<=n;i++)cin>>a[i];ll m1=1ll*a[p1]*k,m2=1ll*a[p2]*k;ll tmp=a[p1],tk=k-1;for(int id=p[p1];id!=p1 && tk;id=p[id]){m1=max(m1,1ll*a[id]*tk+tmp);tmp+=a[id];tk--;}tmp=a[p2];tk=k-1;for(int id=p[p2];id!=p2 && tk;id=p[id]){m2=max(m2,1ll*a[id]*tk+tmp);tmp+=a[id];tk--;}puts((m1==m2)?"Draw":((m1>m2)?"Bodya":"Sasha"));}return 0;
}

E. Cells Arrangement

题意:

给你一个整数 n n n 。您在网格 n × n n\times n n×n 中选择了 n n n 单元格 ( x 1 , y 1 ) , ( x 2 , y 2 ) , … , ( x n , y n ) (x_1,y_1), (x_2,y_2),\dots,(x_n,y_n) (x1,y1),(x2,y2),,(xn,yn) ,其中 1 ≤ x i ≤ n 1\le x_i\le n 1xin 1 ≤ y i ≤ n 1\le y_i\le n 1yin

H \mathcal{H} H 成为任意一对单元格之间不同的曼哈顿距离集合。你的任务是最大化 H \mathcal{H} H 的大小。注释中给出了集合及其构造的例子。

如果存在不止一个解,你可以输出任意一个。

单元格 ( x 1 , y 1 ) (x_1,y_1) (x1,y1) ( x 2 , y 2 ) (x_2,y_2) (x2,y2) 之间的曼哈顿距离等于 ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ |x_1-x_2|+|y_1-y_2| x1x2+y1y2

思路:

直接说构造方法,第一个点放在 ( 1 , 1 ) (1,1) (1,1),第二个点放在 ( 2 , 1 ) (2,1) (2,1),之后第 i i i 个点放在 ( i , i ) (i,i) (i,i)

为什么这样凑得到的不同数的个数就是最大的?因为最多有 0 ∼ 2 n − 2 0\sim 2n-2 02n2 2 n − 1 2n-1 2n1 个不同的数(两个点最大的曼哈顿距离就是一个角到另一个角),因此只要包括了所有的 2 n − 1 2n-1 2n1 个数就是最大的,上面这个构造方法就可以。

前两个点可以保证凑出 0 , 1 0,1 0,1,然后后面 n − 2 n-2 n2 个点相邻点之间曼哈顿距离差 2 2 2,所以相邻的点可以得到 2 2 2,中间隔一个点就能得到 4 4 4,类推可以得到 6 , 8 , … , 2 n − 6 6,8,\dots,2n-6 6,8,,2n6,点 ( 1 , 1 ) (1,1) (1,1) ( n − 1 , n − 1 ) , ( n , n ) (n-1,n-1),(n,n) (n1,n1),(n,n) 可以得到 2 n − 4 , 2 n − 2 2n-4,2n-2 2n4,2n2,这样就凑出了所有的偶数。 ( 2 , 1 ) (2,1) (2,1) ( 3 , 3 ) (3,3) (3,3) 曼哈顿距离差 3 3 3,之后每隔一个点距离加 2 2 2,就可以得到 3 , 5 , 7 , … , 2 n − 3 3,5,7,\dots,2n-3 3,5,7,,2n3,这样就凑出了所有奇数。因此这种凑法可以凑出 0 ∼ 2 n − 2 0\sim 2n-2 02n2 中的所有数。

code:

#include <iostream>
#include <cstdio>
#include <algorithm>
#define pii pair<int,int>
using namespace std;
const int maxn=1e3+5;int T,n;
pii pos[maxn]={{0,0},{1,1},{1,2},{3,3}};int main(){for(int i=4;i<=1e3;i++)pos[i]=pii(i,i);cin>>T;while(T--){cin>>n;for(int i=1;i<=n;i++){auto [x,y]=pos[i];cout<<x<<" "<<y<<endl;}cout<<endl;}return 0;
}

F. Equal XOR Segments

题意:

如果可以将数组分成 k > 1 k\gt1 k>1 部分,使得每部分的值的 bitwise XOR 都相等,那么我们就可以称这个数组为 x 1 , … , x m x_1,\dots,x_m x1,,xm 有意思的数组。

更具体地说,你必须将数组 x x x 分成 k k k 个连续的部分, x x x 中的每个元素都必须准确地属于 1 1 1 个部分。设 y 1 , … , y k y_1,\dots,y_k y1,,yk 分别是各部分元素的 XOR。那么 y 1 = y 2 = ⋯ = y k y_1=y_2=\dots=y_k y1=y2==yk 必须满足。

例如,如果是 x = [ 1 , 1 , 2 , 3 , 0 ] x = [1, 1, 2, 3, 0] x=[1,1,2,3,0] ,可以将其拆分如下: [ 1 ] , [ 1 ] , [ 2 , 3 , 0 ] [\color{blue}1], [\color{green}1], [\color{red}2, \color{red}3, \color{red}0] [1],[1],[2,3,0] .事实上是 1 = 1 = 2 ⊕ 3 ⊕ 0 \color{blue}1=\color{green}1=\color{red}2 \oplus \color{red}3\oplus \color{red}0 1=1=230

给你一个数组 a 1 , … , a n a_1,\dots,a_n a1,,an 。你的任务是回答 q q q 个查询:

  • 对于固定的 l l l , r r r , 判断子数组 a l , a l + 1 , … , a r a_l,a_{l+1},\dots,a_r al,al+1,,ar 是否有趣。

思路:

因为我们要算区间异或和,所以我们先预处理出 a a a 数组的前缀异或和 s s s 数组,这样 a l ⊕ a l + 1 ⊕ ⋯ ⊕ a r = s r ⊕ s l − 1 a_l\oplus a_{l+1}\oplus \dots \oplus a_r=s_r\oplus s_{l-1} alal+1ar=srsl1

上面 y 1 = y 2 = ⋯ = y k y_1=y_2=\dots=y_k y1=y2==yk 的话,就说明 y 1 ⊕ y 2 = 0 y_1\oplus y_2=0 y1y2=0 同理后面两个两个抵消,如果 k k k 是奇数的话,那么 y 1 = y 2 = ⋯ = y k = y 1 ⊕ y 2 ⊕ ⋯ ⊕ y k = s n y_1=y_2=\dots=y_k=y_1\oplus y_2\oplus \dots \oplus y_k=s_n y1=y2==yk=y1y2yk=sn 也就是说所有的 y y y 值都等于 a a a 数组所有数的异或和。同理 k k k 是偶数的时候可以推出来 y 1 ⊕ y 2 ⊕ ⋯ ⊕ y k = s n = 0 y_1\oplus y_2\oplus \dots \oplus y_k=s_n=0 y1y2yk=sn=0

因为划分分区是我们决定的,因此我们可以尽可能少的划分分区。可以发现,如果存在可行的划分方案,我们一定可以划分出两个或三个分区。因为 y y y 是相同的,所以如果存在一个划分方案,我们可以两个相邻分区合并一下,也就是异或一下区间异或和变成 0 0 0,然后合并到相邻分区即可,最后一定会剩下两个或三个分区。

  • s n = 0 s_n=0 sn=0 时,我们随便划分就行了,两个分区即可。划出一个分区,另一半的异或值一定等于它(因为总异或值为 0 0 0)。

  • s n ≠ 0 s_n\not=0 sn=0 时,我们就需要划出三个分区,因为每个分区异或和相同,且等于总的异或值 s n s_n sn,因此第一个分区的异或和为 s n s_n sn,前两个分区异或和就是 0 0 0

第一种情况很简单,第二种情况,我们假设左右端点是 l , r l,r l,r,三个分区分界点分别是 x , y x,y x,y l ∼ x l\sim x lx 是第一个分区, x + 1 ∼ y x+1 \sim y x+1y 是第二个, y + 1 ∼ r y+1\sim r y+1r 是第三个),满足情况的 x , y x,y x,y 就需要 s x ⊕ s l − 1 = s n , s y ⊕ s l − 1 = 0 s_x\oplus s_{l-1}=s_n,s_y\oplus s_{l-1}=0 sxsl1=sn,sysl1=0,即 s x = s n ⊕ s l − 1 , s y = s l − 1 s_x=s_n\oplus s_{l-1},s_y=s_{l-1} sx=snsl1,sy=sl1 l < x < y < r l<x<y<r l<x<y<r

如何查找 l ∼ r l\sim r lr 中有没有等于上面值的位置呢?我们可以对每个数开一个桶,按顺序存储所有等于这个数的位置,然后要找的时候直接去对应桶里二分查找即可,因为值域很大,桶我们用 m a p map map

code:

#include <iostream>
#include <cstdio>
#include <map>
#include <vector>
#include <algorithm>
using namespace std; 
const int maxn=2e5+5;int T,n,q;
int a[maxn];
map<int,vector<int> > val;int main(){cin>>T;while(T--){cin>>n>>q;val.clear();val[0].push_back(0);for(int i=1;i<=n;i++){cin>>a[i];a[i]^=a[i-1];val[a[i]].push_back(i);}for(int i=1,l,r;i<=q;i++){cin>>l>>r;l--;if(a[r]==a[l])puts("YES");else {auto it1=lower_bound(val[a[r]].begin(),val[a[r]].end(),l+1),it2=upper_bound(val[a[l]].begin(),val[a[l]].end(),r-1);if(it1==val[a[r]].end() || it2==val[a[l]].begin()){puts("NO");continue;}else it2--;int x=*it1,y=*it2;if(l<x && x<y && y<r)puts("YES");else puts("NO");}}}return 0;
}

G1. Division + LCP (easy version)

题意:

这是问题的简单版本。在此版本中 l = r l=r l=r

给你一个字符串 s s s 。对于固定的 k k k ,考虑将 s s s 恰好分成 k k k 个连续的子串 w 1 , … , w k w_1,\dots,w_k w1,,wk 。假设 f k f_k fk 是所有分割中最大的 L C P ( w 1 , … , w k ) LCP(w_1,\dots,w_k) LCP(w1,,wk)

L C P ( w 1 , … , w m ) LCP(w_1,\dots,w_m) LCP(w1,,wm) 是字符串 w 1 , … , w m w_1,\dots,w_m w1,,wm 的最长公共前缀的长度。

例如,如果 s = a b a b a b c a b s=abababcab s=abababcab k = 4 k=4 k=4 ,可能的除法是 a b a b a b c a b \color{red}{ab}\color{blue}{ab}\color{orange}{abc}\color{green}{ab} abababcab 。由于 a b ab ab 是这四个字符串的最长公共前缀,因此 L C P ( a b , a b , a b c , a b ) LCP(\color{red}{ab},\color{blue}{ab},\color{orange}{abc},\color{green}{ab}) LCP(ab,ab,abc,ab) 就是 2 2 2 。请注意,每个子串都由一段连续的字符组成,每个字符都属于个子串。

您的任务是找出 f l , f l + 1 , … , f r f_l,f_{l+1},\dots,f_r fl,fl+1,,fr在本版本中为 l = r l=r l=r

思路:

思路看的这个大佬的

首先 w 1 w_1 w1 一定是这个串 s s s 的前缀,我们要 L C P LCP LCP x x x 长度的公共前缀,首先每个 w w w 串都要与串 s s s 至少有 x x x 长度的公共前缀。因此我们要算出从每个位置 i i i 开始的子串与这个串的最长公共前缀。这个东西就是拓展kmp,也叫Z算法(讲解可以看这个,从1小时3分开始讲),我们 O ( n ) O(n) O(n) 跑一遍拓展kmp,就得到了从每个位置 i i i 开始的子串与这个串的最长公共前缀长度 z [ i ] z[i] z[i]

直接算 L C P LCP LCP 不好算,但是我们验证 L C P LCP LCP 是好验证的。上面说了我们要 L C P LCP LCP x x x 长度的公共前缀,每个 w w w 串都要与串 s s s x x x 长度的公共前缀,因此选做起点的那个位置 i i i z [ i ] z[i] z[i] 值需要大于等于 x x x 即可,然后我们把以这个后面 i ∼ i + x − 1 i\sim i+x-1 ii+x1 个位置的字符就划给了一个 w w w 串,一个 w w w 串就凑出来了。之后我们从 i + x i+x i+x 继续向后找,假如说下一个 w w w 串从 j j j 开始,中间 i + x ∼ j − 1 i+x\sim j-1 i+xj1 的字符我们就可以直接划给前面的 w w w 串。我们这样找一遍就能找到最多的 w w w 串。

举个例子,题目上 s = a b a b a b c a b s=abababcab s=abababcab L C P = 2 LCP=2 LCP=2,我们可以算出 z z z 数组如下:

s: a b a b a b c a b
z: 9 0 4 0 2 0 0 2 0

首先从第 0 0 0 个位置开始找, z [ 0 ] = 9 ≥ 2 z[0]=9\ge2 z[0]=92,因此前 L C P = 2 LCP=2 LCP=2 个字符划给 w 1 w_1 w1。再从第 0 + L C P = 2 0+LCP=2 0+LCP=2 个位置开始找, z [ 2 ] = 4 ≥ 2 z[2]=4\ge2 z[2]=42,因此接下来的 2 2 2 个字符划给 w 2 w_2 w2。再从第 4 4 4 个位置开始找, z [ 4 ] = 2 ≥ 2 z[4]=2\ge2 z[4]=22,因此接下来的 2 2 2 个字符划给 w 3 w_3 w3。再从第 6 6 6 个位置开始找, z [ 6 ] = 0 < 2 , z [ 7 ] = 2 ≥ 2 z[6]=0\lt2,z[7]=2\ge2 z[6]=0<2,z[7]=22,因此第六个字符划给前面的 w 3 w_3 w3,接下来第 7 , 8 7,8 7,8 个字符划给 w 4 w_4 w4

因为 L C P LCP LCP 越小,满足条件产生的 w w w 串的个数 k k k 就越多,也就是说 L C P LCP LCP 是单调的,因此我们可以二分来找这个 L C P LCP LCP,时间复杂度就是 O ( n log ⁡ n ) O(n\log n) O(nlogn) 了。

code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=2e5+5;int T,n,L,R;
int z[maxn];
string s;void init(){z[0]=n;for(int i=1,id=0,r=0,len;i<n;i++){len=(i<r)?min(z[i-id],r-i):0;while(i+len<n && s[i+len]==s[len])len++;if(i+len>r){r=i+len;id=i;}z[i]=len;}
}int main(){cin>>T;while(T--){cin>>n>>L>>R>>s;init();
//		for(int i=0;i<n;i++)cout<<z[i]<<" \n"[i==n-1];int l=0,r=n,mid;while(l<r){mid=(l+r+1)>>1;int k=0;for(int i=0;i<n;){if(z[i]>=mid)k++,i+=mid;else i++;}if(k>=L)l=mid;else r=mid-1;}cout<<l<<endl;}return 0;
}

G2. Division + LCP (hard version)

题意:

这是问题的困难版本。在此版本中 l ≤ r l\le r lr

给你一个字符串 s s s 。对于固定的 k k k ,考虑将 s s s 恰好分成 k k k 个连续的子串 w 1 , … , w k w_1,\dots,w_k w1,,wk 。假设 f k f_k fk 是所有分割中最大的 L C P ( w 1 , … , w k ) LCP(w_1,\dots,w_k) LCP(w1,,wk)

L C P ( w 1 , … , w m ) LCP(w_1,\dots,w_m) LCP(w1,,wm) 是字符串 w 1 , … , w m w_1,\dots,w_m w1,,wm 的最长公共前缀的长度。

例如,如果 s = a b a b a b c a b s=abababcab s=abababcab k = 4 k=4 k=4 ,可能的除法是 a b a b a b c a b \color{red}{ab}\color{blue}{ab}\color{orange}{abc}\color{green}{ab} abababcab 。由于 a b ab ab 是这四个字符串的最长公共前缀,因此 L C P ( a b , a b , a b c , a b ) LCP(\color{red}{ab},\color{blue}{ab},\color{orange}{abc},\color{green}{ab}) LCP(ab,ab,abc,ab) 就是 2 2 2 。请注意,每个子串都由一段连续的字符组成,每个字符都属于个子串。

您的任务是找出 f l , f l + 1 , … , f r f_l,f_{l+1},\dots,f_r fl,fl+1,,fr

思路1(根号分治):

最坏情况下就是 l = 1 , r = n l=1,r=n l=1,r=n,我们需要算出所有 f f f 值。上面方法算一个 f f f 值就要花 n log ⁡ n n\log n nlogn 的时间,不可能大量来用。发现当 i i i 比较大的时候,后面不同的 f f f 值就会变的非常稀疏。仔细一想其实也比较好证明为什么,因为划分了 i i i 个串,每个串最多分到 n / i n/i n/i 个字符,更别说相同的前缀长度了。

如果 i ≤ n i\le \sqrt n in ,那么 i i i 的取值 n \sqrt n n 个,不同的 f f f 取值也最多只有 n \sqrt n n 个。如果 i > n i\gt \sqrt n i>n ,那么每个串最多分到 n \sqrt n n 个字符,前缀长度也一定少于 n \sqrt n n ,因此取值最多只有 n \sqrt n n 个,也就是 f f f 的取值只有 n \sqrt n n 个。综上, f f f 的取值最多只有 2 n 2 \sqrt n 2n 个。

感觉很可以根号分治,前面 n \sqrt n n f f f 我们直接暴力来算也无所谓,时间复杂度 O ( n n log ⁡ n ) O(n\sqrt n\log n) O(nn logn),主要是后面的 f f f 如何算。


发现验证一个 L C P LCP LCP 的时候,可以算出它最多可以划分出几个 w w w 串,又因为 L C P LCP LCP 是单调的,所以如果一个 L C P LCP LCP 如果可以划分出若干个 w w w 串,那么更小的 L C P LCP LCP 一定至少也可以划分出这些 w w w 串,划分更少 w w w 串时,前缀长度一定大于等于这个值。

f i f_i fi 其实就是划分 i i i 个串时的 L C P LCP LCP,如果我们验证一个 L C P = x LCP=x LCP=x 得到最多可以划分出 k k k w w w 串,那么相当于 f k = x f_k=x fk=x w w w 串个数越少,能得到的前缀长度就越大,也就是 f 1 ∼ k − 1 ≥ x f_{1\sim k-1}\ge x f1k1x,若 f k ′ = x + 1 f_{k'}=x+1 fk=x+1,那么中间的 f f f 就能知道了,是 f k ′ + 1 ∼ k = x f_{k'+1\sim k}=x fk+1k=x

对块数 i i i 大于等于 n \sqrt n n 的划分,它们的最大前缀长度是 ≤ n \le\sqrt n n 的,所以我们对每个 ≤ n \le\sqrt n n 的前缀长度算一下最多可以划分多少个串,然后放进 f f f 数组中。最后跑一下后缀最大值,这样 i ≥ n i\ge\sqrt n in 直接查 f f f 数组即可,时间复杂度 O ( n ) O(n) O(n)

code1:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
const int maxn=2e5+5;int T,n,L,R;
string s;int z[maxn];
void init(){z[0]=n;for(int i=1,len,id,r=0;i<n;i++){len=(r>i)?min(z[i-id],r-i):0;while(i+len<n && s[i+len]==s[len])len++;if(i+len>r){r=i+len;id=i;}z[i]=len;}
}
int div(int x){//前缀长度为x时能分出的最多块数 int cnt=0;for(int i=0;i<n;i++){if(z[i]>=x){i=i+x-1;cnt++;}}return cnt;
}
int calc(int k){//二分计算分成k块的最大前缀长度 int l=0,r=n,mid;while(l<r){mid=(l+r+1)>>1;if(div(mid)>=k)l=mid;else r=mid-1;}return l;
}int main(){
//	cin.tie(0)->sync_with_stdio(false);cin>>T;while(T--){cin>>n>>L>>R>>s;vector<int> f(n+2,0);
//		s=" "+s;init();
//		for(int i=0;i<n;i++)cout<<z[i]<<" \n"[i==n-1];int t=min(450,n);//分块阈值//分的块数超出k,预处理 //分的块数小于k,直接算for(int i=1;i<=n/t;i++)//枚举前缀长度 分t块 前缀长度最多n/t f[div(i)]=i; for(int i=n;i>=1;i--)f[i]=max(f[i+1],f[i]);for(int i=L;i<=R;i++){if(i>=t)cout<<f[i];else cout<<calc(i);cout<<" \n"[i==R];}}return 0;
}

思路2(分治,剪枝):

还是上面的结论, f f f 的取值最多只有 2 n 2 \sqrt n 2n 个,而且 f f f 的值还是单调(递减)的。所以如果我们算出来某个 f l = x f_l=x fl=x f r = x f_r=x fr=x,那么我们直接就可以确定 f l ∼ r = x f_{l\sim r}=x flr=x

因此我们直接对这个区间进行分治,比如对 1 ∼ n 1\sim n 1n 的区间,它的取值范围就是 f n ∼ f 1 f_{n}\sim f_1 fnf1,我们把它拆成两段 1 ∼ m i d , m i d + 1 ∼ n 1\sim mid,mid+1\sim n 1mid,mid+1n,取值范围分别变成了 f m i d ∼ f 1 , f n ∼ f m i d + 1 f_{mid}\sim f_1,f_{n}\sim f_{mid+1} fmidf1,fnfmid+1,我们一直拆下去,遇到取值只能是一个数的区间就可以直接返回了。

剪枝前就是 n 2 log ⁡ n n^2\log n n2logn,剪枝后时间复杂度比较玄学,说一下我的见解。对一个取值,我们能找到它的边界最多需要 log ⁡ n \log n logn 次计算,因为最多有 2 n 2 \sqrt n 2n 个取值,因此需要 2 n log ⁡ n 2 \sqrt n\log n 2n logn 次计算,总的计算次数为 2 n n log ⁡ 2 n 2 n\sqrt n\log^2 n 2nn log2n,虽然看起来很会 T T T 的样子,但是实际上跑的还是蛮快的,而且写法简单。(希望只是我复杂度算假了,如果有会算的可以评论一下,感谢)

code2:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
const int maxn=2e5+5;int T,n,L,R;
string s;int z[maxn];
void init(){z[0]=n;for(int i=1,len,id,r=0;i<n;i++){len=(r>i)?min(z[i-id],r-i):0;while(i+len<n && s[i+len]==s[len])len++;if(i+len>r){r=i+len;id=i;}z[i]=len;}
}
int div(int x){//前缀长度为x时能分出的最多块数 int cnt=0;for(int i=0;i<n;i++){if(z[i]>=x){i=i+x-1;cnt++;}}return cnt;
}
int calc(int k){//二分计算分成k块的最大前缀长度 int l=0,r=n,mid;while(l<r){mid=(l+r+1)>>1;if(div(mid)>=k)l=mid;else r=mid-1;}return l;
}
int ans[maxn];
void segement(int l,int r,int x,int y){if(x==y){for(int i=l;i<=r;i++)ans[i]=x;return;}int mid=(l+r)>>1;segement(l,mid,x,calc(mid));segement(mid+1,r,calc(mid+1),y);return;
}int main(){
//	cin.tie(0)->sync_with_stdio(false);cin>>T;while(T--){cin>>n>>L>>R>>s;init();segement(L,R,calc(L),calc(R));for(int i=L;i<=R;i++)cout<<ans[i]<<" \n"[i==R];}return 0;
}

思路3:

我们仔细看一下对一个 L C P = x LCP=x LCP=x 算分的块数的过程。就是先找到最近的 ≥ x \ge x x z z z 值,然后再从这个位置向后 x x x 位,再找下一个满足的 z z z 值。我们一位一位向后找很没效率,那么我们处理出所有 ≥ x \ge x x z z z 值的下标,在里面二分地依次找不就行了。

这样我们原本 O ( n ) O(n) O(n) 找一遍就变成了 O ( log ⁡ 2 n ) O(\log^2 n) O(log2n) 找一遍,找到后还是和思路1的预处理的方法一样。如果结果是 k k k,就得到了 f k = x f_k=x fk=x L C P LCP LCP n n n 1 1 1 枚举一遍,算出结果并记录到 f f f 数组上,最后再跑个后缀最大值即可。

不过我们不可能开 n n n 个桶,用桶装所有 ≥ x \ge x x z z z 值的下标,最坏是 n 2 n^2 n2 的。我们用桶装 = x =x =x z z z 值的下标,然后从 n n n 1 1 1 按顺序访问,用一个 s e t set set 存储访问过的下标,这样我们依次访问,在访问到第 x x x 个桶的时候, s e t set set 中正好就存储了所有 ≥ x \ge x x z z z 值的下标,一遍访问一边算即可。

code3:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <set>
using namespace std;
const int maxn=2e5+5;int T,n,L,R;
string s;int z[maxn];
vector<int> a[maxn];
void init(){z[0]=n;a[z[0]].push_back(0);for(int i=1,len,id,r=0;i<n;i++){len=(r>i)?min(z[i-id],r-i):0;while(i+len<n && s[i+len]==s[len])len++;if(i+len>r){r=i+len;id=i;}z[i]=len;a[z[i]].push_back(i);}
}
int div(int x){//前缀长度为x时能分出的最多块数 int cnt=0;for(int i=0;i<n;i++){if(z[i]>=x){i=i+x-1;cnt++;}}return cnt;
}
int ans[maxn];int main(){
//	cin.tie(0)->sync_with_stdio(false);cin>>T;while(T--){cin>>n>>L>>R>>s;for(int i=1;i<=n;i++)a[i].clear(),ans[i]=0;init();set<int> S;for(int i=n;i>=1;i--){for(auto idx:a[i])S.insert(idx);int cnt=0;for(int j=0,nxt;j<n;){auto it=S.lower_bound(j);if(it==S.end())break;j=*it+i;cnt++;}ans[cnt]=max(ans[cnt],i);}for(int i=n-1;i>=1;i--)ans[i]=max(ans[i+1],ans[i]);for(int i=L;i<=R;i++)cout<<ans[i]<<" \n"[i==R];}return 0;
}

这篇关于Codeforces Round 943 (Div. 3)(A,B,C,D,E,F,G1,G2)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/979907

相关文章

Codeforces Round #240 (Div. 2) E分治算法探究1

Codeforces Round #240 (Div. 2) E  http://codeforces.com/contest/415/problem/E 2^n个数,每次操作将其分成2^q份,对于每一份内部的数进行翻转(逆序),每次操作完后输出操作后新序列的逆序对数。 图一:  划分子问题。 图二: 分而治之,=>  合并 。 图三: 回溯:

Codeforces Round #261 (Div. 2)小记

A  XX注意最后输出满足条件,我也不知道为什么写的这么长。 #define X first#define Y secondvector<pair<int , int> > a ;int can(pair<int , int> c){return -1000 <= c.X && c.X <= 1000&& -1000 <= c.Y && c.Y <= 1000 ;}int m

Codeforces Beta Round #47 C凸包 (最终写法)

题意慢慢看。 typedef long long LL ;int cmp(double x){if(fabs(x) < 1e-8) return 0 ;return x > 0 ? 1 : -1 ;}struct point{double x , y ;point(){}point(double _x , double _y):x(_x) , y(_y){}point op

Codeforces Round #113 (Div. 2) B 判断多边形是否在凸包内

题目点击打开链接 凸多边形A, 多边形B, 判断B是否严格在A内。  注意AB有重点 。  将A,B上的点合在一起求凸包,如果凸包上的点是B的某个点,则B肯定不在A内。 或者说B上的某点在凸包的边上则也说明B不严格在A里面。 这个处理有个巧妙的方法,只需在求凸包的时候, <=  改成< 也就是说凸包一条边上的所有点都重复点都记录在凸包里面了。 另外不能去重点。 int

Codeforces 482B 线段树

求是否存在这样的n个数; m次操作,每次操作就是三个数 l ,r,val          a[l] & a[l+1] &......&a[r] = val 就是区间l---r上的与的值为val 。 也就是意味着区间[L , R] 每个数要执行 | val 操作  最后判断  a[l] & a[l+1] &......&a[r] 是否= val import ja

CSS实现DIV三角形

本文内容收集来自网络 #triangle-up {width: 0;height: 0;border-left: 50px solid transparent;border-right: 50px solid transparent;border-bottom: 100px solid red;} #triangle-down {width: 0;height: 0;bor

创建一个大的DIV,里面的包含两个DIV是可以自由移动

创建一个大的DIV,里面的包含两个DIV是可以自由移动 <body>         <div style="position: relative; background:#DDF8CF;line-height: 50px"> <div style="text-align: center; width: 100%;padding-top: 0px;"><h3>定&nbsp;位&nbsp;

Codeforces Round 971 (Div. 4) (A~G1)

A、B题太简单,不做解释 C 对于 x y 两个方向,每一个方向至少需要 x / k 向上取整的步数,取最大值。 由于 x 方向先移动,假如 x 方向需要的步数多于 y 方向的步数,那么最后 y 方向的那一步就不需要了,答案减 1 代码 #include <iostream>#include <algorithm>#include <vector>#include <string>

CF#271 (Div. 2) D.(dp)

D. Flowers time limit per test 1.5 seconds memory limit per test 256 megabytes input standard input output standard output 题目链接: http://codeforces.com/contest/474/problem/D We s

CF #278 (Div. 2) B.(暴力枚举+推导公式+数学构造)

B. Candy Boxes time limit per test 1 second memory limit per test 256 megabytes input standard input output standard output 题目链接: http://codeforces.com/contest/488/problem/B There