Heavy Stones 题解

深刻意识到自己完全不会做正经贪心题,哪怕是套路题。

QOJ2070 Heavy Stones

题意

给定 \(n\) 堆石子,每堆石子有 \(a_i\) 个石头。有一个区间 \([l,r]\) 初始 \(l=r=k\),你可以做 \(n-1\) 次操作,每次操作有两种选择:

  • \(l>1\),则可以让 \(l\to l-1\),花费 \(\sum_{i=l-1}^r a_i\) 的代价。
  • \(r<n\),则可以让 \(r\to r+1\),花费 \(\sum_{i=l}^{r+1} a_i\) 的代价。

显然最后 \(l=1,r=n\),你需要最小化总花费。
输出 \(k=1,2,...,n\) 的答案。

数据范围\(n\le 2\times 10^5,a_i\le 10^6\)

题解

一个基础的观察是,假设第 \(i\) 次合并进来的数是 \(a_i\)\(i\le n-1\)),那么他会对答案造成 \((n-i)a_i\) 的贡献。(\(a_k\) 总是造成 \((n-1)a_k\) 的贡献,我们在最后把他算上即可)

先考虑对于一个固定的 \(k\) 求答案,我们有两种本质相同的思路来得到此题的贪心策略:

  • 法一:注意到我们会轮流在 \(k\) 的左侧和右侧取一段,考虑 Exchange Argument,假设我在左侧取了一段长度为 \(x\) 的段 \(a_1,a_2,..,a_x\),在右侧取了一段长度为 \(y\) 的段 \(b_1,b_2,...,b_y\),考虑交换他们是否会更优,即 \(\sum (n-i)a_i+\sum (n-i-x)b_i\le \sum (n-i)b_i + \sum (n-i-y)a_i\),消去相同项并移项不难得到这个式子等价于 \(\frac{\sum a_i}{x} \le \frac{\sum b_i}{y}\),也就是说最优策略每一次取的一段的平均数单掉递增。
  • 法二:如果我们把这条链看成以 \(k\) 为根的树,那么等价于不断取树上的节点且要求父亲节点取完了才能取儿子节点,这是个经典问题,如果点 \(u\)\(fa_u\) 更优的话,取了 \(fa_u\) 之后一定会立刻取 \(u\),于是可以把 \(u,fa_u\) 合并成一个点。现在我们只需要定义什么时候 \(u\)\(v\) 优就可以了,类似分析同样可以得到是比较他们所包含的点的平均数。

我们现在已经得到了贪心策略:每次取一段,且段的平均数单掉递增。
由于 \(k\) 是分界点,我们考虑先预处理出每个前缀和后缀的最优划分方式,然后我们只需要把 \([1,k-1],[k+1,n]\) 的划分归并起来即可。以预处理前缀的最优划分为例,发现 \((l,r]\) 的平均数 \(\frac{\sum_{i=l+1}^r a_i}{r-l}\) 等价于平面上 \((l,s_l)\)\((r,s_r)\) 两点之间的斜率(\(s\) 是前缀和数组),于是我们相当于要求上凸壳(从后往前斜率递增等价于从前往后斜率递减),用单调栈维护一下即可。
根据单调栈维护凸壳的过程,我们还可以得到一个重要性质:总共出现过的线段是 \(O(n)\) 的!
于是我们可以先把这 \(O(n)\) 条线段排个序,然后建线段树维护答案。接着对 \(k\) 扫描线,当 \(k\to k+1\) 时,对于前缀凸包和后缀凸包我们都会删除并加入一些线段,由于每条线段只会被加入和删除 \(O(1)\) 次,所以我们直接在线段树上单点修改即可。

code

