0%

题目大意

题目描述

给定一个长度为 nn 的目标数组 TT

现有一个与 TT 等长的初始数组 SS,对 SS 进行恰好一次如下操作:
选择一个整数 kk,并将数组 SS 划分为 kk 个连续且不重叠的子段。假设这 kk 个子段的下标区间依次为 [l1,r1],[l2,r2],,[lk,rk][l_1, r_1], [l_2, r_2], \dots, [l_k, r_k],满足 l1=1l_1=1rk=nr_k=n,且对于所有的 1ik11 \le i \le k-1,都有 ri+1=li+1r_i + 1 = l_{i+1}
然后,对这 kk 个子段分别独立地进行翻转操作。

即,选择的区间形如:

image

请求出有多少种不同的初始数组 SS,可以通过上述操作变为目标数组 TT。由于答案可能很大,请输出其对 998244353998244353 取模后的结果。

输入格式

第一行包含一个整数 tt1t80001 \le t \le 8000),表示测试用例的数量。
每组测试用例的第一行包含一个整数 nn1n80001 \le n \le 8000),表示数组的长度。
第二行包含 nn 个整数 T1,T2,,TnT_1, T_2, \dots, T_n1Ti80001 \le T_i \le 8000),表示目标数组 TT 的元素。
保证所有测试用例中 nn 的总和不超过 80008000

输出格式

对于每组测试用例,输出一个整数,表示满足条件的数组 SS 的数量对 998244353998244353 取模后的结果。

样例输入

1
2
3
4
5
6
7
8
9
10
11
5
4
1 1 2 1
4
1 2 3 1
6
1 3 2 3 3 2
10
2 3 1 4 3 1 4 3 1 2
1
8000

样例输出

1
2
3
4
5
4
7
22
383
1

样例解释

在第一组测试用例中,T=[1,1,2,1]T = [1, 1, 2, 1],以下 44 种数组 SS 可以被转换为 TT

  • 对于 S=[2,1,1,1]S = [2, 1, 1, 1],可以选择区间 [1,3][1, 3][4,4][4, 4] 进行翻转。翻转后数组变为 [1,1,2,1][1, 1, 2, 1],等于 TT

  • 对于 S=[1,2,1,1]S = [1, 2, 1, 1],可以选择区间 [1,4][1, 4] 进行翻转。

  • 对于 S=[1,1,2,1]S = [1, 1, 2, 1],可以选择区间 [1,2][1, 2][3,3][3, 3][4,4][4, 4] 进行翻转。

  • 对于 S=[1,1,1,2]S = [1, 1, 1, 2],可以选择区间 [1,2][1, 2][3,4][3, 4] 进行翻转。

在第二组测试用例中,T=[1,2,3,1]T = [1, 2, 3, 1],以下 77 种数组 SS 可以被转换为 TT

  • 对于 S=[1,1,3,2]S = [1, 1, 3, 2],可以选择区间 [1,1][1, 1][2,4][2, 4] 进行翻转。

  • 对于 S=[1,2,1,3]S = [1, 2, 1, 3],可以选择区间 [1,1][1, 1][2,2][2, 2][3,4][3, 4] 进行翻转。

  • 对于 S=[1,2,3,1]S = [1, 2, 3, 1],可以选择区间 [1,1][1, 1][2,2][2, 2][3,3][3, 3][4,4][4, 4] 进行翻转。

  • 对于 S=[1,3,2,1]S = [1, 3, 2, 1],可以选择区间 [1,4][1, 4] 进行翻转。

  • 对于 S=[2,1,1,3]S = [2, 1, 1, 3],可以选择区间 [1,2][1, 2][3,4][3, 4] 进行翻转。

  • 对于 S=[2,1,3,1]S = [2, 1, 3, 1],可以选择区间 [1,2][1, 2][3,3][3, 3][4,4][4, 4] 进行翻转。

  • 对于 S=[3,2,1,1]S = [3, 2, 1, 1],可以选择区间 [1,3][1, 3][4,4][4, 4] 进行翻转。

