【香蕉OI】生与死的境界【codeforces 878 E】numbers on the blackboard(贪心)

模拟赛T3放贪心???反正我想不出来。

题意

有一个序列,每次可以找相邻的两个数(假设排在前面的是 x x x,后面的是 y y y),把他们合并成一个权值为 x + 2 y x+2y x+2y的数,直到最后只剩最后一个数。

现在给出序列,有多组询问,每次问一个子序列,用上述方法合并,可以得到的最大的数。

思路

首先来发现一些性质。

  1. 对于一个子序列,他合并后得到的那个数一定可以写成 a n s = ∑ a i ∗ 2 k i ans=\sum a_i*2^{k_i} ans=ai2ki的形式,也就是给每个数乘上了一个2的倍数。
  2. 对于每一个 a i a_i ai k i k_i ki,假如 a i a_i ai是子序列的开头,那么 k i = 0 k_i=0 ki=0,否则 1 ≤ k i ≤ k i − 1 + 1 1 \le k_i \le k_{i-1}+1 1kiki1+1。可以证明只要满足这个条件,总有一个合并方案能够得到这样一个答案。这里就显然一波假装证掉了

已经可以贪心了。做法是:对于每个不是开头的数,不是 k i = 1 k_i=1 ki=1就是 k i = k i − 1 + 1 k_i=k_i-1+1 ki=ki1+1也挺显然的,假如不是这两种情况的话,一定可以通过变成这两种情况中的一种得到更优的答案。

好像并没有贪到可做的地步。那再进一步,考虑单次询问 O ( n ) O(n) O(n)的做法。

我们定义一个“块”表示一段子序列,其权值为 a 0 ∗ 2 0 + a 1 ∗ 2 1 + . . . a k ∗ 2 k a_0*2^0+a_1*2^1+...a_k*2^k a020+a121+...ak2k

从前往后把子序列中的数加入一个栈,假如栈顶大于0,就把栈顶和压在他下面的那个合并,直到没东西可以合并或者栈顶为负。合并的含义就是将两个“块”合并成一个“块”。容易证明权值为正的时候合并上去肯定比不合并要优。

最后得到的栈,除了栈底的块权值有可能为正,其他均为负。这时候为了使负的块的 k i k_i ki尽量小,我们从前往后合并,那么除了第一块之外,其他块都要 ∗ 2 *2 2。这就得到了答案。

发现只要栈还在,我们就可以用二分或者并查集之类的东西方便地处理以当前最后一个数为右端点的询问。那离线下来就可以过了此题。

注意

因为不停 ∗ 2 *2 2,中间权值可能很大,存不下。但是可以发现虽然正数很大,但是负数却不会很小,最小只有 − 1 0 9 -10^9 109,因为一旦负了就不会再合并了。

所以一旦某个块的权值大于 1 0 9 10^9 109,后面无论怎么合并都不可能变成小于 1 0 9 10^9 109,只要把他赋值成 1 0 9 + 1 10^9+1 109+1,表示无穷大就好了。

代码

莫名其妙的长。。。可能我对此题的理解不够深刻???

#include<bits/stdc++.h>
using namespace std;
#define int long long
const long long inf = 1e9;
const int N = 5e5 + 10, mod = 1e9 + 7;
int n, m, a[N], po[N], ipo[N], pre[N];
vector<int> q[N], id[N];
int rval[N], val[N], sum[N], cnt[N], beg[N], top;
int ans[N];

template<class T>inline void read(T &x){
	x = 0; bool fl = 0; char c = getchar();
	while (!isdigit(c)){if (c == '-') fl = 1; c = getchar();}
	while (isdigit(c)){x = (x<<3)+(x<<1)+c-'0'; c = getchar();}
	if (fl) x = -x;
}

template<class T> inline T fpow(T x, T y, T p){
	T r = 1;
	while (y){
		if (y&1) r = 1LL*r*x%p;
		x = 1LL*x*x%p;
		y >>= 1;
	}
	return r;
}

void init()
{
	po[0] = 1;
	for (int i = 1; i <= n; ++ i)
		po[i] = 2*po[i-1]%mod;
	ipo[0] = 1; ipo[1] = fpow(2ll, mod-2, mod);
	for (int i = 2; i <= n; ++ i)
		ipo[i] = ipo[i-1]*ipo[1]%mod;
	for (int i = 1, t = 2; i <= n; ++ i, t = 2*t%mod)
		pre[i] = (pre[i-1]+a[i]*t%mod+mod)%mod;
}

void push(int x, int y)
{
	val[++top] = x;
	rval[top] = (x+mod)%mod;
	sum[top] = (sum[top-1]+rval[top])%mod;
	cnt[top] = 1;
	beg[top] = y;
}

bool pop()
{
	if (top <= 1) return false;
	if (val[top] < 0) return false;
	int newval;
	if (val[top] == 0) newval = val[top-1];
	else if (cnt[top-1] > 31) newval = inf+1;
	else if (po[cnt[top-1]]*val[top]+val[top-1] > inf) newval = inf+1;
	else newval = po[cnt[top-1]]*val[top]+val[top-1];
	val[top-1] = newval;
	rval[top-1] = (rval[top-1]+po[cnt[top-1]]*rval[top]%mod)%mod;
	sum[top-1] = (sum[top-2]+rval[top-1])%mod;
	cnt[top-1] += cnt[top];
	top--;
	return true;
}

int solve_solve(int x)
{
	int l = 1, r = top, mid, now;
	while (l <= r){
		mid = l+r>>1;
		if (beg[mid] <= x)
			now = mid, l = mid+1;
		else
			r = mid-1;
	} 
	return (2*(sum[top]-sum[now]+mod)+ipo[x]*(pre[beg[now]+cnt[now]-1]-pre[x-1]+mod)%mod)%mod;
}

void solve()
{
	top = 0;
	for (int i = 1; i <= n; ++ i){
		push(a[i], i);
		while (pop());
		for (int j = 0, sz = q[i].size(); j < sz; ++ j)
			ans[id[i][j]] = solve_solve(q[i][j]);
	}
}

signed main()
{
	read(n); read(m);
	for (int i = 1; i <= n; ++ i) read(a[i]);
	for (int i = 1; i <= m; ++ i){
		int x, y;
		read(x); read(y);
		q[y].push_back(x);
		id[y].push_back(i);
	}
	init();
	solve();
	for (int i = 1; i <= m; ++ i)
		printf("%lld\n", ans[i]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值