A完美序列 数组统计频率+目标和组合优化

核心特征

问题目标:找到一个目标和s,使得数组中能组成最多数量元素对,每个元素对的和为 s,最终求最大的总元素个数(或对数量)。

数据特点:输入元素的取值范围较小(如题目中ai ≤5000),因此目标和s的范围可枚举(2≤s≤2×5000)

核心操作:对每个可能的目标和s,统计数组中能组成和为s的元素对数量。

分两种情况计算:元素对中两个元素相等(j=s−j)和不相等(j!=s−j)。

取所有目标和中能组成的最大元素总数作为答案.

核心思路

1.频率统计,用数组统计每个元素的出现次数,降低重复计算成本

2.目标和枚举,根据元素取值范围枚举所有可能的目标和s

3.分情况计算匹配数,j==i-j和j!=i-j

4.取最大值,对所有目标和的结果取最大值即为最优解

代码实现

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5e3;

void solve(){
    int n;
    cin >> n;
    vector<int> a(2*N+1);//()非[]
    for(int i = 1;i <= n;i ++){
        int x;
        cin >> x;
        a[x]++;
    }
    int ans = 0;
    for(int i = 1;i <= 2*N;i++){
        int res = 0;
        for(int j = 1;j <= i - j;j ++){
            if(j==i-j) res += a[j] / 2 * 2;
            else res += min(a[j],a[i-j]) * 2;
        }
        ans = max(ans,res);
    }
    cout << ans << endl;
}

signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T = 1;
    //cin >> T;
    while(T--) solve();
    return 0;
}

相关问题衍生

1.完美序列问题:要求序列中所有相邻元素对的和相等,本质是找到最优目标和s并计算最大匹配数

2.两数之和问题,统计和为s的元素对有多少组

3.计数类优化问题,需要枚举所有可能的目标值,通过统计频率,避免暴力O(n^2)->O(V^2)其中V为元素取值范围,通常V<<n

总结

基于频率统计的目标和组合优化问题,利用元素取值范围小的特点,通过枚举目标和与分情况计数求解

B-0 数论分块 由末尾0想到10由质数2和5相乘得

推导过程

alt

f函数:统计1 到 n 的所有整数中,每个数的所有因数里,质因数 x 的总出现次数

代码实现

#include <bits/stdc++.h>
using namespace std;
#define int long long

void solve() {
    int L,R;
    cin >> L >> R;
    auto f = [&](int x,int lim) ->int{
        int num = 0;
        for(int i = x;i <= lim;i *= x){
            int n = lim / i;
            for(int l = 1;l <= n;l = n / (n / l) + 1){
                int r = n / (n / l);
                num += (r - l + 1) * (n / l);
            }
        }
        return num;
    };
    int cnt2 = f(2,R) - f(2,L-1);
    int cnt5 = f(5,R) - f(5,L-1);
    cout << min(cnt2,cnt5) << endl;
}

signed main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int T = 1;
    // std::cin >> T;
    while(T--) {
        solve();
    }
    return 0;
}

相关题型+共同思路

质因数+统计

1.阶乘末尾0个数

2.阶乘中质因数p的总个数

3.组合数C(n,m)的末尾0个数 alt

计算 n!、m!、(n-m)! 中质因数 5 的个数,三者相减后即为 C (n,m) 中 5 的个数(2通常比5多)

4.判断一个数a是否为阶乘n!的倍数

将a分解为质因数,若对每个pi,都比其在 n! 中的总个数多,则a为n!的倍数

G 枚举

算法基础积累

set容器存储数组中的元素,去重且有序aa.insert(a[i])数组元素插入 set会处理重复值

aa.contains()高效判断某值是否存在于数组中

vector数组排序:sort(a.begin(),a.end());

代码实现

#include <bits/stdc++.h>
using namespace std;
#define int long long

void solve(){
    int n;
    cin >> n;
    vector<int> a(n);
    set<int> aa;//有序且去重
    for(int i = 0;i < n;i ++){
         cin >> a[i];
         aa.insert(a[i]);//数组元素插入
    } 
    sort(a.begin(),a.end());
    for(int i = 0;i < n;i ++)
        for(int j = 0;j < n;j ++){
            if(!aa.contains(a[i] | a[j])){
                cout << "NO" << endl;
                return;
            }
        }
      cout << "YES" << endl;
    
}