思路讲解

分析 rev 操作的一个重要公式是

rev(XY)=rev(Y)rev(X)\text{rev}(XY) = \text{rev}(Y)\text{rev}(X)

可以继续拓展:

image

image

所以,我们要求转移时前缀相同,只采用这一后缀,即可得到独特的计数

具体来讲,我们对这一后缀的要求是(在前缀相同的情况下)这一后缀无论如何划分,都达不到整体的效果。

1
rev(S) != rev(任意对S的划分方法)

比如说 [1, 2, 3, 4] 任何其他划分方式都达不到 rev([1, 2, 3, 4]) 的效果。

image

image

image

image

不过上面,opus4.6 给出的解释实际上不是很令人满意,因为带有非常明显的知道答案,凑出证明的痕迹。我们采用一种更加好的方法?(可能没有那么严谨,但是实际上是严谨的)

image

P4391 [BalticOI 2009] Radio Transmission 无线传输

我们不难发现,可以这种所谓的查找目标 C=CC=C',其实就是查找周期,其实和查找周期的哈希实现一模一样(详见上面一道题目)。

image

相信看了我的这个解释,应该都能看出来这个东西。

其实,形如 rev(S)=rev(ABA)rev(S)=rev(ABA),其实是唯一一种形式,可以保证分段反转等于整体反转。

不过快速检查一个字符串是否有真前后缀相同,显然是使用 kmp(前缀数组,前缀数组就是这么定义的,能不好吗?) 还是比较好的,使用字符串哈希时间复杂度不是很优秀。

P3375 【模板】KMP(前缀函数)

好,现在问题就来到了,我们怎么样求 s[l;r]s[l;r] 是否有这个真前后缀匹配情况?

image

哈哈,其实解决这个问题的方法也很简单,那么就是跑 n 遍 kmp 就可以了,这道题目不是 O(n2)O(n^2) 的嘛。绷不住了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
vector<ll> kmp(ll N,const vector<ll> &A){	// 返回总前缀数组pi
vector<ll> pi(N+2);
for(int i=2;i<=N;++i){
ll len=pi[i-1];
// if(S[i]==S[len+1]){
// pi[i]=len+1;
// }
while(true){
if(A[i]==A[len+1]){
pi[i]=len+1;
break;
}
if(len==0) break;
len=pi[len];
}
}
return pi;
}

最后的这个 dp 代码:

1
2
3
4
5
6
7
8
9
10
11
for (int l=1;l<=N;++l) {
vector<ll> pi=kmp(N-l+1,vector<ll>(A.begin()+l-1,A.end()));
ll offset=l-1;
for (int r=l;r<=N;++r) {
if (pi[r-offset]>0) {
continue;
}
dp[r]+=dp[l-1];
dp[r]%=mod;
}
}

AC代码

AC
https://codeforces.com/contest/2205/submission/365177121

这个代码跑的好快啊,竟然

心路历程(WA,TLE,MLE……)

要好好想清楚这个 dp 是从哪里转移过来的,是从 l-1 转移过来的

1
2
3
4
5
6
7
8
9
10
11
for (int l=1;l<=N;++l) {
vector<ll> pi=kmp(N-l+1,vector<ll>(A.begin()+l-1,A.end()));
ll offset=l-1;
for (int r=l;r<=N;++r) {
if (pi[r-offset]>0) {
continue;
}
dp[r]+=dp[l-1];
dp[r]%=mod;
}
}

下面是废掉的 AI 提示


P4391 [BalticOI 2009] Radio Transmission 无线传输

计算这个串的周期所使用的这个哈希做法。

首先呢,计数问题,还是要选择 dp 解决。

不过总体上来说,我们对 border 的概念不是很熟。

确实,这道题目的难点就在于,对于不同的划分,如果有相同的这个答案怎么办?这个时候就需要引入字符串算法。

