Skip to content
26牛客暑假多校2

26牛客暑假多校2

点击查看题面

M题 Maybe Connected

题目大意

n个点,m条边,求连通但不相连点的对数,无自环或重边

数据范围:1n105,0mmin(109,n(n1)2)

思路

  • n2时,无论怎样都是0
  • 当所有点连接成一条线的时候,取得最大值 当有k个点一条线,则有(k1)(k2)2对点
  • mn1时 可以将m+1个点连成一条线,取得最大值m(m1)2
  • m=n1时取得最大值,当mn时,每多一条边,最大值将减1,需要减去的部分为m(n1)多余出来边数

复杂度分析

时间复杂度:O(1)
空间复杂度:O(1)

参考代码

参考代码
c++
#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的数组,要求执行恰好一次,每次选取k个数,将k个数都变成这k个数都中位数,求这个数组最终和的最大值

数据范围:1kn,n2×105,1ai109

思路

  • 由于排序不会影响答案,先排序
  • 升序条件下,要求操作后增量最大化,应当小于和大于中位数的部分都要最小,即假设M为中位数,前x个较小元素(<=M)无额外限制,直接取全局最小x个,后y个较大元素(>=M),最优解为紧跟中位数之后连续的y

1. k为奇数

  • m=k12,中位数 M=bm
  • 增量展开:
Δ=2mbmi=0m1bii=m+12mbi

贪心构造

    1. 较小元素集合 (b0bm1):仅需满足 bibm,无额外下界限制。要使和最小,直接选取全局最小的前 m 个数,即排序后的前缀 a0,a1,,am1
    1. 较大元素集合 (bm+1b2m):必须满足 bibm。要使和最小,最优解为在排序数组中紧跟在 bm 后的连续 m 个元素。

结构结论

    1. bm=aj(其中 jm),则选取的 k 个元素分割为:固定前缀:a0,a1,,am1(共 m 个元素)。
    1. 连续区间:aj,aj+1,,aj+m(共 m+1 个元素,首元素为 aj)。

2. k为偶数

  • d=k12=m1,中位数 M=bm1+bm2

  • 增量展开:

Δ=(m1)(bm1+bm)i=0m2bii=m+12m1bi

贪心构造

    1. 较小元素集合 (b0bm2):直接选取全局最小的前 m1 个数,即前缀 a0,a1,,am2
    1. 较大元素集合 (bm+1b2m1):紧跟在中位数对 (bm1,bm) 后的连续 m1 个元素。

结构结论: 设 bm1=aj(其中 jm1),则 bm=aj+1。选取的 k 个元素分割为:

    1. 固定前缀a0,a1,,am2(共 d=m1 个元素)。连续区间:aj,aj+1,,aj+m(共 m+1 个元素,包含中位数对及其右侧元素)。
    1. 连续区间aj,aj+1,,aj+m(共 m+1 个元素,包含中位数对及其右侧元素)

3.统一奇偶模型

为了方便计算,只需要统一变量无需大量奇偶讨论,通过滑动窗口求解

固定前缀长度d=k12

连续窗口长度L=kd

滑动范围:窗口首元素 aj 的下标 j[d,nL]

参考代码

参考代码
c++
#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;
}

复杂度分析

时间复杂度:排序 O(nlogn),滑动窗口维护 O(n),总时间复杂度 O(nlogn),完美通过 n2×105

空间复杂度O(n) 存储数组,常数空间完成窗口滑动。

B题 Bitwise Maximization


题目大意

给定一个长度为n的数组,将其分成任意两个集合,输出两个集合的异或和之和的最大值

数据范围:n5×105,0Ai<230


思路

设全局异或和为 S=i=1nAi。对于任意划分,两集合异或和 x,y 满足 xy=S。根据按位加法等式:

x+y=(xy)+2(x & y)=S+2(x & y)

由于 S 为定值,目标转化为最大化 x & y

  1. S 的第 k 位为 1xy 在该位必为一 0 一 1,故 x & y 的第 k 位恒为 0。
  2. S 的第 k 位为 0xy 在该位值相同。若能使 x 在该位为 1,则 y 在该位也必然为 1,此时 x & y 的第 k 位为 1。

然后对所有 Ai 插入线性基,求出最大异或和 X(此 X 即为 x & y 能达到的最大值)

复杂度分析

时间复杂度O(nlog(maxAi))

空间复杂度O(n)

参考代码

参考代码
c++
#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,2,3, 命名的无限有向图。对于每对满足 1i<j 的整数 (i,j),存在一条从 ij、权值为 wi,j=gcd(i,j) 的有向边。定义 cost(u,v) 为顶点 uv 的最小总代价。

对于给定的参数 l,r,n,计算并输出:

i=lrcost(i,n)

数据范围:1T1001lr<n107


思路

设不超过 n 的最大质数为 P。受限于素数间隙理论(Prime Gap),在 107 范围内连续素数之间的最大间隔仅为 154,因此 nP 的数值极小。我们可以以 P 为界将区间 [l,r] 划分为两部分分别处理:

  1. 对于 x<P 的部分
  • gcd(x,n)=1,存在直连边 xn,代价为 1。
  • gcd(x,n)>1,考虑取质数 P 作为中转点。因为 P 为质数且 x<P,必有 gcd(x,P)=1;若 P<n,由于 P 为质数亦有 gcd(P,n)=1。路径 xPn 的代价为 1+1=2
  • 因此在 [l,min(P1,r)] 范围内,与 n 互质的数代价为 1,不互质的数代价为 2。设该区间长度为 len,其中与 n 互质的个数为 C,则贡献为:
ans1=2lenC
  • 利用线性筛提取 n 的质因子,结合容斥原理在前缀上计算互质个数 C=count_coprime(R1)count_coprime(l1)
  1. 对于 xP 的部分
  • 由于 nP 的范围极小,可以采用逆序动态规划求解。设 dp[uP] 表示从顶点 un 的最短路长度。
  • 动态规划转移方程为:
dp[uP]=minu<in(dp[iP]+gcd(i,u))
  • 边界条件为 dp[nP]=0

最后将两部分的贡献相加即为所求答案。


复杂度分析

时间复杂度:线性筛预处理为 O(MAXN);每组测试用例中质因子分解耗时 O(logn),容斥原理状态数 2ω(n)(其中 ω(n)8),DP 部分耗时 O((nP)2logn)。由于 nP154,单组测试用例的复杂度约为 O(2ω(n)+(nP)2logn),能高效通过全部数据。

空间复杂度:线性筛预处理耗费 O(MAXN) 空间(约 40MB),DP 辅助数组耗费 O(nP) 空间,整体空间复杂度为 O(MAXN)


参考代码

参考代码
c++
#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;
}