Codeforces gym101981 (2018 icpc 南京站) B.Tournament

一条直线上有N个村庄,要在这条直线上选K个地方建雕像,使得每个村庄到离其最近的雕像的距离的和最小。输出最小的和。(范围1e5)

首先考虑一堆村庄建一个雕像,则最优方案一定是放在中间的村庄(偶数的话中间两个都可以)

那么考虑最优方案一定被包含于,所有的把整个序列划分成k段,并在每段中挑选中间位置建雕像的方案。

可以dp,dp到某个位置i建了j个雕像的最小代价。

转移dp(i,j) = min { dp(x - 1,j - 1)+ cost(x,i)}

cost是i,j这一段在中间建雕像的代价,可O(1)查询;该转移方程表示当前位置为右端点,枚举所有该段左端点的选取。

发现复杂度n^3...

又可以发现,这个东西满足wqs二分性(凸性)。简单点说,就是假设当前这n个村庄,让你建k个雕像的代价是res(k),则有res(k + 1)- res(k) >= res(k + 2) - res(k + 1).

也就是说,让你多建一个雕像,能让答案变小的贡献其实是单调不增的。

(不严谨的)证明应该很简单,一种建立k个雕像的方案,考虑把k个雕像依次建立,如果后面建立的雕像使代价的减小量能多于之前建立的雕像,则显然可以调换它,让它在之前建立得到大于等于现在减少代价量的贡献,故建立雕像时一定可以做到上述凸性。

这种性质的问题,可以二分一个额外贡献val,在选取一个雕像时要额外花费val的代价,则由凸性,val的增加会导致选的雕像会越来越少,而对于一个val,转移方程变成了:

dp(i) = min { dp(x - 1)+ cost(x, i))+ val},同时维护选的雕像数(作为总代价的第二关键字)

根据此时最优方案选择的雕像数量调整二分值,直到二分到一个val,此时最优方案选择的最小雕像数小于等于k(而val+1对应的就大于k),则以val为额外代价dp,dp出的答案减去 val* k就是实际答案。(最小雕像数可能不是恰好k,是因为一个val值对应的最优方案选取的雕像数其实是一段区间)

这样就用logn的时间减少了一维dp,复杂度优化到了n^2logn

继续考虑确定了一个val下的dp如何优化:

发现当i递增时,dp(i)的最优决策点(多个,选最靠后的)是单调不减的,但不一定是连续的增长,故不能直接暴力右移决策位置.

考虑维护一个存放决策点队列,里面存的是决策点位置,以及该位置为决策点管辖的区间。

也就是说,队列里一个元素记为(ps,l,r),表示根据当前已知的所有dp值,之后访问到的(l,r)区间内元素决策位置都会是ps。

显然根据决策点随i递增而单调不减的性质,这个队列实际上是单调队列,即队列中元素ps递增。

那么考虑枚举到i,要对队列如何操作:

1.弹队首,直到队首元素的管辖区间包含i,选取队首元素的ps作为i的决策点即可算出dp(i)。

2.把i作为决策点插入队列。因为最优决策点单调不减性,则对于队尾元素,如果对于队尾元素管辖区间的左端点,选取i都比选取队尾元素的ps优,则直接弹掉队尾元素。重复弹队尾元素,直到:

    1.队列为空,直接扔进i决策点,管辖后面所有位置即可。

    2.剩下的队尾元素,满足在队尾元素管辖的左端点还是选队尾元素更优,则二分队尾元素管辖的l,r区间,找到队尾元素管辖范围与i管辖范围的分界点,然后修改队尾元素右端点,并插入决策i。

此时对于一个val的dp复杂度就是nlogn了

故总复杂度nlog^2n

#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)
using namespace std;
const int N = 3e5 + 100;

int n, k, T;

int a[N];
ll sum[N];

struct node{
	int lp, rp, x;
	node(){};
	node(int lp, int rp, int x):lp(lp), rp(rp), x(x){};
};

ll dp[N], f[N];
deque<node> q;

int sol(ll val)
{
	auto w = [&](int l, int r)
	{
		int mid = l + r >> 1;
		return 1LL * a[mid] * (mid + mid - l - r + 1) + (sum[r] + sum[l - 1] - sum[mid] - sum[mid]);
	};
	auto calc = [&](int x, int i)
	{
		return dp[x] + w(x + 1, i) + val;
	};
	auto cmp = [&](int x1, int x2, int i)
	{
		ll w1 = calc(x1, i), w2 = calc(x2, i);
		if(w1 < w2) return 1;
		if(w1 == w2 && f[x1] < f[x2]) return 1;
		return 0; 
	};
	auto find = [&](int x1, int x2, int l, int r)
	{
		int mid, res = r + 1;
		while(l <= r)
		{
			mid = l + r >> 1;
			if(cmp(x1, x2, mid)){
				res = mid;
				r = mid - 1;
			}
			else l = mid + 1;
		}
		return res;
	};
	while(!q.empty())q.pop_back();
	dp[0] = 0, f[0] = 0;
	q.push_back(node(1, n, 0));
	node nw;
	for(int i = 1, x, ps; i <= n; i++)
	{
		while(!q.empty() && q.front().rp < i)q.pop_front();
		x = q.front().x;
		dp[i] = calc(x, i);
		f[i] = f[x] + 1;
		while(!q.empty() && cmp(i, q.back().x, q.back().lp))q.pop_back();
		if(q.empty()){
			q.push_back(node(i + 1, n, i));
		}
		else{
			ps = find(i, q.back().x, q.back().lp, q.back().rp);
			q.back().rp = ps - 1;
			if(ps <= n)q.push_back(node(ps, n, i));
		}
	}
	return f[n];
}

signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> k;
	for(int i = 1; i <= n; i++)
		cin >> a[i], sum[i] = sum[i - 1] + a[i];
	ll l = 0, r = sum[n], mid, res, ans;
	int num;
	while(l <= r)
	{
		mid = l + r >> 1;
		num = sol(mid);
		if(num <= k) res = mid, r = mid - 1;
		else l = mid + 1;
	}
	sol(res);
	ans = dp[n] - res * k;
	cout << ans << '\n';
	return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值