众所周知,计数题目,必须要找到一种方法,一种特征,保证符合这一特征的划分方式/方案之间是独立,互不相同的。这道题目的特征如下:(注意,我们认为单一字符符合这个要求,毕竟递归需要有一终止条件嘛)

image

只要有前后缀相同情况,那么一定会有更加细粒度的切分方法,实现相同效果。

不过,如果只是需要无前后缀相同情况的话,那么直接判断一下前后缀第一个一不一样不就行了?事实上,我们要求,我们所选择的串,其所有子串(长度 ≥ 2)的前后缀都必须不同。

题目大意

题目描述

nn 篇博客,第 ii 篇博客按顺序提到了 lil_i 个用户,用数组 ai=[ai,1,ai,2,,ai,li]a_i=[a_{i,1},a_{i,2},\dots,a_{i,l_i}] 表示。

你需要将这 nn 篇博客全部发布。维护一个序列 QQ(初始为空)来记录最近提到的用户列表。你需要选择一个所有 nn 篇博客的发布顺序进行发布,每次发布一篇博客 ii 时,会对每一个 1jli1 \le j \le l_i 依次执行以下操作:

  • 如果 ai,ja_{i,j} 已经存在于 QQ 中,则将 ai,ja_{i,j} 移动到 QQ 的开头。

  • 否则,将 ai,ja_{i,j} 插入到 QQ 的开头。

求在发布所有 nn 篇博客后,所能得到的字典序最小的序列 QQ

输入格式

第一行包含一个整数 tt1t10001 \le t \le 1000),表示测试用例的数量。
每个测试用例第一行包含一个整数 nn1n30001 \le n \le 3000),表示博客数量。
接下来 nn 行,每行首先包含一个整数 lil_i1li30001 \le l_i \le 3000),表示第 ii 篇博客提到的用户数量,随后跟着 lil_i 个整数 ai,1,ai,2,,ai,lia_{i,1},a_{i,2},\dots,a_{i,l_i}1ai,j1061 \le a_{i,j} \le 10^6),表示提到的用户列表。
保证所有测试用例中 nn 的总和不超过 30003000li\sum l_i 的总和不超过 30003000

输出格式

对于每个测试用例,输出一行包含若干个整数,表示能够得到的字典序最小的 QQ

样例数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Input
5
3
5 1 2 3 4 6
3 2 5 1
4 1 9 2 3
2
2 1 6
1 6
1
3 6 1 1
5
4 2 3 3 4
5 1 2 4 3 1
2 4 1
3 3 3 1
5 4 3 2 2 2
5
4 2 3 1 4
5 2 5 5 6 5
5 3 4 7 5 5
8 3 6 4 3 1 1 5 4
2 1 1

Output
1 5 2 3 9 6 4
6 1
1 6
1 3 2 4
1 4 3 2 5 6 7

样例解释

在第一个测试用例中,可以按如下顺序发布博客:

  1. 发布第一篇博客,QQ 变为 [6,4,3,2,1][6,4,3,2,1]

  2. 发布第三篇博客,QQ 变为 [3,2,9,1,6,4][3,2,9,1,6,4]

  3. 发布第二篇博客,QQ 变为 [1,5,2,3,9,6,4][1,5,2,3,9,6,4]

这里存在其他的发布方式,例如:

  1. 发布第三篇博客,QQ 变为 [3,2,9,1][3,2,9,1]

  2. 发布第一篇博客,QQ 变为 [6,4,3,2,1,9][6,4,3,2,1,9]

  3. 发布第二篇博客,QQ 变为 [1,5,2,6,4,3,9][1,5,2,6,4,3,9]

可以看到 [1,5,2,3,9,6,4][1,5,2,3,9,6,4] 的字典序比其他结果更小。如果我们不把第二篇博客放在最后发布,序列的第一个元素将不会是 11。因此 [1,5,2,3,9,6,4][1,5,2,3,9,6,4] 就是能得到的字典序最小的数组 QQ

