CF2153题解

A Circle of Apple Trees

即求有多少种大小不同的数

code
#include<bits/stdc++.h>
using namespace std;
const int NN = 108;
int t,n;
int a[NN];
void solve(){
	memset(a,0,sizeof(a));
	cin >> n; 
	for(int i = 1; i <= n; ++i){
		int x;
		cin >> x;
		++a[x];
	}
	int cnt = 0;
	for(int i = 1; i <= n; ++i) if(a[i]) ++cnt;
	cout << cnt << '\n';
}
int main(){
	ios::sync_with_stdio(false);
	cin >> t;
	while(t--){
		solve();
	}
} 

B Bitwise Reversion

对于 \(c\) 而言, \(y | z\)\(1\) 的位必须为 \(1\),其他位任意。

为了避免和其他数\((a,b)\) \(\&\) 之后得到的结果在本不该有 \(1\) 的位置多出 \(1\),我们的最优方案就是 \(c = y|z\)

同理 \(a = x | z\)\(b = x | y\)

最后验证是否符合题目条件即可

code
#include<bits/stdc++.h>
using namespace std;
int t;
int x,y,z;
void solve(){
	cin >> x >> y >> z;
	int asum = x & y & z;
	int a,b,c;
	c = y | z;
	b = x | y;
	a = x | z;
	if((a&b) == x && (b&c) == y && (a&c) == z) puts("YES");
	else puts("NO");
}
int main(){
	cin >> t;
	while(t--){
		solve();
	}
}

C Symmetrical Polygons

可以发现,如果构造出凹多边形,可以把凹进去的那一堆进行翻转得到一个凸多边形

同时,对于选定的边,只要没有任何一条边长度大于其他边,那么就可以构造出一个多边形

怎么找到最大周长?

首先两两相同的边配对加入答案即可,然后对于剩下的边进行排序,排序完之后对于每相邻两个边,看是否加进多边形后能够满足条件,并更新答案

code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll; 
const int NN = 2e5 + 8;
int t,n;
int a[NN];
ll res,ans,cnt;
int maxn,mexn;
void solve(){
	maxn = mexn = 0;res = ans = 0;cnt = 0;
	cin >> n;
	for(int i = 1; i <= n; ++i) cin >> a[i];
	a[n+1] = 0;
	sort(a+1,a+1+n);
	for(int i = 1; i <= n; ++i){
		if(a[i] == a[i+1]){
			res += a[i] * 2ll;
			++i;cnt += 2;
			continue;
		}
	}
	if(cnt >= 3) ans = res;
	int pre = 0;
	for(int i = 1; i <= n; ++i){
		if(a[i] == a[i+1]){
			++i;
			continue;
		}
		if(res > a[i] - pre && cnt != 0) ans = max(ans,res + a[i] + pre);
		pre = a[i];
	}
	cout << ans << '\n';
} 
int main(){
	ios::sync_with_stdio(false),cin.tie(0);
	cin >> t;
	while(t--){
		solve();
	}
}

D Not Alone

tag: DP 性质

首先我们定义最后方案中,一段调整为同一个数的区间称作一组

我们可以发现,最后的方案一定可以让每一组只由 \(2/3\) 个数组成

因为如果说最后一个组中有 \(4/5\) 个数,不难证明分成左右两组得到的答案一定不会更劣

所以根据这个条件,我们就可以进行 \(DP\)

至于环的处理,因为我们一组最多只有 \(2/3\) 个数,所以我们分别求出以 下标 \(1/2/3\) 开始分组得到的答案即可覆盖所有方案

code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll NN = 2e5 + 8, INF = 1e18;
int t;
int n;
ll a[NN];
ll f[NN];
void solve(){
	ll ans = INF;
	cin >> n;
	for(int i = 1; i <= n; ++i){
		cin >> a[i];
		f[i] = INF;
	}
	f[0] = 0;
	for(int i = 1; i <= n; ++i){
		if(i >= 2) f[i] = min(f[i],f[i-2]+max(a[i],a[i-1]) - min(a[i],a[i-1]));
		if(i >= 3) f[i] = min(f[i],f[i-3]+max(a[i],max(a[i-1],a[i-2])) - min(a[i],min(a[i-1],a[i-2])));
	}
	ans = min(ans,f[n]);
	
	for(int i = 1; i <= n; ++i){
		a[i-1] = a[i];
		f[i] = INF;
	}
	f[0] = 0;
	a[n] = a[0]; a[0] = 0;
	for(int i = 1; i <= n; ++i){
		if(i >= 2) f[i] = min(f[i],f[i-2]+max(a[i],a[i-1]) - min(a[i],a[i-1]));
		if(i >= 3) f[i] = min(f[i],f[i-3]+max(a[i],max(a[i-1],a[i-2])) - min(a[i],min(a[i-1],a[i-2])));
	}
	ans = min(ans,f[n]);
	
	for(int i = 1; i <= n; ++i){
		a[i-1] = a[i];
		f[i] = INF;
	}
	f[0] = 0;
	a[n] = a[0]; a[0] = 0;
	for(int i = 1; i <= n; ++i){
		if(i >= 2) f[i] = min(f[i],f[i-2]+max(a[i],a[i-1]) - min(a[i],a[i-1]));
		if(i >= 3) f[i] = min(f[i],f[i-3]+max(a[i],max(a[i-1],a[i-2])) - min(a[i],min(a[i-1],a[i-2])));
	}
	ans = min(ans,f[n]);
	
	cout << ans << '\n';
}
int main(){
	ios::sync_with_stdio(false),cin.tie(0);
	cin >> t;
	while(t--){
		solve();
	}
}

E Zero Trailing Factorial

tag: 数学 性质 暴力