#include<bits/stdc++.h>
#define Debug puts("-------------------------")
#define LL long long 
using namespace std;
const int N=2e5+5;
int n,a[N],cnt;
LL s[N],pre[N],suf[N],ans[N];
LL sum(int l,int r){ return s[r]-s[l-1]; }
struct P{ 
	int l,r,op; 
	P () : l(0),r(0),op(0) {}
	P (int _l,int _r,int _op) : l(_l),r(_r),op(_op) {}
}line[N<<1];
bool operator < (const P &x,const P&y){
	if(sum(x.l,x.r)*(y.r-y.l+1)!=sum(y.l,y.r)*(x.r-x.l+1)) return sum(x.l,x.r)*(y.r-y.l+1)<sum(y.l,y.r)*(x.r-x.l+1);
	if(x.op!=y.op) return x.op<y.op;
	if(x.l!=y.l) return x.l<y.l;
	return x.r<y.r;
}
vector<P> add[2][N],del[2][N];
P calc(int l,int r,int op){ return (!op)?P(l,r,op):P(n-r+1,n-l+1,op); }
int Dis(P x){ return lower_bound(line+1,line+cnt+1,x)-line; } 
LL Val(P x){   
	int l=x.l,r=x.r;
	if(x.op==0) return pre[r]-pre[l-1]+sum(l,r)*(n-1-r); //op=1,Val[l,r]=a[r]*(n-1)+a[r-1]*(n-2)+...
	else return suf[l]-suf[r+1]+sum(l,r)*(l-2);  //op=2,Val[l,r]=a[l]*(n-1)+a[l+1]*(n-2)...
}
int st[N],top;
void init(int op){
	for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i];
	st[top=1]=0;
	for(int i=1;i<=n;i++){
		while(top>1&&sum(st[top]+1,i)*(st[top]-st[top-1])>=sum(st[top-1]+1,st[top])*(i-st[top])){
			del[op][i].push_back(calc(st[top-1]+1,st[top],op));
			top--;
		}
		add[op][i].push_back(calc(st[top]+1,i,op));
		line[++cnt]=calc(st[top]+1,i,op);
		st[++top]=i;
	}
}
struct SegmentTree{
	struct node{
		int l,r;
		LL res,cnt,sum;
	}t[N<<3];
	void pushup(int p){
		t[p].cnt=t[p<<1].cnt+t[p<<1|1].cnt;
		t[p].sum=t[p<<1].sum+t[p<<1|1].sum;
		t[p].res=t[p<<1].res+t[p<<1|1].res-t[p<<1].cnt*t[p<<1|1].sum;
	}
	void build(int p,int l,int r){
		t[p]={l,r,0,0,0};
		if(l==r) return;
		int mid=(l+r)>>1;
		build(p<<1,l,mid),build(p<<1|1,mid+1,r);
	}
	void modify(int p,int x,bool flag){
		if(t[p].l==t[p].r){
			if(!flag) t[p].res=t[p].cnt=t[p].sum=0;
			else t[p].res=Val(line[x]),t[p].cnt=line[x].r-line[x].l+1,t[p].sum=sum(line[x].l,line[x].r);
			return;
		}
		int mid=(t[p].l+t[p].r)>>1;
		if(x<=mid) modify(p<<1,x,flag);
		else modify(p<<1|1,x,flag);
		pushup(p);
	}
}Seg; 
void Init(){
	for(int i=1;i<=n;i++) pre[i]=pre[i-1]+1ll*a[i]*i;
	for(int i=n;i>=1;i--) suf[i]=suf[i+1]+1ll*a[i]*(n-i+1);
	init(0);
	reverse(a+1,a+n+1);
	init(1);
	reverse(a+1,a+n+1);
	for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i];
	sort(line+1,line+cnt+1);
	Seg.build(1,1,cnt);
}
void work(){
	for(int _=n;_>=2;_--){
		int i=n-_+1;
		for(P x:del[1][i]) Seg.modify(1,Dis(x),0);
		for(P x:add[1][i]) Seg.modify(1,Dis(x),1);
	}
	for(int k=1;k<=n;k++){
		ans[k]=Seg.t[1].res+1ll*(n-1)*a[k];
		if(k==n) break;
		for(P x:del[0][k]) Seg.modify(1,Dis(x),0);
		for(P x:add[0][k]) Seg.modify(1,Dis(x),1);
		for(P x:add[1][n-k]) Seg.modify(1,Dis(x),0);
		for(P x:del[1][n-k]) Seg.modify(1,Dis(x),1);
	}
}
signed main(){
	freopen("interval.in","r",stdin);
	freopen("interval.out","w",stdout);
	double beg=clock();
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	Init();
	work();
	for(int i=1;i<=n;i++) printf("%lld ",ans[i]);
	puts("");
	cerr << "Time: " << (clock()-beg) << endl;
	return 0;
}
/*
input 1:
5
2 1 3 5 4
output 1:
35 35 36 43 49

input 2:
10
18 37 81 6 58 99 87 34 75 9
output 2:
2637 2637 2657 2657 2695 2949 2995 2905 2880 2880
*/
posted @ 2026-01-28 09:15  Green&White  阅读(0)  评论(0)    收藏  举报