Codeforces Round 903 div3 D-G

D. Divide and Equalize

D

题意

给定一个长度为 n n n 的正整数数组 a a a,可以执行一下的操作任意次:

  • 选择一对下标 i 、 j i、j ij ,且 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 ajx

判断是否可能通过上述操作将数组的所有元素都变成相等

思路

可以先将每一个 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

E

题意

给定一个长度为 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,i1] 区间的最大长度 + + + 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[i1+ai+1]=dp[i1]+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[i1]) 来维护 i i i 个元素构成的最大长度。

答案就是 n − d p [ n ] n - dp[n] ndp[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

F

题意

给定一个 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 k2 的情况:我们可以使用类似树的直径的做法,两次 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 st 中的一个。
由于这是一颗树,我们可以将其想象成一根多叉的绳子,握住 s 、 t s、t st 两端,其他枝条会垂直落下:

1

那么对于图中的 1   5 1~5 1 5 号点,距离他们最远的标记点一定是 s 、 t s、t st 中的一个。假设:距离节点 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=sw=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 xw 的路上一定要先上来 s − t s-t st 这条直线的一个点 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

G

题意

给定一个长度为 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+1r i + 2 ≤ r i+2 \leq r i+2r,那么说明区间 [ 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] [l2,l+2] [ r − 2 , r + 2 ] [r-2,r+2] [r2,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; 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值