当前位置: 代码迷 >> 综合 >> 【启发式合并】Codeforces1037F Maximum Reduction
  详细解决方案

【启发式合并】Codeforces1037F Maximum Reduction

热度:86   发布时间:2023-09-27 06:20:27.0

分析:

再次出现F题比E题简单。。。。

这题其实非常的水,那个代码的意义就是:在a序列中,找到所有大小为k,2k?1,3k?1,k,2k?1,3k?1,……的区间,再将每个区间内的最大值加起来。

然后很容易想到算贡献,对于一个数aiai,找到它左边第一个不小于它的位置lili,以及它右边第一个大于它的位置riri,在这两个位置之间,且包含了i的区间,一定是以aiai为它的值。

找这两个位置是单调栈的基本操作,这里就不赘述了。

然后就可以启发式合并,对每个位置i,分为左右两段:[li,i],[i,ri][li,i],[i,ri]
枚举这两段中较小的一段中每个位置jj,然后算一端为 j ,一端在另一段中的区间有多少,再乘上aiai作为贡献加进去。然后就可以过了
 

额,感觉这样有点不友好,我还是写写这玩意怎么就是启发式合并了:
首先,把大小关系定义为:如果aiajai大于aj,满足ai>aj(ai=aji<j)ai>aj或(ai=aj且i<j),即相等的靠前的更大。

这样一来所有点都不可能相等了。

找到全局最大的一个点,视为根,它一定覆盖了整个序列。剩下的点覆盖的区间一定不会越过这个根。然后再在左侧找一个最大点,右侧找一个最大点,将它们设为根的左右儿子,这样依次递归下去,最后就能建成一颗二叉树。

用启发式合并的思维来看,每次将当前子树的所有元素,贡献到它的父亲那里去,然后和它的兄弟的点数比大小,较小的一个可以支持线性操作。这实质上就是上面的操作。所以这就是启发式合并。。。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#define SF scanf
#define PF printf
#define MAXN 1000010
#define MOD 1000000007
using namespace std;
typedef long long ll;
int n,k;
ll ans;
ll a[MAXN];
int l[MAXN],r[MAXN];
ll st[MAXN];
int top;
int main(){SF("%d%d",&n,&k);for(int i=1;i<=n;i++)SF("%I64d",&a[i]);for(int i=1;i<=n;i++){while(top>0&&a[st[top]]<a[i])top--;l[i]=st[top]+1;st[++top]=i;}top=0;st[top]=n+1;for(int i=n;i>0;i--){while(top>0&&a[st[top]]<=a[i])top--;r[i]=st[top]-1;st[++top]=i;}for(int i=1;i<=n;i++){if(i-l[i]<r[i]-i){for(int j=l[i];j<=i;j++){int minl=i-j;int maxl=r[i]-j+1;minl=max(minl,k-1);if(minl<maxl){ans+=1ll*(max(0,(maxl-1))/(k-1)-max(0,(minl-1))/(k-1))*a[i];ans%=MOD;}}}else{for(int j=i;j<=r[i];j++){int minl=j-i;int maxl=j-l[i]+1;minl=max(minl,k-1);if(minl<maxl){ans+=1ll*(max(0,(maxl-1))/(k-1)-max(0,(minl-1))/(k-1))*a[i];ans%=MOD;}}}}PF("%I64d",ans);
}
  相关解决方案