差分+排序不等式+贪心

:加油加油,没几天就要比赛了

1.差分

差分的思想:给定a[1],a[2]…a[N],构造差分数组,使得a[i]=b[1]+b[2]+…b[i]
总的来说,a[i]就是b[1~i]的前缀和数组,那么构成a[i]的b[1],b[2]…b[i]就是差分数组.
其中b[i]=a[i]-a[i-1]
核心思想如果要对a[l~r]全部都+c,等价于b[l]+=c,b[r+1]-=c;
在这里插入图片描述
相当于:
1.a[1~l-1]无影响
2.a[l~r]加c
3.a[r+1~n]无影响

题目:
输入一个长度为 n 的整数序列。接下来输入 m 个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r] 之间的每个数加上 c。请你输出进行完所有操作后的序列。

代码:

//差分 时间复杂度 o(m)
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N];
int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
        b[i] = a[i] - a[i - 1];      //构建差分数组
    }
    int l, r, c;
    while (m--)
    {
        scanf("%d%d%d", &l, &r, &c);
        b[l] += c;     //将序列中[l, r]之间的每个数都加上c
        b[r + 1] -= c;
    }
    for (int i = 1; i <= n; i++)
    {
        a[i] = b[i] + a[i-1];    //前缀和运算
        //或b[i]=b[i]+b[i-1];//就是将b[i]恢复成前缀和数组
        printf("%d ", a[i]);
    }
    return 0;
}

2.排序不等式

给定两个序列a,b,
同序相乘得最大:如果两个序列都是相同的排序顺序,即都从小到大或都从大到小,这样求得的结果是最大的
逆序相乘得最小:如果两个序列都是相反的排序顺序,即一个从小到大,一个从大到小,这样求得的结果是最小的

题目
在这里插入图片描述
思路:贪心算法:让最慢的人最后打水,即打水时间从小到大排序后就是最优的打水顺序
所以这个和排序不等式有什么关系呢?
我们可以让打水时间从小到大排列,然后每个人的等待时间正好就是从大到小排列的,相乘就是最优/小/少的打水顺序
代码:

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5+5;
int n;
int a[N];
int main() {
    cin >> n;
    for (int i = 0; i < n; ++i) cin >> a[i];    
    sort(a, a + n);    
    long long res = 0;      // WA后就知道开long long 了..    
    for (int i = 0; i < n; ++i) res += a[i] * (n - i - 1);
    //a[i]是第i个人的打水时间,n-i-1是第i个人到第n个人,
    //相乘就是第i个人到第n个人在此期间的所有花费的时间(第i个人的打水时间+其他人的等待时间)
    cout << res << endl;
    return 0;
}

3.差分+贪心+排序不等式

题目:
在这里插入图片描述
思路:
看到尽可能大这种,就想着要求最优解,然后看查询了好几次,那么查询次数最多的区间,肯定要分配更大的数字,才能满足题目的要求,然后落实到每个数上就是,查询越多次数的数字,这个数字要尽可能大,这就是贪心的思想;
那么如何才能得到查询的次数呢,有两种办法,一种就是暴力算出每个位置的查询次数,另一种就是用差分的思想,因为有区间l~r,所以想到了差分
那么最后如何得到结果总和呢,那就是每个位置查询的次数乘以该位置上的数字
如何才能使结果总和变大,应该就是利用排序不等式的思想,使得查询位置次数最多的乘以最大的数字,也就是两个数组(查询次数数组和数字数组)同序相乘再相加,就能使结果和变大;

代码:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std; 
const int N = 1e5 + 10;