对于 \(v_k(x!)\) 的性质探究,可以知道 当 \(x < y\) 时, \(v_k(x!) \leq v_k(y!)\)


对于 \(f_m(x,n)\) 的性质探究,可以发现:

由于 \(n \leq m\),记 \(p\) 为小于等于 \(n\) 的最大质数,可以知道 \(\forall x < p, f_m(x,n) = 0(w_p(x,n) = 0)\)

所以我们只需要考虑 \(x \geq p\) 的情况


接着,我们对于 \(w_k(x,n)\) 中,\(k\) 的取值进行研究发现,我们只需要考虑质数和质数的幂。

同时对于质数,我们只需要找到能够至少整数 \(p\sim n\)\(1\) 个数的质数即可。


可以知道 \(\leq 10^7\) 的相邻质数间的最大差值为 152

所以复杂度可控(不想分析了)

code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll NN = 1e7 + 8, INF = 1e11;
int t,n,m;

bool isprime[NN];
vector<int> small_prime,large_prime;


void init(){//get prime 
	for(int i = 2; i <= 1e7; ++i){
		isprime[i] = 1;
	}
	for(int i = 2; i * i <= 1e7; ++i){
		if(!isprime[i]) continue;
		small_prime.push_back(i);
		for(int j = i * i; j <= 1e7; j += i)
			isprime[j] = 0;
	}
}
ll Calc(int p,int num){
	ll res = 0;
	for(ll i = p; i <= num; i *= p){
		res += num / i;
	}
	return res;
}
void solve(){
	cin >> n >> m;
	
	int lgt = n;//largest prime lower n 
	while(!isprime[lgt]) --lgt;
	
	large_prime.clear();
	for(int i = 1; i * i <= n; ++i){
		int x = n / i;
		while(!isprime[x]) --x;
		large_prime.push_back(x);
	}
	
	ll ans = 0;
	
	for(int x = lgt; x < n; ++x){
		ll res = INF;
		for(int p : large_prime){
			ll vx = Calc(p,x), vn = Calc(p,n);
			if(vx != vn) res = min(res,vx);
		}
		
		for(int p : small_prime){
			if(p * p > m) break;
			ll _vx = Calc(p,x), _vn = Calc(p,n);
			for(ll pw = p, zc = 1; pw <= m; ++zc, pw *= p){
				ll vx = _vx / zc, vn = _vn / zc;
				if(vx != vn) res = min(res,vx);
			}
		}
		
		ans += res;
	}
	
	cout << ans << '\n';
}
int main(){
	ios::sync_with_stdio(false),cin.tie(0);
	init();
	cin >> t;
	while(t--){
		solve();
	}
} 

F Odd Queries on Odd Array

tag: 图论建模 LCA

solution translated by deepseek

问题给出的奇怪条件是:数组中不存在形如 [x,y,x,y](x≠y)的子序列。

我们构建一棵树,使得其 DFS 序 等于数组 a。构建树的方法如下:

  • 初始化一个栈,其中包含顶点 0。
  • 遍历数组 a,从索引 i=1 到 i=n。
  • 对于每个元素 a[i]:
    • 在栈顶顶点和顶点 i 之间添加一条边。为顶点 i 赋值 a[i]。
    • 如果这是 a[i] 的第一次出现,将 i 压入栈。
    • 如果这是 a[i] 的最后一次出现,从栈中弹出栈顶元素(即使刚刚压入它)。

只有每个值的第一次和最后一次出现会影响栈。可以证明,在每个值的最后一次出现时,被弹出栈的顶点将具有与当前处理顶点相同的值。这是因为,由于可爱数组的性质,值 x 的第一次和最后一次出现之间的所有元素必须严格包含在该范围内。因此,在 x 第一次出现之后压入栈的所有顶点都将在 x 最后一次出现之前被弹出。

由于可爱数组的性质,具有相同值的顶点集合在树中形成一个连通分量。

证明
由于每个值在栈中只能有一个顶点,所有相同值的顶点具有相同的父节点。

为了处理查询,我们通过考虑顶点 l 和顶点 r 的最低公共祖先(LCA)将范围 l 到 r 分解为三部分:

  1. 从 l 到 LCA

    • 如果 l 的值与 LCA 相同,可以将其留到第二部分处理。
    • 否则,令 l′ 是 LCA 的一个子顶点,且是 l 的祖先,并令 lastl′ 是 l′ 子树中索引最大的顶点。l′ 子树中出现的值永远不会在树的其他部分出现。因此,子数组 a[l…lastl′] 的答案等于整个数组后缀 a[l…n] 的答案减去后缀 a[lastl′+1…n] 的答案。通过预计算整个数组每个后缀的答案,我们可以在 O(1) 时间内找到子数组 a[l…lastl′] 的答案。
  2. 在 LCA 处

    • 令 r′ 是 LCA 的一个子顶点,且是 r 的祖先。我们需要计算子数组 a[lastl′+1…r′−1] 的答案。
    • 这可以通过考虑 LCA 在 l′ 和 r′ 之间的子顶点,然后求和这些子顶点的子树答案来实现。
    • 对于单个顶点的子树,它们的值与 LCA 相同。我们可以在预计算前缀和后,在 O(1) 时间内统计顶点 l′ 和 r′ 之间该值的出现次数。
    • 对于其他子树,其对应子数组的答案独立于数组的其余部分。因此,我们可以通过简单的 DFS 预计算每个顶点的子树答案。然后,使用前缀和求和 l′ 和 r′ 之间所有子顶点的子树答案。
  3. 从 LCA 到 r

    • 这部分处理方式与第一部分类似,但需要预计算整个数组的前缀答案,而不是后缀答案。

时间复杂度为 \(O(n + q \log n)\)

posted @ 2025-10-11 16:17  ricky_lin  阅读(35)  评论(0)    收藏  举报