bzoj 4709 [Jsoi2011]柠檬——单调栈二分处理决策单调性
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4709
题解:https://blog.csdn.net/neither_nor/article/details/53285115
每次选的段的两端种类相同。因为贡献有个数的二次方,所以对于 i ,更小的 j 的 [ j+1 , i ] 之间部分的贡献增长得更快。所以随着个数的增加,较小的 j 会越来越优于较大的 j ,就有决策单调性。
但是用指针的话,可能有下一个位置不优于这个位置,但下下个位置就优于这个位置的情况,用指针就走不过去了。
所以用这种单调栈+二分的方法。
就是用二分求一下栈里两个相邻元素,什么时候下一个元素会变得比这个元素更优(更优的时间指的是有几个“当前种类”的元素的时候下一个元素会更优(“下一个”是序列上更靠前的));新加入一个元素的时候,如果栈顶的下一个元素优于栈顶的时间早于栈顶优于新加入元素的时间,就把栈顶弹出(因为当栈顶优于当前元素的时候,栈顶下一个元素优于栈顶,所以栈顶下一个元素也优于当前元素;如果要弹掉当前元素,说明当前元素之后的元素优于当前元素,即栈顶优于当前元素,那么栈顶下一个元素此时也优于栈顶元素,所以会顺便把栈顶弹出,那么不如此时就把栈顶弹出,就能使得栈里相邻元素的“下一个优于上一个”的时间是单调递减的,就不会出现 i < j < k 且 i 优于 k 且 j 劣于 k 的情况了);加入之后如果栈顶下一个元素优于栈顶的时间在当前时间之前,就弹栈顶。
#include<cstdio> #include<cstring> #include<algorithm> #include<vector> #define ll long long #define tp sta[a[i]] using namespace std; int rdn() { int ret=0;bool fx=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();} while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar(); return fx?ret:-ret; } const int N=1e5+5,M=1e4+5; int n,a[N],s[N],ct[M];ll dp[N]; vector<int> sta[M]; ll cal(int p,int sm) { sm=sm-s[p]+1; return dp[p-1]+(ll)a[p]*sm*sm; } int cz(int u,int v) { int l=1,r=n,ret=n+1;//ret=n+1 while(l<=r) { int mid=l+r>>1; if(cal(u,mid)>=cal(v,mid))ret=mid,r=mid-1; else l=mid+1; } return ret; } int main() { n=rdn(); for(int i=1;i<=n;i++) { a[i]=rdn(); s[i]=++ct[a[i]];} for(int i=1;i<=n;i++) { while(tp.size()>=2&&cz(tp[tp.size()-2],tp[tp.size()-1])<=cz(tp[tp.size()-1],i)) tp.pop_back(); tp.push_back(i); while(tp.size()>=2&&cz(tp[tp.size()-2],tp[tp.size()-1])<=s[i]) tp.pop_back(); dp[i]=cal(tp[tp.size()-1],s[i]); } printf("%lld\n",dp[n]); return 0; }