在第二个测试用例中,可以按如下顺序发布博客:

  1. 发布第一篇博客,QQ 变为 [6,1][6,1]

  2. 发布第二篇博客,QQ 保持自身为 [6,1][6,1]

在第三个测试用例中,只能发布唯一的一篇博客,QQ 会变为 [1,6][1,6]

思路讲解

哎,其实这个思路和我一模一样,这个思路其实和我一模一样,我说白了你不会更加有更加简单的做法,实际上你就是这个做法,绷不住了。

也看了各路大神的代码,确实是没有更加简单的做法了。

1
2
3
4
5
6
7
8
9
10
11
12
while (!g.empty()) {
sort(all(g));
for (auto a:g[0]) {
ans.push_back(a);
used.insert(a);
}
vector<vector<ll>> new_g;
for (int i=1;i<g.size();++i) {
new_g.push_back(gen_uni(g[i]));
}
swap(new_g,g);
}

AC代码

AC
http://codeforces.com/contest/2205/submission/365031780

心路历程(WA,TLE,MLE……)

题目大意

题目描述

定义一个长度为 mm 的数组 bb 是“好的”,当且仅当不存在任何索引 ii1<i<m1 < i < m)满足 bi=max({bi1,bi,bi+1})b_i = \max(\{b_{i-1}, b_i, b_{i+1}\})

给定一个长度为 nn 的排列 aa。如果数组不是“好的”,你可以反复执行以下操作:
选择一个满足 ai=max({ai1,ai,ai+1})a_i = \max(\{a_{i-1}, a_i, a_{i+1}\}) 的索引 ii1<i<n1 < i < n)。然后,你可以从数组中删除 ai1a_{i-1} ai+1a_{i+1}。删除后,数组的左右两部分会重新拼接在一起。

求出使初始排列 aa 变为“好的”所需的最小操作次数。

输入格式

第一行包含一个整数 tt1t51041 \le t \le 5 \cdot 10^4),表示测试用例的数量。

对于每个测试用例:
第一行包含一个整数 nn3n51053 \le n \le 5 \cdot 10^5),表示排列的长度。
第二行包含 nn 个互不相同的整数 a1,a2,,ana_1, a_2, \ldots, a_n1ain1 \le a_i \le n),表示初始的排列。

保证所有测试用例中 nn 的总和不超过 51055 \cdot 10^5

输出格式

对于每个测试用例,输出一个整数,表示使数组变为“好的”所需的最小操作次数。

样例

输入

1
2
3
4
5
6
7
8
9
10
11
5
3
1 2 3
5
4 1 3 2 5
6
4 5 3 6 2 1
7
6 5 1 7 4 2 3
15
7 4 10 12 9 14 5 3 8 11 1 15 2 13 6

输出

1
2
3
4
5
0
1
3
3
9

样例解释

在第一组测试用例中,数组初始时已经是“好的”,因此不能执行任何操作,答案为 00

在第二组测试用例中,可以进行如下操作:
选择索引 33,此时满足 a3=max({a2,a3,a4})a_3 = \max(\{a_2, a_3, a_4\})(即 3=max({1,3,2})3 = \max(\{1, 3, 2\}))。接着从数组中移除 a2a_2(元素 11)。数组变为 [4,3,2,5][4, 3, 2, 5]
此时数组已经是“好的”,因此答案为 11

在第三组测试用例中,可以进行如下操作:
选择索引 22。从数组中移除 a1a_1。数组变为 [5,3,6,2,1][5, 3, 6, 2, 1]
选择索引 33。从数组中移除 a2a_2。数组变为 [5,6,2,1][5, 6, 2, 1]
选择索引 22。从数组中移除 a1a_1。数组变为 [6,2,1][6, 2, 1]
通过上述 33 次操作,数组变为“好的”。

image

