26牛客暑假多校2
点击查看题面
M题 Maybe Connected
题目大意
有
数据范围:
思路
时,无论怎样都是0 - 当所有点连接成一条线的时候,取得最大值 当有
个点一条线,则有 对点 - 当
时 可以将 个点连成一条线,取得最大值 - 在
时取得最大值,当 时,每多一条边,最大值将减1,需要减去的部分为 多余出来边数
复杂度分析
时间复杂度:
空间复杂度:
参考代码
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void fc() {
ll n,m;std::cin>>n>>m;
if(n<=2){
std::cout<<0<<"\n";return;
}
if(m<=n-1){
std::cout<<m*(m-1)/2<<"\n";
}else{
std::cout<<(n-1)*(n-2)/2-(m-(n-1))<<"\n";
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
std::cin>>t;
while (t--) fc();
return 0;
}N题 Lazy Shuffling
题目大意
给定一个长度为n的数组,要求执行恰好一次,每次选取
数据范围:
思路
- 由于排序不会影响答案,先排序
- 在升序条件下,要求操作后增量最大化,应当小于和大于中位数的部分都要最小,即假设
为中位数,前 个较小元素 无额外限制,直接取全局最小的 个,后 个较大元素 ,最优解为紧跟中位数之后连续的 个
1. 为奇数
,中位数 - 增量展开:
贪心构造:
- 较小元素集合 (
):仅需满足 ,无额外下界限制。要使和最小,直接选取全局最小的前 个数,即排序后的前缀 。
- 较小元素集合 (
- 较大元素集合 (
):必须满足 。要使和最小,最优解为在排序数组中紧跟在 后的连续 个元素。
- 较大元素集合 (
结构结论:
- 设
(其中 ),则选取的 个元素分割为:固定前缀: (共 个元素)。
- 设
- 连续区间:
(共 个元素,首元素为 )。
- 连续区间:
2. 为偶数
,中位数 增量展开:
贪心构造:
- 较小元素集合 (
):直接选取全局最小的前 个数,即前缀 。
- 较小元素集合 (
- 较大元素集合 (
):紧跟在中位数对 后的连续 个元素。
- 较大元素集合 (
结构结论: 设
- 固定前缀:
(共 个元素)。连续区间: (共 个元素,包含中位数对及其右侧元素)。
- 固定前缀:
- 连续区间:
(共 个元素,包含中位数对及其右侧元素)
- 连续区间:
3.统一奇偶模型
为了方便计算,只需要统一变量无需大量奇偶讨论,通过滑动窗口求解
固定前缀长度:
连续窗口长度:
滑动范围:窗口首元素
参考代码
参考代码
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
void fc() {
int n,k;std::cin>>n>>k;
std::vector<int>a(n);
bool t=(k&1);
ll sk=k;
ll sum=0;
for(int i=0;i<n;i++)std::cin>>a[i],sum+=a[i];
std::sort(a.begin(),a.end());
int d=(k-1)/2;
ll s=0,ans=LLONG_MIN;
for(int i=0;i<d;i++)s+=a[i];
k=k-d;
for(int i=d;i<n;i++){
s+=a[i];
if(i<d+k-1)continue;
ll mid=(t?a[i-k+1]*sk:sk/2*(a[i-k+1]+a[i-k+2]));
ans=max(ans,sum-s+mid);
s-=a[i-k+1];
}
std::cout<<ans<<"\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
fc();
}
return 0;
}复杂度分析
时间复杂度:排序
空间复杂度:
B题 Bitwise Maximization
题目大意
给定一个长度为
数据范围:
思路
设全局异或和为
由于
- 若
的第 位为 1: 与 在该位必为一 0 一 1,故 的第 位恒为 0。 - 若
的第 位为 0: 与 在该位值相同。若能使 在该位为 1,则 在该位也必然为 1,此时 的第 位为 1。
然后对所有
复杂度分析
时间复杂度:
空间复杂度:
参考代码
参考代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
using namespace std;
using ll = long long;
ll p[30];
void insert(ll x) {
for (int i = 29; i >= 0; --i) {
if (!(x >> i & 1)) continue;
if (!p[i]) {
p[i] = x;
return;
}
x ^= p[i];
}
}
void fc() {
int n;
cin >> n;
vector<ll> a(n);
ll s = 0;
for (int i = 0; i < n; ++i) {
cin >> a[i];
s ^= a[i];
}
memset(p, 0, sizeof(p));
for (int i = 0; i < n; ++i) {
insert(a[i] & (~s));
}
ll max_x = 0;
for (int i = 29; i >= 0; --i) {
max_x = max(max_x, max_x ^ p[i]);
}
cout << max_x * 2 + s << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
fc();
}
return 0;
}G题 GCD Graph
题目大意
给定一个顶点由正整数
对于给定的参数
数据范围:
思路
设不超过
- 对于
的部分:
- 若
,存在直连边 ,代价为 1。 - 若
,考虑取质数 作为中转点。因为 为质数且 ,必有 ;若 ,由于 为质数亦有 。路径 的代价为 。 - 因此在
范围内,与 互质的数代价为 1,不互质的数代价为 2。设该区间长度为 ,其中与 互质的个数为 ,则贡献为:
- 利用线性筛提取
的质因子,结合容斥原理在前缀上计算互质个数 。
- 对于
的部分:
- 由于
的范围极小,可以采用逆序动态规划求解。设 表示从顶点 到 的最短路长度。 - 动态规划转移方程为:
- 边界条件为
。
最后将两部分的贡献相加即为所求答案。
复杂度分析
时间复杂度:线性筛预处理为
空间复杂度:线性筛预处理耗费
参考代码
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 10000000;
vector<int> primes;
int min_prime[MAXN + 1];
// 线性筛预处理最小质因子
void sieve() {
for (int i = 2; i <= MAXN; ++i) {
if (min_prime[i] == 0) {
min_prime[i] = i;
primes.push_back(i);
}
for (int p : primes) {
if (p > min_prime[i] || (ll)i * p > MAXN) break;
min_prime[i * p] = p;
}
}
}
// 获取 n 的所有不同质因子
vector<int> get_prime_factors(int n) {
vector<int> factors;
while (n > 1) {
int p = min_prime[n];
factors.push_back(p);
while (n % p == 0) {
n /= p;
}
}
return factors;
}
// 容斥原理:求 [1, m] 中与 n 互质的数的个数
ll count_coprime(ll m, const vector<int>& factors) {
if (m <= 0) return 0;
int k = factors.size();
ll count = 0;
int total_subsets = 1 << k;
for (int mask = 0; mask < total_subsets; ++mask) {
ll prod = 1;
int bits = 0;
for (int i = 0; i < k; ++i) {
if ((mask >> i) & 1) {
prod *= factors[i];
bits++;
}
}
if (bits % 2 == 1) {
count -= m / prod;
} else {
count += m / prod;
}
}
return count;
}
void fc() {
ll l, r, n;
cin >> l >> r >> n;
// 1. 找到不超过 n 的最大质数 P
int P = n;
while (P > 1 && min_prime[P] != P) {
P--;
}
ll ans = 0;
// 2. 处理 x < P 的部分:使用容斥原理求解互质个数
ll R1 = min((ll)P - 1, r);
if (l <= R1) {
vector<int> factors = get_prime_factors(n);
ll len = R1 - l + 1;
ll coprime_cnt = count_coprime(R1, factors) - count_coprime(l - 1, factors);
ans += 2 * len - coprime_cnt;
}
// 3. 处理 x >= P 的部分:使用小范围 DP
ll L2 = max(l, (ll)P);
if (L2 <= r) {
// dp[i - P] 表示从 i 到 n 的最短路长度
vector<int> dp(n - P + 1, 0);
dp[n - P] = 0;
for (int u = n - 1; u >= P; u--) {
int min_dist = 1e9;
for (int i = u + 1; i <= n; i++) {
min_dist = min(min_dist, dp[i - P] + std::gcd(i, u));
}
dp[u - P] = min_dist;
}
for (int x = L2; x <= r; x++) {
ans += dp[x - P];
}
}
cout << ans << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
sieve();
int t = 1;
cin >> t;
while (t--) fc();
return 0;
}