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 分解为三部分:
-
从 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′] 的答案。
-
在 LCA 处:
- 令 r′ 是 LCA 的一个子顶点,且是 r 的祖先。我们需要计算子数组 a[lastl′+1…r′−1] 的答案。
- 这可以通过考虑 LCA 在 l′ 和 r′ 之间的子顶点,然后求和这些子顶点的子树答案来实现。
- 对于单个顶点的子树,它们的值与 LCA 相同。我们可以在预计算前缀和后,在 O(1) 时间内统计顶点 l′ 和 r′ 之间该值的出现次数。
- 对于其他子树,其对应子数组的答案独立于数组的其余部分。因此,我们可以通过简单的 DFS 预计算每个顶点的子树答案。然后,使用前缀和求和 l′ 和 r′ 之间所有子顶点的子树答案。
-
从 LCA 到 r:
- 这部分处理方式与第一部分类似,但需要预计算整个数组的前缀答案,而不是后缀答案。
时间复杂度为 \(O(n + q \log n)\)。
本文来自博客园,作者:ricky_lin,转载请注明原文链接:https://www.cnblogs.com/rickylin/p/19135285/CF2153

浙公网安备 33010602011771号