:加油加油,没几天就要比赛了
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)在索引是非常方便;