TOJ 1611 Moo University - Financial Aid -- 线段树 + DP

题目链接:http://acm.tzc.edu.cn/acmhome/problemdetail.do?&method=showdetail&id=1611

题目大意:给定c个二元组(x, y),要求从中选出n(n为奇数)个,满足这n个二元组的y之和不超过给定的f,而且x的中位数最大。输出最大的中位数。

分析:先将二元组(记为cow)按X排序,然后枚举每个X作为中位数。这时需要在X之前和X之后各选n / 2个数,因为已经按X排序,不管怎么选都可以满足X是中位数的条件,所以只需要各自选取Y最小的n / 2个数,判断它们的和是否满足不超过f这个条件。如果满足,X可行,否则不行。如果按X从大到小枚举X,那么遇到的第一个可行X即为答案。剩下的问题在于如何高效地求出最小的n / 2个数的和。

可以用动态规划解决这个问题。用一个长为n / 2的数组a记录以cow[i].X为中位数时前面最小的n / 2个Y,同时用dp[i]表示这些数中的最大值的下标,那么计算dp[i + 1]时,如果cow[i + 1].Y >= a[dp[i]],则dp[i + 1] = dp[i]。否则用cow[i + 1].Y替换a[dp[i]],同时计算出新的最大值下标也就是dp[i + 1]。这涉及单点更新及全区间的最大值查询,用线段树可以实现。至于求和,在计算dp[i]的时候顺便就可以求出来了。对于cow[i].X之后的数,再进行一次同样的操作。下面的代码中的dp保存的就是n / 2个数的和,因为最大值下标可以直接从线段树中读出,不需要另外存储。求出dp之后再进行一次线性扫描就可以求出答案了。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define mp make_pair
#define X first
#define Y second
#define MEMSET(a, b) memset(a, b, sizeof(a))
using namespace std;

typedef unsigned int ui;
typedef long long ll;
typedef unsigned long long ull;
typedef pair pii;
typedef vector vi;
typedef vi::iterator vi_it;
typedef map mii;
typedef priority_queue pqi;
typedef priority_queue, greater > rpqi;
typedef priority_queue pqp;
typedef priority_queue, greater > rpqp;

const int MAX_C = 100000 + 2;
const int MAX_N = 10000 + 2;
pii cow[MAX_C];
int a[MAX_N];
int dp[2][MAX_C];
int cnt;

struct
{
	int left;
	int right;
	int max_index;
	
	inline int mid() {
		return (left + right) >> 1;
	}
} st[MAX_N * 4];

void build(int l, int r, int idx)
{
	st[idx].left = l;
	st[idx].right = r;
	if (l == r) st[idx].max_index = cnt++;
	else {
		int mid = st[idx].mid(), lc = idx << 1, rc = lc | 1;
		build(l, mid, lc);
		build(mid + 1, r, rc);
		int lm = st[lc].max_index, rm = st[rc].max_index;
		st[idx].max_index = a[lm] > a[rm] ? lm : rm;
	}
}

void update(int pos, int idx)
{
	if (st[idx].left != st[idx].right) {
		int mid = st[idx].mid(), lc = idx << 1, rc = lc | 1;
		if (pos <= mid) update(pos, lc);
		else update(pos, rc);
		int lm = st[lc].max_index, rm = st[rc].max_index;
		st[idx].max_index = a[lm] > a[rm] ? lm : rm;
	}
}

int main(int argc, char *argv[])
{
//	freopen("D:\\in.txt", "r", stdin);
	int n, c, f, i;
	cin >> n >> c >> f;
	for (i = 0; i < c; ++i) scanf("%d%d", &cow[i].X, &cow[i].Y);
	
	//先处理n = 1时的特殊情况 
	if (n == 1) {
		int ans = -1;
		for (int j = 0; j < c; ++j) if (cow[j].Y <= f) ans = max(ans, cow[j].X);
		cout << ans << endl;
		return 0;
	}
	
	sort(cow, cow + c);
	
	//计算dp[0] 
	for (i = 0; i < (n >> 1); ++i) dp[0][n >> 1] += (a[i] = cow[i].Y);
	cnt = 0;
	build(0, (n >> 1) - 1, 1);
	for (; i < c - (n >> 1); ++i) {
		int mi = st[1].max_index;
		if (a[mi] > cow[i].Y) {
			dp[0][i + 1] = dp[0][i] - a[mi] + cow[i].Y;
			a[mi] = cow[i].Y;
			update(mi, 1);
		}
		else dp[0][i + 1] = dp[0][i];
	}
	
	//计算dp[1] 
	for (i = 0; i < (n >> 1); ++i) dp[1][c - (n >> 1) - 1] += (a[i] = cow[c - 1 - i].Y);
	cnt = 0;
	build(0, (n >> 1) - 1, 1);
	for (i = c - (n >> 1) - 1; i > (n >> 1); --i) {
		int mi = st[1].max_index;
		if (a[mi] > cow[i].Y) {
			dp[1][i - 1] = dp[1][i] - a[mi] + cow[i].Y;
			a[mi] = cow[i].Y;
			update(mi, 1);
		}
		else dp[1][i - 1] = dp[1][i];
	}
	
	for (i = c - (n >> 1) - 1; i >= (n >> 1); --i) if (dp[0][i] + dp[1][i] + cow[i].Y <= f) break;
	if (i >= (n >> 1)) cout << cow[i].X << endl;
	else cout << -1 << endl;
	return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章