思路讲解

官方解法是这个笛卡尔树,不过我不太会这个东西,我们来看看其他做法吧。

https://www.luogu.com.cn/article/h4sddubz

这个 luogu 题解抓住了最大值一定不会被删除这个特性。(并不是删除最大值不优秀,而是重新阅读题面,你会发现)

我们最终采用的呢,是递归式求解做法啊,也就是分治做法,配合使用的那个ST表。

这个是利用了最大值一定不会被删除的这个特性,而且还利用了一个特性,就是只要这个最大值它不在L或者不在R,一定要把L旁边的或者R旁边的全部给删掉

1
2
3
4
5
6
7
8
9
10
auto solve=[&](auto && self,ll l,ll r) -> ll {
if (l>=r) {
return 0;
}
ll mx_i=st.query(l,r);
ll tmp1=self(self,l,mx_i-1);
ll tmp2=self(self,mx_i+1,r);
ll res=min(tmp1 + r-mx_i ,tmp2 + mx_i-l);
return res;
};

AC代码

AC
https://codeforces.com/contest/2205/submission/365029208

心路历程(WA,TLE,MLE……)

C++ 14下,因为必须传入模板参数,嗯,可以这样子写其模板类型。

1
ST<ll,decltype(max_i)> st(N,vec_idx,max_i);

嗯,我们可以看到,嗯,这里应该是减L,不应该是减1。

1
2
3
4
5
6
7
8
9
10
auto solve=[&](auto && self,ll l,ll r) -> ll {
if (l>=r) {
return 0;
}
ll mx_i=st.query(l,r);
ll tmp1=self(self,l,mx_i-1);
ll tmp2=self(self,mx_i+1,r);
ll res=min(tmp1 + r-mx_i ,tmp2 + mx_i-l);
return res;
};

题目大意

题目描述
给定一个整数 nn,要求找到最小的正整数 kk,使得 nn 能够整除 knk^n(即 nnknk^n 的因数)。
数据保证在给定范围内始终存在答案。

输入格式
第一行包含一个整数 tt1t1001 \le t \le 100),表示测试用例的数量。
接下来的 tt 行,每行包含一个整数 nn2n1092 \le n \le 10^9),表示题目给定的数值。

输出格式
对于每个测试用例,输出一行一个整数,表示满足条件的最小正整数 kk

样例输入

1
2
3
4
5
4
8
12
369
55635800

样例输出

1
2
3
4
2
6
123
2090

样例解释
在第一个测试用例中(n=8n = 8):
k=1k = 1 时,18=11^8 = 188 不是 11 的因数;
k=2k = 2 时,28=2562^8 = 25688256256 的因数(因为 256=8×32256 = 8 \times 32)。
因此,最小可能的 kk22

在第二个测试用例中(n=12n = 12):
最小的 kk66,因为 1212612=21767823366^{12} = 2176782336 的因数(因为 2176782336=12×1813985282176782336 = 12 \times 181398528)。

思路讲解

不难发现,答案就是 N 的所有素因子的乘积。

不过对于我来说,一个难点是怎么样求,就是N的所有素因子的乘积是多少。(在这道题目的数据范围限制下)

我们这里就用到了一个小trick,就是一个数啊,大于根号N的素因子,有的话只有一个。因此我们就不断的把小的素因子全部都给除掉,那么剩下的一个素因子就是最大的了。(而且这个大于根号N的素因子显然不可能被乘很多遍)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ll opN=N;
for (auto p1:primes) {
if (p1*p1>N) {
break;
}
if (opN%p1==0) {
res*=p1;
while (opN%p1==0) {
opN/=p1;
}
}
}
res*=opN;
cout<<res<<"\n";

AC代码

AC

https://codeforces.com/contest/2205/submission/364837938

心路历程(WA,TLE,MLE……)

题目大意

题目大意

Alice 和 Bob 在玩一个游戏。给定两个数组:包含 nn 个元素的数组 aa 和包含 mm 个元素的数组 bb

