D. Divide and Equalize
题意
给定一个长度为 n n n 的正整数数组 a a a,可以执行一下的操作任意次:
- 选择一对下标 i 、 j i、j i、j ,且 i ≠ j i \neq j i=j
- 再选择一个 a i a_i ai 的因子 x x x
- 将 a i a_i ai 替换为 a i x \frac{a_i}{x} xai, a j a_j aj 替换为 a j ⋅ x a_j \cdot x aj⋅x
判断是否可能通过上述操作将数组的所有元素都变成相等
思路
可以先将每一个 a i a_i ai 质因数分解,可以发现:每次操作都是减少 a i a_i ai 的一个因子,然后给另一个 a j a_j aj 添加这个因子。所以这 n n n 个数的质因子的集合是不变的。那么我们只要统计所有质因子,看看这些质因子能否平均分配即可。
质因子分解可以使用 欧拉筛 来预处理每个数的最小质因子,可以优化到 l o g ( n ) log(n) log(n)
时间复杂度: O ( n l o g n ) O(nlog \hspace{2pt}n) O(nlogn)
// Problem: D. Divide and Equalize
// Contest: Codeforces - Codeforces Round 903 (Div. 3)
// URL: https://codeforces.com/contest/1881/problem/D
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
#define fore(i,l,r) for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
const int INF=0x3f3f3f3f;
const long long INFLL=0x3f3f3f3f3f3f3f3fLL;
typedef long long ll;
const int N=1000050;
int minp[N];
std::vector<int> primes;
void euler(){
minp[1] = 1;
fore(i,2,N){
if(!minp[i]){
minp[i] = i;
primes.push_back(i);
}
for(int j : primes){
if(j*i >= N) break;
minp[j*i] = j;
if(i%j == 0) break;
}
}
}
bool solve(){
int n;
std::cin>>n;
std::map<int,int> mp;
fore(i,1,n+1){
int x;
std::cin>>x;
while(x > 1){
++mp[minp[x]];
x /= minp[x];
}
}
for(auto [x,cnt] : mp)
if(cnt % n)
return false;
return true;
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
euler();
int t;
std::cin>>t;
while(t--){
std::cout<<(solve()?"YES":"NO")<<endl;
}
return 0;
}
E. Block Sequence
题意
给定一个长度为 n n n 的正整数数组 a a a,定义一个序列是 b e a u t i f u l beautiful beautiful 的当且仅当:
- 第一个数字大小是序列后面数字的长度,例如 [ 3 , 3 , 4 , 5 ] [3,3,4,5] [3,3,4,5],后面的数字可以任意
在一次操作中,可以删除 a a a 中的一个数字,问最少删除几个数字,可以使得 a a a 是被几个互不交叉的 b e a u t i f u l beautiful beautiful 序列组成的
思路
转换一下问题,要使删除的元素最少 ⇔ \Leftrightarrow ⇔ 使数组中留下的元素最多。那么我们可以在原数组中找能够成为 b e a u t i f u l beautiful beautiful 的若干个序列,这些序列的长度之和最大。对于一个新的元素 a i a_i ai,如果它要作为一个序列的开头,那么它后面一定有 a i a_i ai 个元素,所以就是前面的元素的子状态的最优,加上 a i a_i ai 开头的序列。考虑线性 D P DP DP:
定义 d p [ i ] dp[i] dp[i] 为 [ 1 , i ] [1,i] [1,i] 里面可以留下的最多元素数量,那么对于 a i a_i ai,转移方程就是前面的区间: [ 1 , i − 1 ] [1,i-1] [1,i−1] 区间的最大长度 + + + a i a_i ai 开头的序列,也即是 d p [ i − 1 + a i + 1 ] = d p [ i − 1 ] + a i + 1 dp[i - 1 + a_i + 1] = dp[i-1] + a_i + 1 dp[i−1+ai+1]=dp[i−1]+ai+1。在遍历下标 i i i 的时候,要注意时刻保持更新 d p [ i ] = m a x ( d p [ i ] , d p [ i − 1 ] ) dp[i] = max(dp[i],dp[i-1]) dp[i]=max(dp[i],dp[i−1]) 来维护前 i i i 个元素构成的最大长度。
答案就是 n − d p [ n ] n - dp[n] n−dp[n]
// Problem: E. Block Sequence
// Contest: Codeforces - Codeforces Round 903 (Div. 3)
// URL: https://codeforces.com/contest/1881/problem/E
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
#define fore(i,l,r) for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
const int INF=0x3f3f3f3f;
const long long INFLL=0x3f3f3f3f3f3f3f3fLL;
typedef long long ll;
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int t;
std::cin>>t;
while(t--){
int n;
std::cin>>n;
std::vector<int> a(n+1);
fore(i,1,n+1) std::cin>>a[i];
std::vector<int> dp(n+5,0);
fore(i,1,n+1){
dp[i] = std::max(dp[i],dp[i-1]); //从前面取max
if(i + a[i] <= n) //确保后面元素足够
dp[i+a[i]] = std::max(dp[i+a[i]],dp[i-1] + a[i] + 1);
}
std::cout<<n-dp[n]<<endl;
}
return 0;
}
F. Minimum Maximum Distance
题意
给定一个
n
n
n 个节点的树,树上有一些点被标记了。定义
f
i
f_i
fi 为:节点
i
i
i 到所有标记点的最大距离
请输出
m
i
n
(
f
i
)
i
∈
[
1
,
n
]
min(f_i) \quad i \in [1,n]
min(fi)i∈[1,n]
思路
很显然当
k
=
1
k=1
k=1 时答案是
0
0
0 ,因为答案就是被标记的那个节点到自己的距离。
对于
k
≥
2
k \geq 2
k≥2 的情况:我们可以使用类似树的直径的做法,两次
d
f
s
dfs
dfs 找到相互距离最远的两个标记点,那么答案就是这两个标记点之间的距离除
2
2
2 上取整。
证明:
先来证明:
∀
i
∈
[
1
,
n
]
\forall i \in [1,n]
∀i∈[1,n],距离节点
i
i
i 最远的标记点一定是这两个点
s
、
t
s、t
s、t 中的一个。
由于这是一颗树,我们可以将其想象成一根多叉的绳子,握住
s
、
t
s、t
s、t 两端,其他枝条会垂直落下:
那么对于图中的
1
5
1~5
1 5 号点,距离他们最远的标记点一定是
s
、
t
s、t
s、t 中的一个。假设:距离节点
x
x
x 的最远标记点是
s
s
s,也就是
d
i
s
(
x
,
s
)
dis(x,s)
dis(x,s) 最大(在所有标记点中)
(
∗
)
(*)
(∗)
如果这时候有一个其他的标记点
w
w
w,
w
≠
s
且
w
≠
t
w \neq s 且 w \neq t
w=s且w=t,使得:
d
i
s
(
x
,
w
)
>
d
i
s
(
x
,
s
)
dis(x,w) > dis(x,s)
dis(x,w)>dis(x,s),那么
x
→
w
x \rightarrow w
x→w 的路上一定要先上来
s
−
t
s-t
s−t 这条直线的一个点
y
y
y,然后再从某个位置下去。
由于:
{
d
i
s
(
x
,
w
)
=
d
i
s
(
x
,
y
)
+
d
i
s
(
y
,
w
)
d
i
s
(
x
,
s
)
=
d
i
s
(
x
,
y
)
+
d
i
s
(
y
,
s
)
\begin{cases} dis(x,w) = dis(x,y) + dis(y,w) \\\\ dis(x,s) = dis(x,y) + dis(y,s) \end{cases}
⎩
⎨
⎧dis(x,w)=dis(x,y)+dis(y,w)dis(x,s)=dis(x,y)+dis(y,s)
而
d
i
s
(
x
,
w
)
>
d
i
s
(
x
,
s
)
dis(x,w) > dis(x,s)
dis(x,w)>dis(x,s),所以:
d
i
s
(
y
,
w
)
>
d
i
s
(
y
,
s
)
dis(y,w) > dis(y,s)
dis(y,w)>dis(y,s) (
⌣
\smile
⌣),那么可以推出:
{
d
i
s
(
t
,
w
)
=
d
i
s
(
t
,
y
)
+
d
i
s
(
y
,
w
)
d
i
s
(
t
,
s
)
=
d
i
s
(
t
,
y
)
+
d
i
s
(
y
,
s
)
\begin{cases} dis(t,w) = dis(t,y) + dis(y,w) \\\\ dis(t,s) = dis(t,y) + dis(y,s) \end{cases}
⎩
⎨
⎧dis(t,w)=dis(t,y)+dis(y,w)dis(t,s)=dis(t,y)+dis(y,s)
由
(
⌣
)
(\smile)
(⌣) 式可以推出:
d
i
s
(
t
,
w
)
>
d
i
s
(
t
,
s
)
dis(t,w) > dis(t,s)
dis(t,w)>dis(t,s),所以
w
w
w 会替代
s
s
s 成为新的直径端点,与
(
∗
)
(*)
(∗) 式矛盾。
其实上面的证明里面,路径不一定是简单的相加关系,但是可以使用矢量和来进行更加严谨的表述,读者可以自行推导,大意是像上面这样。
那么剩下的答案是这两个端点之间距离 d d d 的 ⌈ d 2 ⌉ \lceil \dfrac{d}{2} \rceil ⌈2d⌉ 就很好说明了,如果不在两端点之间的话, f i f_i fi 一定会取到更远的那个端点,肯定是没有两端点路径中间位置的那个节点的 f f f 那么小。
// Problem: F. Minimum Maximum Distance
// Contest: Codeforces - Codeforces Round 903 (Div. 3)
// URL: https://codeforces.com/contest/1881/problem/F
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
#define fore(i,l,r) for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
const int INF=0x3f3f3f3f;
const long long INFLL=0x3f3f3f3f3f3f3f3fLL;
typedef long long ll;
const int N=200005;
std::vector<int> edge[N];
void dfs(int u,int fa,std::vector<int>& d){
for(auto v : edge[u])
if(v != fa){
d[v] = d[u] + 1;
dfs(v,u,d);
}
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int t;
std::cin>>t;
while(t--){
int n,k;
std::cin>>n>>k;
fore(i,1,n+1) edge[i].clear();
std::vector<int> marked;
fore(i,1,k+1){
int x;
std::cin>>x;
marked.push_back(x);
}
fore(i,1,n){
int u,v;
std::cin>>u>>v;
edge[u].push_back(v);
edge[v].push_back(u);
}
if(k == 1){
std::cout<<0<<endl;
continue;
}
std::vector<int> d(n+1,0);
dfs(marked[0],0,d);
int s = marked[0];
for(auto v : marked)
if(d[v] > d[s])
s = v;
d.assign(n+1,0);
dfs(s,0,d);
for(auto v : marked)
if(d[v] > d[s])
s = v;
std::cout<<(d[s]+1)/2<<endl;
}
return 0;
}
G. Anya and the Mysterious String
题意
给定一个长度为 n n n 的字符串 s s s, q q q 次操作,每次操作有两种类型:
- 1 l r x 1 \hspace{2pt} l \hspace{2pt} r \hspace{2pt} x 1lrx:将区间 [ l , r ] [l,r] [l,r] 的字母增加 x x x
- 2 l r 2 \hspace{2pt} l \hspace{2pt} r 2lr:回答区间 [ l , r ] [l,r] [l,r] 是否包含回文串
思路
如果没有修改操作的话,我们可以将所有的回文串分成两种情况:
- 长度为偶数,那么它最中间的两个元素一定是相等的。
- 长度为奇数,那么它最中间的三个元素的两端的两个字母一定相等
那么所有的回文串都可以表示成: s i s i + 1 s_i s_{i+1} sisi+1 或 s i s i + 1 s i + 2 s_i s_{i+1} s_{i+2} sisi+1si+2。我们不妨分别维护两个 s e t set set, M 2 M2 M2 存所有长度为 2 2 2 的回文串的第一个下标 i i i , M 3 M3 M3 则存长度为 3 3 3 的回文串的第一个下标。
对于一个询问 l , r l,r l,r,我们只需要找到 M 2 M2 M2 或 M 3 M3 M3 中 大于或等于 l l l 的第一个元素 i i i ,如果 i + 1 ≤ r i + 1 \leq r i+1≤r 或 i + 2 ≤ r i+2 \leq r i+2≤r,那么说明区间 [ l , r ] [l,r] [l,r] 中有完整的长度为 2 2 2 或 3 3 3 的回文串。如果找不到,就是 Y E S YES YES。
对于修改操作,其实就是一个区间修改,发现修改一个区间 [ l , r ] [l,r] [l,r] 的话,原来包含在区间里面的那些完整的回文串并不会改变其回文的性质,因为所有字母的增幅都是一样的。唯一改变的只有区间两端那些横跨区间端点的回文串,可以发现最多只影响到长度为 3 3 3 的回文串,也就是 [ l − 2 , l + 2 ] [l-2,l+2] [l−2,l+2] 和 [ r − 2 , r + 2 ] [r-2,r+2] [r−2,r+2] 这两段,长度只有 8 8 8,分别维护每一个点即可,利用 r e l a x relax relax 操作维护 M 2 M2 M2 和 M 3 M3 M3 里的元素 ,复杂度是可以通过的。区间修改的操作利用 树状数组 的差分来表示 或者使用 线段树懒标记 都可以实现
// Problem: G. Anya and the Mysterious String
// Contest: Codeforces - Codeforces Round 903 (Div. 3)
// URL: https://codeforces.com/contest/1881/problem/G
// Memory Limit: 256 MB
// Time Limit: 3000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
#define fore(i,l,r) for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define lowbit(x) ((x) & -(x))
const int INF=0x3f3f3f3f;
const long long INFLL=0x3f3f3f3f3f3f3f3fLL;
typedef long long ll;
const int N=200050;
const int L=26; //mod of length
int n;
std::string s;
int fen[N]; //FenWick Tree
std::set<int> M2;
std::set<int> M3;
void fenadd(int p,int d){
d = (d%L + L)%L; //mod L 最小正整数
while(p <= n){
fen[p] = (fen[p] + d)%L;
p += lowbit(p);
}
}
int fenget(int p){
int res = 0;
while(p >= 1){
res = (res + fen[p])%L;
p -= lowbit(p);
}
return res;
}
void init(){
M2.clear();
M3.clear();
fore(i,0,n+1) fen[i] = 0;
fenadd(1,s[1] - 'a');
fore(i,2,n+1) fenadd(i,s[i]-s[i-1]); //差分数组
fore(i,1,n){
if(s[i] == s[i+1]) M2.insert(i);
if(i+2 <= n && s[i] == s[i+2]) M3.insert(i);
}
}
void relax(int l,int r){
l = std::max(l,1);
r = std::min(n,r);
fore(i,l,r){
int c1 = fenget(i);
int c2 = fenget(i+1);
if(c1 == c2) M2.insert(i);
else M2.erase(i);
if(i+2 > r) continue;
int c3 = fenget(i+2);
if(c1 == c3) M3.insert(i);
else M3.erase(i);
}
}
void update(int l,int r,int x){
fenadd(l,x); //差分区间修改
fenadd(r+1,-x); //差分区间修改
relax(l-2,l+2); //区间两端的回文串
relax(r-2,r+2);
}
bool query(int l,int r){
auto it = M2.lower_bound(l);
if(it != M2.end() && *it + 1 <= r) return false;
it = M3.lower_bound(l);
if(it != M3.end() && *it + 2 <= r) return false;
return true;
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int t;
std::cin>>t;
while(t--){
int q;
std::cin>>n>>q;
std::cin>>s;
s = '0' + s; //let the index strat from '1'
init();
while(q--){
int opt,l,r,x;
std::cin>>opt>>l>>r;
if(opt == 1){
std::cin>>x;
update(l,r,x);
}
else{
std::cout<<(query(l,r)?"YES":"NO")<<endl;
}
}
}
return 0;
}