CF751E Phys Ed Online

Problem - E - Codeforces

一个非常妙的处理技巧,对每个点维护它前k+1个(包括自身)的最小值为多少,记作b数组,可以发现询问的就是

 然后可以根据询问的左端点%k为多少,把询问看作是对于一个%k的b数组,查询一段区间的前缀最小值的和。

可以从后到前维护dp(i),表示从i开始往后跳出整个数组的最小代价,有dp(i) = dp(nxt_i) + b[i] * (nxt_i - i),其中nxt是右边第一个小于b[i]元素位置。

显然这个dp数组很好求。

而询问就可以找到区间中第一个最小值位置ps,这个ps再跳一步就离开查询区间了,因此答案为dp(l) - dp(ps) + b[ps] * (r - ps + 1)

#include <bits/stdc++.h>
#define pii pair<int,int>
#define fi first
#define sc second
#define pb push_back
#define ll long long
#define trav(v, x) for(auto v:x)
#define VI vector<int>
#define VLL vector<ll>
//define double long double
#define all(x) (x).begin(),(x).end()
using namespace std;
const double eps = 1e-10;//1e-12
const int N = 3e5 + 100;
const ll mod = 998244353;//1e9 + 7;

struct gg{
	int l, r;
	int id;
	gg(){};
	gg(int l, int r, int id):l(l), r(r), id(id){};
};

int n, m, a[N], step, qq, lg[N], b[N];
vector<int>val[N];
vector<gg>qry[N];
int mn[N][20];
ll ans[N];

signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> qq >> step;
	multiset<int>st;
	queue<int>bin;
	for(int i = 1; i <= n; i++)
	{
		int x;
		cin >> x;
		b[i] = x;
		st.insert(x);
		bin.push(x);
		if(i > step + 1)
			st.erase(st.find(bin.front())), bin.pop();
		a[i] = (*st.begin());
	}
	for(int i = 1; i <= n; i++)
	{
		val[i % step].pb(a[i]);
	}
	for(int i = 1; i <= qq; i++)
	{
		int l, r, lp, rp;
		cin >> l >> r;
		ans[i] += b[l];
		l += step;
		if(l > r)
		{
			continue;
		}
		lp = (l - 1) / step;
		r = (r - l) / step * step + l;
		rp = (r - 1) / step;
		qry[l % step].pb(gg(lp, rp, i));
	}
	lg[1] = 0;
	for(int i = 2; i <= n; i++)
		lg[i] = lg[i >> 1] + 1;
	for(int rem = 0; rem < step; rem ++)
	{
		vector<int>&buk = val[rem];
		auto get = [&](int l, int r)
		{
			int k = lg[r - l + 1];
			r -= (1 << k), ++r;
			if(buk[mn[l][k]] <= buk[mn[r][k]])
				return mn[l][k];
			else
				return mn[r][k];
		};
		int sz = buk.size();
		buk.pb(-1);
		for(int i = 0; i <= sz; i++)
			mn[i][0] = i;
		for(int k = 1; k < 20; k++)
		{
			int len = (1 << k);
			for(int i = 0; i + len - 1 <= sz; i++)
			{
				int ni = i + (len >> 1);
				if(buk[mn[i][k - 1]] <= buk[mn[ni][k - 1]])
					mn[i][k] = mn[i][k - 1];
				else 
					mn[i][k] = mn[ni][k - 1];
			}
		}
		vector<ll> dp(sz + 1, 0);
		stack<int>stk;
		while(!stk.empty())stk.pop();
		dp[sz] = 0, stk.push(sz);
		for(int i = sz - 1; i >= 0; i--)
		{
			while(buk[stk.top()] >= buk[i])
				stk.pop();
			int nx = stk.top();
			dp[i] = dp[nx] + 1LL * buk[i] * (nx - i);
			stk.push(i);
		//	cerr << i << ' ' << buk[i] << ' ' << nx<< ' ' << dp[i] << '\n';
		}
		trav(v, qry[rem])
		{
			int l = v.l;
			int r = v.r;
			int id = v.id;
			int ps = get(l, r);
			ll res = dp[l] - dp[ps];
			res += 1LL * buk[ps] * (r - ps + 1);
			ans[id] += res;
		}
	}
	for(int i = 1; i <= qq; i++)
		cout << ans[i] << '\n';
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值