signed main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int T = 1;
    //cin >> T;
    while(T--) solve();
    return 0;
}

C-合并石子 贪心+递推+前后缀

思路

1.问题聚焦:所有石子合并到某一个位置的最小总代价

2.问题分解:左右代价分离,合并到位置i的总代价 = 左边所有元素移到i的代价 + 右边所有元素移到i的代价

3.递推计算:lr[i]:从左到右递推,存储 1 - i-1 号元素合并到 i 号的总代价。

递推关系:lr[i] = lr[i-1] + 新增代价(新增代价是将 i-1 号位置的所有元素移到 i 号的趟数)。

rl[i]:从右到左递推,存储 i+1 - n 号元素合并到 i 号的总代价。

递推关系:rl[i] = rl[i+1] + 新增代价(新增代价是将 i+1 号位置的所有元素移到 i 号的趟数)。

递推意义:复用前一步的计算结果,避免重复累加元素和,时间复杂度从O(n²)降至O(n)

4.向上取整没搬完剩下<k个石子也要占一次体力消耗(sum + k - 1) / k,k-1作为偏移量只要sum/k还有余数就会向上取整

5.贪心选择:遍历终点体力消耗最小值找最优合并点

代码实现

#include <bits/stdc++.h>
using namespace std;
#define int long long

void solve(){
    int n,k;
    cin >> n >> k;
    vector<int> a(n+1),lr(n+1),rl(n+1);
    for(int i = 1;i <= n;i ++) cin >> a[i];
    
    int sum = 0;
    for(int i = 2;i <= n;i ++){
        sum += a[i-1];
        lr[i] = lr[i-1] + (sum + k - 1) / k;
    }
    
    sum = 0;
    for(int i = n - 1;i >= 1;i --){
        sum += a[i+1];
        rl[i] = rl[i+1] + (sum + k - 1) / k;
    }
    
    int ans = 2e18;
    for(int i = 1;i <= n;i ++)
        ans = min(ans,lr[i] + rl[i]);
    
    cout << ans << endl;
}

signed main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    int T = 1;
    //cin >> T;
    while(T--) solve();
    return 0;
}                                

总结

用分治拆解决策,左右代价分离

靠递推优化,计算前后缀累加

细节技巧向上取整公式

最后贪心遍历找全局最优

J 贪心 +特殊值

思路:多关键字比较+特殊值

操作使a1的值最大,选择最大值索引j(2-n)
如果存在多个最大值,选择编号最大的索引 如果最大值为0,不进行操作(a1无变化而长度减小)

代码实现

#include <bits/stdc++.h>
using namespace std;
#define int long long

void solve() {
   int n;
    cin >> n;
    vector<int> a(n+1);
    for(int i = 1;i <= n;i ++) cin >> a[i];
    int idx = 0;
    for(int i = 2;i <= n;i ++){
        if(a[i]==0) continue;
        //if(std::tie(a[i], i) > std::tie(a[idx], idx)) idx = i;
        if(a[i] > a[idx] || (a[i] == a[idx] && i > idx)) idx = i;
    }
    cout << n - (idx != 0) << endl;
    
    a[1] += a[idx];
    for(int i = 1;i <= n;i ++){
        if(i==idx) continue;
        cout << a[i] << " " ;
    }
    cout << endl;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T = 1;
    while(T--) solve();
    return 0;
}  

总结

代码的精细处学习
1.if(a[i]==0) continue;
2.if(a[i] > a[idx] || (a[i] == a[idx] && i > idx)) idx = i;

D 01-BFS

处理带有两种不同移动代价的二维网格路径搜索问题

问题场景

给定一个二维字符网格,要求从左上角走到右下角,并且根据每个位置的字符方向(U、D、L、R)来决定移动的代价。如果按照字符方向移动,代价为 0,否则代价为 1。需要判断是否能在不超过给定步数k的情况下到达右下角。由于移动代价只有 0 和 1 两种情况,这符合 01BFS 的典型特征。通过双端队列来有效处理。

思路

一、数据结构与初始化:

1.deque<tuple<int, int, int>>(dq)双端队列存储待处理的节点,包含坐标(x,y)和到达该点的代价num

2.vis(n, vector(m, - 1))来记录是否被访问过,及从起点到该位置的最小代价num