两人轮流进行操作,Alice 先手。在每个回合中,玩家需要从数组 aa 中选择一个数字 xx,并从数组 bb 中选择一个数字 yy

  • Alice 的规则:选择的 yy 必须能被 xx 整除。

  • Bob 的规则:选择的 yy 必须不能被 xx 整除。

选定 xxyy 后,yy 会从数组 bb 中被移除(如果数组中存在多个相同的 yy,只移除其中一个),而 xx 仍然保留在数组 aa 中。无法进行合法操作(即无法选出满足自身规则的 xxyy)的玩家将输掉游戏。

假设双方都采取最优策略,请判断谁会赢得游戏。

输入格式

第一行包含一个整数 tt1t1041 \le t \le 10^4),表示测试用例的数量。

对于每个测试用例:
第一行包含两个整数 nnmm1n,m1061 \le n, m \le 10^6)。
第二行包含 nn 个整数 aia_i1ain+m1 \le a_i \le n+m),表示数组 aa 的元素。
第三行包含 mm 个整数 bib_i1bin+m1 \le b_i \le n+m),表示数组 bb 的元素。

保证所有测试用例中 nn 的总和以及 mm 的总和均不超过 10610^6

输出格式

对于每个测试用例,输出获胜者的名字:如果 Alice 赢,输出 Alice;如果 Bob 赢,输出 Bob

样例输入

1
2
3
4
5
6
7
8
9
10
3
9 3
3 2 4 2 2 4 4 2 4
6 7 12
10 3
3 2 5 4 2 5 3 4 4 4
10 7 13
1 5
1
1 2 3 4 5

样例输出

1
2
3
Alice
Bob
Alice

样例解释

对于第一个测试用例,Alice 的获胜过程如下:

  1. Alice 的回合:选择 x=3,y=6x=3, y=666 能被 33 整除)。操作后 66 从数组 bb 中移除,此时 b=[7,12]b=[7, 12]

  2. Bob 的回合:选择 x=3,y=7x=3, y=777 不能被 33 整除)。Bob 在这个回合只能选择 y=7y=7,因为对于 y=12y=12,数组 aa 中找不到任何不能整除它的 xx。操作后 77 从数组 bb 中移除,此时 b=[12]b=[12]

  3. Alice 的回合:选择 x=4,y=12x=4, y=121212 能被 44 整除)。操作后 1212 从数组 bb 中移除,此时数组 bb 变为空。

  4. Bob 的回合:数组 bb 已经为空,Bob 无法进行任何操作,因此 Bob 输掉游戏,Alice 获胜。

思路讲解

OK,我们说回来。这个D为什么交了5发还是没过呢?主要是因为就是这个D,它的数据量比较大。使用Victor victor或者victor的这种调和级数写法呢,它这个时间会超。可以使用vis 数组标记所有 a 的倍数。这样子可以避免卡常。

1
2
3
4
5
6
7
8
9
10
ll cnt_un_divide=0,cnt_all_divide=0;
vector<char> vis(N+M+2);
for (auto a:A) {
for (ll i=1;i<=N+M;++i) {
if (i*a>N+M) {
break;
}
vis[i*a]=true;
}
}

然后为什么标题里面提了不要用 while 循环?是因为涉及累加的逻辑(在 D 的对拍暴力程序当中),直接 continue 以后,while 循环的这个累加就崩掉了。

AC代码

AC
https://codeforces.com/contest/2203/submission/364454955

心路历程(WA,TLE,MLE……)

image

后面还被系统测试阴了一手,把这个LCM改成128,用INT128就可以了,这个叫什么,当时其实我就知道这里应该是会有点问题的,想用这个128嘛,但是可能还是犹豫还是犹豫了。

1
2
3
4
i128 lcm128(i128 a,i128 b) {
i128 res=a*b/__gcd(a,b);
return res;
}