// long long 避免可能会爆 int 。
long long arr[N];
long long cnt[N]; // 一维差分,恢复后就是每个位置出现的次数。
int main()
{
    int n; cin >> n;
    for(int i = 1; i <= n; ++i){
        scanf("%d", &arr[i]);
    }
    int m; cin >> m;
    while (m -- ){
        int l, r;
        scanf("%d%d", &l, &r);
        cnt[l]++;//一维差分
        //目的:使得l~r的出现次数都增加1
        cnt[r + 1]--;//一维差分,使r+1~n出现次数保持不变
    }
    for(int i = 1; i <= n; ++i){
        cnt[i] += cnt[i - 1]; // 恢复成统计每个位置出现次数的数组(前缀和数组)
    }
  
    long long sumA = 0;
    for(int i = 1; i <= n; ++i){
        //结果总和=每个位置查询的次数乘以该位置上的数字
        sumA += cnt[i] * arr[i]; //原数组之和
    }
    /*
       因为较大的值 4 5 已经与 较大的次数 2 2 正好对应了。
       所以两个 数组全部排序,再遍历一遍,进行求和就性
        arr[]: 1 2 3 4 5
        cnt[]: 1 1 1 2 2
    */
    long long sumB = 0;
    sort(arr + 1, arr + n + 1); sort(cnt + 1, cnt + n + 1);
    for(int i = 1; i <= n; ++i){
        sumB += cnt[i] * arr[i];//排序后数组之和
    }

    cout << sumB - sumA << endl;

    return 0;
}

4.贪心

贪心的思想主要是局部最优推全局最优
题目:
在这里插入图片描述
主要思想:
一个字符串恰好可以重复k次得到,说明字符串的长度%k应该是0,不为0说明无法修改成k次字符串,输出-1,然后我们通过字符串长度除以k就可以得到子字符串的长度,子字符串的个数就是k,我们依次比较每个子字符串在同位置上的字符,如果不一样就修改
但会出现几个问题,首先是不一样就修改,那谁是正确的?其次,要比较k次很麻烦,每个都得比较,复杂度高
故这里想到了贪心算法,求每个子字符串在同位置上的字符的出现个数,出现最多的就是正确的,那么其它的就是需要修改的,k-正确字符的个数就是修改的次数

代码:

#include <iostream>
#include <cstring>
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 1e6+100;
int arr[N],res[N],sum=0;
int j = 0;
string s;
string ss[N];
int cnt[N];
int ans=0;//修改次数
int main()
{
    int k;
    cin >> k;   
    cin >> s;
    int l = s.size();
    if (l % k != 0)
    {
        cout << -1;
    }
    int m = l / k;//每个子列的长度
   
   //求子字符串
    for (int i = 0,t=0; i < l;)//i控制s,t控制次数,j控制ss的位置
    {
        if (t<m)
        {
            ss[j]+= s[i];
            i++;
            t++;
        }
        else
        {
            t = 0;
            j++;
        }
    }
    /*
1.求子串的方法:
 for (int i = 0; i < s.size(); i=i+t) {
            string x = s.substr(i, t);//substr(起始位置,终止位置)
            v.push_back(x);
        }
2.此处可以不必求子串,直接遍历就好
 for (int i = 0; i < m; i++)
    {
        for (int j = i; j < l; )
        {
            cnt[s[j] - 'a']++;
            j += m;
        }
        int ma = *max_element(cnt, cnt + 50);
        ans += k - ma;
        memset(cnt, 0, sizeof cnt);
    }
*/   
    for (int i = 0; i < m; i++)
    {
        for (int t = 0; t <=j; t++)
        {
            cnt[ss[t][i] - 'a']++;
        }
        int ma = *max_element(cnt,cnt+50);//求正确字符的出现次数
        ans += k - ma;//k-正确字符的个数就是修改的次数
        memset(cnt, 0, sizeof cnt);
        //fill(cnt, cnt + 26, 0);fill可以把初值设为任意值,memset只能设为0/-1
    }
    cout << ans;
    return 0;
}

5.其他小技巧

1.vector做二维动态dfs方便,也就是不知道数组大小,我们可以用vector来代替数组,因为vector是动态增加的;
2.map(key,value)在索引是非常方便;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值