3.vector<pair<int, int>> d来表示四个方向坐标偏移量,string idx = "UDLR"对应方向字符

二、BFS:

1.起点入队:将起点(0,0)以代价 0 入队,即dq.push_back({0, 0, 0})

2.循环处理队列:auto [x, y, num] = dq.front();取出队首节点, 如果该节点已被访问过(vis[x][y] != -1),则跳过。 标记该节点已访问,并记录到达该节点的代价vis[x][y] = num;

遍历四个方向: 获取方向偏移量const auto &[dx, dy] = d[i];,计算新坐标int xx = x + dx; int yy = y + dy;

如果新坐标越界或者已被访问,则跳过。

根据当前位置(x,y)的字符v[x][y]与移动方向idx[i]是否一致来决定新节点插入队首还是队尾

如果一致(v[x][y] == idx[i]),则将新节点(xx,yy,num)插入队首,表示代价为 0 的移动。

如果不一致,则将新节点(xx,yy,num + 1)插入队尾,表示代价为 1 的移动。

3.结果判断:最后判断vis[n - 1][m - 1]是否小于等于k判断输出

代码实现

#include <bits/stdc++.h>
using namespace std;
#define int long long
typedef pair<int,int> PII;
typedef tuple<int,int,int> TIII;

vector<PII> d = {{-1,0},{1,0},{0,-1},{0,1}};
string idx = "UDLR";

void solve() {
    int n,m,k;
    cin >> n >> m >> k;
    vector<string> v(n);
    for(int i = 0;i < n;i ++) cin >> v[i];
    vector vis(n,vector<int>(m,-1));
    deque<TIII> dq;
    dq.push_back({0,0,0});
    while(!dq.empty()){
        auto[x,y,num] = dq.front();
        dq.pop_front();
        if(vis[x][y] != -1) continue;
        vis[x][y] = num;
        for(int i = 0;i < 4;i ++){
            auto[dx,dy] = d[i];
            int xx = x + dx,yy = y + dy;
            if(xx < 0 || xx >= n || yy < 0 || yy >= m || vis[xx][yy] != -1) continue;
            if(v[x][y] == idx[i]) dq.push_front({xx,yy,num});
            else dq.push_back({xx,yy,num+1});
        }
    }
    cout << (vis[n-1][m-1] <= k ? "YES": "NO" ) << endl;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T = 1;
    cin >> T;
    while(T--) solve();
    return 0;
}
    
    

L 素数筛+数论

一个数的因数个数为质数且非2 则加到最后结果中

思路

数学上:这类数等价于质数的(质数 - 1)次幂

预处理:用线性筛找质数,枚举其幂次,标记符合条件的数的平方根

验证:对输入数,判断是否为完全平方数且平方根已标记,累加符合条件的数。

代码实现

  #include <bits/stdc++.h>
using namespace std;
#define int long long

const int N = 1e6;
vector<int> pri;           // 存储质数列表
vector<bool> not_prime;    // 标记非质数的数组

// 线性筛预处理质数
void pre(int n) {
    not_prime.resize(n + 1);
    for (int i = 2; i <= n; ++i) {
        if (!not_prime[i]) {
            pri.push_back(i);
        }
        for (int p : pri) {
            if (i * p > n) break;
            not_prime[i * p] = true;
            if (i % p == 0) break;
        }
    }
}

void solve() {
    vector<bool> valid_sqrt(N + 1);  // 标记符合条件的数的平方根
    
    // 预处理符合条件的平方根标记
    for (int a : pri) {
        for (int b = 2, y = a * a; y <= N * N; ++b, y *= a) {
            if (!not_prime[b + 1]) {  // 若因数个数是质数(b+1为质数)
                valid_sqrt[(int)sqrtl(y)] = true;
            }
        }
    }
    
    // 处理输入并计算结果
    int n;
    cin >> n;
    int ans = 0;
    while (n--) {
        int x;
        cin >> x;
        int sqx = (int)sqrtl(x);
        if (sqx * sqx == x && valid_sqrt[sqx]) {
            ans += x;
        }
    }
    cout << ans << '\n';
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    pre(N);  // 预处理质数
    
    int T = 1;
    // cin >> T;  // 多组测试数据取消注释
    while (T--) solve();
    
    return 0;
}

总结

完全平方数是满足因数个数为质数且非2的一种常见且易筛选的情况