这场主要还是贪心,后面那几道贪心还是相当典型的。值得一写。
洛谷原题 B3673 [语言月赛202210] 垃圾分类
没什么好说的,第 i i i 种垃圾能放进第 i i i 个垃圾桶就放,放不下再放万能垃圾桶。
#include
#include
using namespace std;
const int maxn=1e6+6;
typedef long long ll;
int n,c,r[maxn];
int main(){
cin>>n>>c;
for(int i=1;i<=n;i++)
cin>>r[i];
ll ans=0;
for(int i=1,t;i<=n;i++){
cin>>t;
if(t>r[i])ans+=1ll*(t-r[i])*c;
}
cout<<ans;
return 0;
}
洛谷原题 P1093 [NOIP2007 普及组] 奖学金
排序题,先按总分从高到低排序,再按语文成绩从高到低排序,最后按学号从小到大排序 的一个三关键字排序。
一般排序就用sort就可以了,它默认降序排列,某些题需要用到特定排序的性质时可能需要手写排序,比如求逆序对,求第k大数。sort最常规的使用方式就是对基本数据类型进行排序,但这远远不够,我们需要学会使用重载或者函数对象实现自定义的排序方法。
sort函数是依靠比较来实现的排序算法(大部分排序算法是通过比较实现,少部分则不是,比如桶排序,基数排序),因此如果要实现自定义的排序方法,实际上只需要我们确定元素之间的大小关系(“小”数放在前面,“大”数放在后面)。
下面介绍三种实现方式(23不是那么必要):
#include
#include
#include
using namespace std;
const int maxn=305;
int n;
struct student{
int yu,tot,id;
}a[maxn];
bool cmp(student a,student b){
if(a.tot!=b.tot)return a.tot>b.tot;
if(a.yu!=b.yu)return a.yu>b.yu;
return a.id<b.id;
}
int main(){
cin>>n;
for(int i=1,x,y,z;i<=n;i++){
cin>>x>>y>>z;
a[i].id=i;
a[i].tot=x+y+z;
a[i].yu=x;
}
sort(a+1,a+n+1,cmp);
for(int i=1;i<=5;i++){
printf("%d %d\n",a[i].id,a[i].tot);
}
return 0;
}
#include
#include
#include
using namespace std;
const int maxn=305;
int n;
struct student{
int yu,tot,id;
bool operator<(student x){
if(tot!=x.tot)return tot>x.tot;
if(yu!=x.yu)return yu>x.yu;
return id<x.id;
}
}a[maxn];
int main(){
cin>>n;
for(int i=1,x,y,z;i<=n;i++){
cin>>x>>y>>z;
a[i].id=i;
a[i].tot=x+y+z;
a[i].yu=x;
}
sort(a+1,a+n+1);
for(int i=1;i<=5;i++){
printf("%d %d\n",a[i].id,a[i].tot);
}
return 0;
}
sort(a+1,a+n+1,greater())
。这里greater()
创建了一个临时的greater
对象,并把这个对象传入了sort函数中,这个对象重载了()
运算符,使得对象可以像函数一样使用。这个greater
实现的功能是把sort函数改为对int元素的降序排序,理论上来说,只要重载了()
运算符,它就可以当作函数对象实现自定义排序手段。()
运算符的类对象,实现机理和上面一致。它的写法比较简便,简单来说就是[](){}
,方括号里写捕获参数,圆括号里写传入参数,花括号里写函数体即可。sort(a+1,a+n+1,[](student a,student b){
if(a.tot!=b.tot)return a.tot>b.tot;
if(a.yu!=b.yu)return a.yu>b.yu;
return a.id<b.id;
});
洛谷原题 P1223 排队接水
比较经典的贪心,可以很容易想到一个贪心策略:接水时间短的人先接。
接下来证明:假如按接水时间升序排列得到每个人的节水时间为 t 1 , t 2 , . . . , t x , . . . , t y , . . . , t n t_1,t_2,...,t_x,...,t_y,...,t_n t1,t2,...,tx,...,ty,...,tn
如果交换其中 x x x 和 y y y 两个人更优的话,那么交换 t x t_x tx 和 t y t_y ty ,看等待时间如何变化。发现 x x x 前面和 y y y 后面的人的等待时间没有变化,而 x x x 到 y y y 之间的人从原来每人等 t x t_x tx 变成了等 t y t_y ty,显然不优,与假设矛盾。类似的,如果把接水时间更长的人与前面接水时间更短的人交换,中间的人等待的时间就会变长,因此一定是接水时间短的人在前。
实际上贪心策略大部分情况下都不太好证明,所以赛时基本都是手玩一下数据,如果感觉没有问题就可以试一试。
策略找到了,只需要排好序后统计一下总的等待时间即可。
#include
#include
#include
using namespace std;
const int maxn=1005;
int n;
pair<int,int> t[maxn];
int main(){
cin>>n;
for(int i=1;i<=n;i++)
cin>>t[i].first,t[i].second=i;
sort(t+1,t+n+1);
long long tot=0;
for(int i=1;i<=n;i++){
tot+=1ll*(n-i)*t[i].first;
printf("%d ",t[i].second);
}
printf("\n%.2lf",1.*tot/n);
return 0;
}
洛谷原题 P4995 跳跳!
很容易想到一个贪心思路:从最低点跳到最高点,然后跳到次低点,再跳到次高点…后面类推。
证明有些难度,不妨假设地面高度是 h 0 = 0 h_0=0 h0=0,那么题目相当于找到一个路径 h 0 → h p → h q → . . . → h x h_0 \rightarrow h_p \rightarrow h_q \rightarrow ... \rightarrow h_x h0→hp→hq→...→hx ,经过每个点一次,路线长度最长。
不妨先看从 h 0 h_0 h0 跳到 h n h_n hn 是否最优,如果不优,那么应当存在一个点 p p p,使得从 h 0 h_0 h0 移动到 h p h_p hp更优。那么不妨让 h n h_n hn 到 h p h_p hp 之间的路径翻转一下,也就是从 h 0 → h n → . . . → h p ‾ → h q → . . . → h x h_0 \rightarrow \underline{h_n \rightarrow ... \rightarrow h_p} \rightarrow h_q \rightarrow... \rightarrow h_x h0→hn→...→hp→hq→...→hx变为 h 0 → h p → . . . → h n ‾ → h q → . . . → h x h_0 \rightarrow \underline{h_p \rightarrow ... \rightarrow h_n} \rightarrow h_q \rightarrow... \rightarrow h_x h0→hp→...→hn→hq→...→hx看看这时代价产生了什么变化。
因为 h 0 ≤ h p ≤ h n h_0\le h_p\le h_n h0≤hp≤hn,所以 − 2 ( h 0 − h p ) ( h n − h p ) ≥ 0 -2(h_0-h_p)(h_n-h_p)\ge 0 −2(h0−hp)(hn−hp)≥0,因此前者要更大,即原本 h 0 h_0 h0 跳到 h n h_n hn 比 h 0 h_0 h0 跳到任意一个 h p h_p hp 都要更优。
综上,因此 h 0 h_0 h0 一开始一定是要跳到 h n h_n hn,否则不优。同理,离开 h 0 h_0 h0 后,这个点相当于消失了,这时候的最小值变成了 h 1 h_1 h1 ,从 h n h_n hn 一定是要跳到最小值 h 1 h_1 h1 上的。类推,因此路径一定是先从地面跳到最高点,再跳到最低点,再跳到次高点,再跳回次低点… 即路径为 h 0 → h n → h 1 → h n − 1 → . . . → h ⌈ n 2 ⌉ h_0 \rightarrow h_n \rightarrow h_1 \rightarrow h_{n-1}\rightarrow ... \rightarrow h_{\left\lceil\frac n 2\right\rceil} h0→hn→h1→hn−1→...→h⌈2n⌉
实现的话左右各放一个指针(双端队列也可以),模拟一遍路径累加一下长度就行了。
#include
#include
#include
using namespace std;
const int maxn=305;
typedef long long ll;
int n,a[maxn];
int main(){
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
sort(a+1,a+n+1);
int i=0,j=n;
ll ans=0;
while(i<j){
ans+=1ll*(a[j]-a[i])*(a[j]-a[i]);
i++;
ans+=1ll*(a[j]-a[i])*(a[j]-a[i]);
j--;
}
cout<<ans;
return 0;
}
洛谷原题 P1080 [NOIP2012 提高组] 国王游戏
这个很有难度,而且需要使用高精度乘法和除法,应该是最难的题,直接干掉一票人(昨天晚上为止就两个人做出来)。
考虑如果编号 i i i 和 i + 1 i+1 i+1 的两个大臣交换位置,代价会怎么变:
首先 i i i 前面和 i + 1 i+1 i+1 后面的大臣得到的金币数量是没有变的,只有 i i i 和 i + 1 i+1 i+1 两个人变了。如果要它们交换会影响最大值,那么这两个人中一定有一个为最大值,要不然换不换无所谓。假设前 x x x 个人左手上的乘积为 s x s_x sx ,那么:
我们要找 i i i 在前和 i + 1 i+1 i+1 在前两种情况中 两人获得金币的最大值 较小的那个。假如大臣 i i i 在前面要较小( 大臣 i + 1 i+1 i+1 同理,因此只需要证明大臣 i i i 在前需要满足什么条件即可)。
我们把上面的四个式子标上序号,如果 1 ◯ \textcircled 1 1◯ 式是最大值,那么应当满足 1 ◯ ≥ 2 ◯ \textcircled 1 \ge \textcircled 2 1◯≥2◯。当大臣 i + 1 i+1 i+1 在前时, 1 ◯ → 3 ◯ \textcircled 1\rightarrow \textcircled 3 1◯→3◯,最大值显然变大,符合假设(也就是 1 ◯ \textcircled 1 1◯ 是第一种情况的最大值,同时小于第二种情况的最大值,即两个最大值中较小的那个)。因此此时只需要满足 1 ◯ ≥ 2 ◯ \textcircled 1 \ge \textcircled 2 1◯≥2◯ 即可,即 b i + 1 ≥ a i ∗ b i b_{i+1}\ge a_i*b_i bi+1≥ai∗bi
如果 2 ◯ \textcircled 2 2◯ 式是最大值,那么应当满足 1 ◯ ≤ 2 ◯ \textcircled 1 \le \textcircled 2 1◯≤2◯。当大臣 i + 1 i+1 i+1 在前时, 2 ◯ → 4 ◯ \textcircled 2\rightarrow \textcircled 4 2◯→4◯ 会变小,要维持 2 ◯ \textcircled 2 2◯ 是两种情况中较小的最大值,需要 3 ◯ \textcircled 3 3◯ 是第二种情况的最大值,同时满足 2 ◯ ≤ 3 ◯ \textcircled 2 \le \textcircled 3 2◯≤3◯。 1 ◯ ≤ 2 ◯ ⇒ b i + 1 ≤ a i ∗ b i \textcircled 1 \le \textcircled 2 \Rightarrow b_{i+1}\le a_i*b_i 1◯≤2◯⇒bi+1≤ai∗bi 2 ◯ ≤ 3 ◯ ⇒ a i ∗ b i ≤ a i + 1 ∗ b i + 1 \textcircled 2 \le \textcircled 3 \Rightarrow a_i*b_i\le a_{i+1}*b_{i+1} 2◯≤3◯⇒ai∗bi≤ai+1∗bi+1
仔细观察两个条件,发现两个条件可以合并成一个条件,即 a i ∗ b i ≤ a i + 1 ∗ b i + 1 a_i*b_i\le a_{i+1}*b_{i+1} ai∗bi≤ai+1∗bi+1这说明在满足上面这个式子时,大臣 i i i 在前时不管谁取得最大值,得到的最大值要更小。因此满足这个条件时,大臣 i i i 要放在前面。
之后两两比较,把 a i ∗ b i a_i*b_i ai∗bi 更小的放在前面,可以通过冒泡排序把序列排序,按 a i ∗ b i a_i*b_i ai∗bi 升序排列。排好序后,我们只需要从头枚举到尾,看每个人能得到多少金币,最大值即为所求,因为 n ≤ 1000 a i ≤ 10000 n\le1000 \quad a_i\le 10000 n≤1000ai≤10000 ,前缀积最多能有最多4000位,需要用高精度。
高精乘高精,高精除低精。我个人建议除了除法,加减乘都只写高精运算高精,再写一个普通数转化高精数的函数就可以了。反正都用高精了,效率一般不是问题,这样写码量少很多。备好板子,赛时要用直接抄板子。
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn=1005;
int n;
struct peo{
int a,b;
bool operator<(peo x){
return a*b<x.a*x.b;
}
}a[maxn];
vector<int> g(int x){
vector<int> C;
do{
C.push_back(x%10);
x/=10;
}while(x);
return C;
}
vector<int> mul(vector<int> A,vector<int> B){
vector<int> C(A.size()+B.size());
for(int i=0;i<A.size();i++){
for(int j=0;j<B.size();j++){
C[i+j]+=A[i]*B[j];
}
}
for(int i=0;i<C.size();i++)
if(C[i]>9){
C[i+1]+=C[i]/10;
C[i]%=10;
}
while(C.size()>1 && C.back()==0)
C.pop_back();
return C;
}
vector<int> div(vector<int> A,int b){
vector<int> C(A.size());
int r=0;
for(int i=A.size()-1;i>=0;i--){
r=r*10+A[i];
C.push_back(r/b);
r%=b;
}
reverse(C.begin(),C.end());
while(C.size()>1 && C.back()==0)
C.pop_back();
return C;
}
vector<int> max(vector<int> A,vector<int> B){
if(A.size()!=B.size())return (A.size()>B.size())?A:B;
for(int i=A.size()-1;i>=0;i--)
if(A[i]!=B[i])
return (A[i]>B[i])?A:B;
return A;
}
void print(vector<int> A){
for(int i=A.size()-1;i>=0;i--)
printf("%d",A[i]);
puts("");
}
int main(){
cin>>n;
for(int i=0;i<=n;i++)
cin>>a[i].a>>a[i].b;
sort(a+1,a+n+1);
vector<int> s(g(a[0].a)),ans(g(0));
for(int i=1;i<=n;i++){
ans=max(ans,div(s,a[i].b));
s=mul(s,g(a[i].a));
}
print(ans);
return 0;
}
洛谷原题 P1842 [USACO05NOV] 奶牛玩杂技
和上一个题思路一模一样,不过因为没有高精所以好写很多。
考虑如果编号 i i i 和 i + 1 i+1 i+1 的两个奶牛交换位置,压扁指数会怎么变:
首先 i i i 上面和 i + 1 i+1 i+1 下面的奶牛压扁指数是没有变的,只有 i i i 和 i + 1 i+1 i+1 两个奶牛变了。如果要它们交换会影响最大值,那么这两个奶牛中一定有一个为最大值,要不然换不换无所谓。假设上面 x x x 个奶牛重量之和为 s x s_x sx ,那么:
我们要找 i i i 在上和 i + 1 i+1 i+1 在上两种情况中 两奶牛压扁指数的最大值 较小的那个。假如奶牛 i i i 在上面要较小( 奶牛 i + 1 i+1 i+1 同理,因此只需要证明奶牛 i i i 在上面需要满足什么条件即可)。
我们把上面的四个式子标上序号,如果 1 ◯ \textcircled 1 1◯ 式是最大值,那么应当满足 1 ◯ ≥ 2 ◯ \textcircled 1 \ge \textcircled 2 1◯≥2◯。当奶牛 i + 1 i+1 i+1 在上时, 1 ◯ → 3 ◯ \textcircled 1\rightarrow \textcircled 3 1◯→3◯,最大值显然变大,符合假设(也就是 1 ◯ \textcircled 1 1◯ 是第一种情况的最大值,同时小于第二种情况的最大值,即两个最大值中较小的那个)。因此此时只需要满足 1 ◯ ≥ 2 ◯ \textcircled 1 \ge \textcircled 2 1◯≥2◯ 即可,即 b i + 1 ≥ a i + b i b_{i+1}\ge a_i+b_i bi+1≥ai+bi
如果 2 ◯ \textcircled 2 2◯ 式是最大值,那么应当满足 1 ◯ ≤ 2 ◯ \textcircled 1 \le \textcircled 2 1◯≤2◯。当奶牛 i + 1 i+1 i+1 在上时, 2 ◯ → 4 ◯ \textcircled 2\rightarrow \textcircled 4 2◯→4◯ 会变小,要维持 2 ◯ \textcircled 2 2◯ 是两种情况中较小的最大值,需要 3 ◯ \textcircled 3 3◯ 是第二种情况的最大值,同时满足 2 ◯ ≤ 3 ◯ \textcircled 2 \le \textcircled 3 2◯≤3◯。 1 ◯ ≤ 2 ◯ ⇒ b i + 1 ≤ a i + b i \textcircled 1 \le \textcircled 2 \Rightarrow b_{i+1}\le a_i+b_i 1◯≤2◯⇒bi+1≤ai+bi 2 ◯ ≤ 3 ◯ ⇒ a i + b i ≤ a i + 1 + b i + 1 \textcircled 2 \le \textcircled 3 \Rightarrow a_i+b_i\le a_{i+1}+b_{i+1} 2◯≤3◯⇒ai+bi≤ai+1+bi+1
仔细观察两个条件,发现两个条件可以合并成一个条件,即 a i + b i ≤ a i + 1 + b i + 1 a_i+b_i\le a_{i+1}+b_{i+1} ai+bi≤ai+1+bi+1这说明在满足上面这个式子时,奶牛 i i i 在上时不管谁取得最大值,得到的最大值要更小。因此满足这个条件时,奶牛 i i i 要放在上面。
之后两两比较,把 a i + b i a_i+b_i ai+bi 更小的放在上面,可以通过冒泡排序把序列排序,按 a i + b i a_i+b_i ai+bi 升序排列。排好序后,我们只需要从头枚举到尾,记录一下最大的压扁指数即可(压扁指数可以是负数)。
#include
#include
#include
using namespace std;
const int maxn=5e4+5;
int n;
struct cow{
int w,s;
bool operator<(cow x){
return w+s<x.w+x.s;
}
}a[maxn];
int main(){
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i].w>>a[i].s;
sort(a+1,a+n+1);
int ans=-1145141919,t=0;
for(int i=1;i<=n;i++){
ans=max(ans,t-a[i].s);
t+=a[i].w;
}
cout<<ans;
return 0;
}
洛谷原题 P1803 凌乱的yyy / 线段覆盖
这也是个典型的贪心,按比赛结束时间排序,每次枚举结束时间,尝试在这个结束时间之前塞入最多的比赛。
感性理解的话,如果你在某时刻会闲下来,之后选一个能选的比赛中结束时间最早的比赛来打,这样比赛结束后对后续选择影响也最小。
如果选择结束时间最早的比赛不优,那么肯定存在一场结束时间不是最早的比赛,打它会更优,但是选择前者可以打的比赛数量+1,同时闲下来的时间更早;选择后者打的比赛数量+1,但闲下来的时间要晚。显然不优,和前面的假设就矛盾了。
因此对比赛按结束时间排序,每次尝试打一场结束比赛最早的比赛,直到遍历一遍比赛,看看能打几场,就是答案。如果要看严谨证明的话可以去洛谷题解区看。
codeforces上有一期比赛有道题考了一道有点类似的,指路。好吧可能不太一样
#include
#include
#include
using namespace std;
const int maxn=1e6+5;
int n;
struct data{
int s,e;
bool operator<(data x){
if(e!=x.e)return e<x.e;
return s<x.s;
}
}a[maxn];
int main(){
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i].s>>a[i].e;
sort(a+1,a+n+1);
int ans=0,lst=0;
for(int i=1;i<=n;i++){
if(a[i].s>=lst){
ans++;
lst=a[i].e;
}
}
cout<<ans;
return 0;
}
洛谷原题 P1589 泥泞路
题目也没说是开区间还是闭区间,看样例推测应该是半开半闭,这样就不需要考虑端点了。
我们只铺一段区间的时候,肯定从左端点开铺,铺到大于等于右端点就行了。但是这时有可能盖到下一段区间的一部分,这时候肯定接着刚刚的继续铺,而不是重新从区间左端点开铺,有便宜为什么不占呢。
一开始的想法:记录一下上一段铺的区间,如果上一段区间盖到了这段区间的一部分,就把这个区间合并到上一个区间上,铺的时候一块铺。如果没盖到,把上一个区间铺了,改为记录这个区间。
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn=1e4+5;
int n,L;
struct mud{
int s,e;
bool operator<(mud x){
return s<x.s;
}
}a[maxn],b[maxn];
int cnt;
int main(){
cin>>n>>L;
for(int i=1;i<=n;i++)
cin>>a[i].s>>a[i].e;
sort(a+1,a+n+1);
ll l=a[1].s,r=a[1].e,ans=0;
for(int i=2;i<=n;i++){
if(l+(r-l+L-1)/L*L>a[i].s){//合并区间
r=a[i].e;
}
else {
ans+=(r-l+L-1)/L;
l=a[i].s;
r=a[i].e;
}
}
ans+=(r-l+L-1)/L;
cout<<ans;
return 0;
}
简单点的想法,只记录上一次铺到的位置。如果铺到的位置大于当前区间左端点,接着刚才的位置继续铺。如果铺到的位置小于当前区间左端点,从区间左端点开始铺。
#include
#include
#include
using namespace std;
const int maxn=1e4+5;
int n,L;
struct merge{
int s,e;
bool operator<(merge x){
return s<x.s;
}
}a[maxn],b[maxn];
int cnt;
int main(){
cin>>n>>L;
for(int i=1;i<=n;i++)
cin>>a[i].s>>a[i].e;
sort(a+1,a+n+1);
long long ans=0;
for(int i=1,r=0,cnt;i<=n;i++){//r表示上次铺到了哪里
r=max(r,a[i].s);
cnt=max((a[i].e-r+L-1)/L,0);
r=r+cnt*L;
ans+=cnt;
}
cout<<ans;
return 0;
}
洛谷原题 P1843 奶牛晒衣服
第一眼感觉是二分答案,然后才想到贪心。
二分答案的做法非常明显,就是二分用的时间,然后计算每件衣服需要烘干几次,如果总次数超过了时间,说明时间不够多。如果没有超过,说明时间够多了。
贪心的算法其实也很好理解,衣服晾干是同时进行的,所以最湿的衣服晾干了,其他衣服也就都干了,那么我们每次烘一次最湿的衣服,烘干次数等于晾的时间,如果最湿的衣服剩下的湿度小于晾的时间乘a。那么这个时间就是答案。因为要动态找最湿的衣服,所以用堆来存储。
二分答案
#include
#include
#include
using namespace std;
const int maxn=5e5+5;
int n,a,b;
int w[maxn];
bool check(int t){
int cnt=0;//烘干几次
for(int i=1;i<=n;i++)
if(1ll*a*t<w[i])
cnt+=(w[i]-a*t+b-1)/b;
return cnt<=t;
}
int main(){
cin>>n>>a>>b;
for(int i=1;i<=n;i++)
cin>>w[i];
int l=1,r=5e5,mid;
while(l<r){
mid=(l+r)>>1;
if(check(mid))r=mid;
else l=mid+1;
}
cout<<l;
return 0;
}
堆+贪心
#include
#include
#include
#include
using namespace std;
const int maxn=5e5+5;
int n,a,b;
priority_queue<int> h;
int main(){
cin>>n>>a>>b;
for(int i=1,t;i<=n;i++){
cin>>t;
h.push(t);
}
int cnt=0;
while((h.top()+a-1)/a>=cnt){
cnt++;
int t=h.top()-b;
h.pop();
h.push(t);
}
cout<<cnt-1;
return 0;
}
2023 清华大学学生程序设计竞赛暨高校邀请赛(THUPC2023)初赛
这个题好。很难。
贪心的想法,肯定让最大的数尽量当众数,最大的数有 a n a_n an 个,那么第 i i i 个数要出 m i n ( a i , a n ) min(a_i,a_n) min(ai,an) 个,每出一个,产生一次贡献 n n n。同理,如果最大数不够了,当不了众数了,在剩余的数中选取最大的那个继续当众数,以此类推。
那么对一个数 i i i ,它的前 a n a_n an 个产生了 n 个贡献,然后假如剩下的次大数为 k k k,则有 a k − a n a_{k}-a_{n} ak−an 个产生了 k k k 个贡献( a k > a n a_{k}>a_{n} ak>an),类推。1 ~ a n a_n an a n a_n an ~ a k a_k ak … 是固定的,对一个 a i a_i ai 我们只需要从小到大累加一遍即可,不够了后面就可以停了。所以对每个区间,需要记录一下右端点 r r r,这个值 k k k 是什么 i d id id,为了快速求和,在记录一下前缀和贡献 s s s。之后二分查找它最终所在的区间,累加一下第 i i i 个数的贡献即可。
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
int n,a[maxn];
ll r[maxn],id[maxn],s[maxn];
int cnt;
int main(){
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
ll ans=0;
for(int i=n;i>=1;i--){
if(a[i]>r[cnt]){
r[++cnt]=a[i];
id[cnt]=i;
s[cnt]=s[cnt-1]+1ll*(r[cnt]-r[cnt-1])*i;
}
int idx=upper_bound(r+1,r+cnt+1,a[i])-r-1;
ans+=s[idx]+1ll*(a[i]-r[idx])*id[idx+1];
}
cout<<ans;
return 0;
}