[bzoj4345][POI2016]Korale_堆_贪心_线段树_dfs

bzoj4345 POI2016 Korale

题目链接https://lydsy.com/JudgeOnline/problem.php?id=4345

数据范围:略。



题解

由于$k$的范围问题,我们很容易想到优先队列。

至于从每个状态怎么往下一个转移就是这个题的精髓。

我们先考虑第一问:

第一问没有字典序的限制,我们把所有的数按照从小到大排序。

堆里维护二元组$(Sum, id)$表示这种选取方式的和位$Sum$,最大下标为$id$。

它可以转移到$(Sum - a_{id} + a_{id+1}, id+1)$和$(Sum + a_{id + 1}, id + 1)$。

这一想是显然的,但是不咋好想...有点超级钢琴的味道。

下面我们考虑第二问:

第二问我们爆搜即可,想求出来当前下标(不排序)到最后一个数这个区间内,小于当前剩余和的最小下标的数是啥,然后暴力搜下去即可。

这个过程可以用线段树维护。

至于复杂度为什么是对的?因为我们每时每刻都保证了所有的枚举和都是小于第一问的值的,即使枚举到了第一问的值也在接受范围内。

言外之意我们枚举的每一个值,都是前$k-1$中的一个。

代码

#include <bits/stdc++.h>

#define ls p << 1 

#define rs p << 1 | 1 

#define N 1000010 

using namespace std;

typedef long long ll;

char *p1, *p2, buf[100000];

#define nc() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1 ++ )

int rd() {
	int x = 0;
	char c = nc();
	while (c < 48) {
		c = nc();
	}
	while (c > 47) {
		x = (((x << 2) + x) << 1) + (c ^ 48), c = nc();
	}
	return x;
}

priority_queue <pair<ll, int> > q;

int a[N], b[N], Same, mn[N << 2];

ll ans[N];

inline void pushup(int p) {
	mn[p] = min(mn[ls], mn[rs]);
}

void build(int l, int r, int p) {
	if (l == r) {
		mn[p] = b[l];
		return;
	}
	int mid = (l + r) >> 1;
	build(l, mid, ls), build(mid + 1, r, rs);
	pushup(p);
}

int query(int x, ll y, int l, int r, int p) {
	if (x <= l) {
		if (mn[p] > y) {
			return 0;
		}
		if (l == r) {
			return l;
		}
	}
	int mid = (l + r) >> 1;
	if (x <= mid) {
		int mdl = query(x, y, l, mid, ls);
		if (mdl) {
			return mdl;
		}
	}
	return query(x, y, mid + 1, r, rs);
}

int top, st[N], n, k;

void dfs(int p, ll re) {
	if (!Same) {
		return;
	}
	if (!re) {
		Same -- ;
		if (!Same) {
			for (int i = 1; i <= top; i ++ ) {
				printf("%d ", st[i]);
			}
			puts("");
		}
		return;
	}
	for (int i = p + 1; i <= n; i ++ ) {
		i = query(i, re, 1, n, 1);
		if (i) {
			st[ ++ top] = i;
			dfs(i, re - b[i]);
			top -- ;
		}
		else {
			break;
		}
	}
}

int main() {
	n = rd(), k = rd() - 1;
	for (int i = 1; i <= n; i ++ ) {
		a[i] = b[i] = rd();
	}
	sort(a + 1, a + n + 1);
	q.push(make_pair(-a[1], 1));
	for (int i = 1; i <= k; i ++ ) {
		ans[i] = -q.top().first;
		int x = q.top().second;
		q.pop();
		if (x < n) {
			q.push(make_pair(-(ans[i] - a[x] + a[x + 1]), x + 1));
			q.push(make_pair(-(ans[i] + a[x + 1]), x + 1));
		}
	}
	// for (int i = 1; i <= k; i ++ ) {
	// 	printf("%lld ", ans[i]);
	// }
	// puts("");
	cout << ans[k] << endl ;
	for (int i = k; i; i -- ) {
		if (ans[i] != ans[k]) {
			break;
		}
		Same ++ ;
	}
	// cout << Same << endl ;
	build(1, n, 1);
	dfs(0, ans[k]);
	return 0;
}

原文地址:https://www.cnblogs.com/ShuraK/p/11795638.html

时间: 2024-11-08 23:47:57

[bzoj4345][POI2016]Korale_堆_贪心_线段树_dfs的相关文章

B20J_2733_[HNOI2012]永无乡_权值线段树合并

Description:n座岛,编号从1到n,每座岛都有自己的独一无二的重要度,按照重要度可以将这n座岛排名,名次用1到 n来表示.某些岛之间由巨大的桥连接,通过桥可以从一个岛到达另一个岛.现在有两种操作:B x y表示在岛 x与岛y之间修建一座新桥.Q x k表示询问当前与岛 x连通的所有岛中第k重要的是哪座岛,即所有与岛 x连通的岛中重要度排名第 k小的岛是哪座,请你输出那个岛的编号. 对于100%的数据n≤100000,m≤n,q≤300000. 分析:读懂题后发现是一道线段树合并的裸题.

BZOJ_3685_普通van Emde Boas树_权值线段树

Description 设计数据结构支持: 1 x  若x不存在,插入x 2 x  若x存在,删除x 3    输出当前最小值,若不存在输出-1 4    输出当前最大值,若不存在输出-1 5 x  输出x的前驱,若不存在输出-1 6 x  输出x的后继,若不存在输出-1 7 x  若x存在,输出1,否则输出-1 Input 第一行给出n,m 表示出现数的范围和操作个数 接下来m行给出操作 n<=10^6,m<=2*10^6,0<=x<n Output Sample Input 1

UVA 11134 - Fabled Rooks(贪心 / 二分图 + 线段树优化连边)

题目地址:Fabled Rooks 题目大意:n * n 的棋盘上摆了 n <=10^5 个车,让他们两两不攻击,每个车必须摆在一个给定矩形里,给出一个解决方案? 1. 贪心 由于行列互不影响, 所以可以分两遍求.第一遍确定每个车的行数,第二遍确定列数. 以行为例,若从左到右扫描,则按照区间的右端点升序排序,因为如果扫到一个位置两枚棋子都可以放,则选择右端点较小的那个(右端点大的后面还有机会). 2. 二分图匹配 有个毒瘤老师把题目改成了这样:n * n 的棋盘上摆了 n <=10^5 个车,

BZOJ 1852:[MexicoOI06]最长不下降序列(贪心+DP+线段树+离散化)

[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1852 [题目大意] 给你N对数A1,B1……An,Bn.要求你从中找出最多的对, 把它们按照一种方式排列,重新标号1,2,..,k.能满足对于每一对i<j,都有Ai>Bj. [题解] 对于排序的问题,如果i必须要在j前面, 那么有A[i]>B[j],且B[i]>=A[j],相加得A[i]+B[i]>A[j]+B[j], 因此按A+B从大到小排序后最优, 我们先将A

[bzoj4027][HEOI2015]兔子与樱花_贪心_树形dp

兔子与樱花 bzoj-4027 HEOI-2015 题目大意:每个点有c[i]朵樱花,有一个称重m, son[i]+c[i]<=m.如果删除一个节点,这个节点的樱花或移动到它的祖先中深度最大的,且没有被删除的节点,求在满足所有点界限的情况下,最多能删除的节点数. 注释:$1\le n\le 2\cdot 10^6$,$1\le m\le 10^5$,$0\le c_i\le 1000$. 想法:开始的时候很容易想到贪心,但是这东西对不对还两说 其实仔细一想这玩意儿tm显然啊??! 我们令c[i]

[bzoj2097][Usaco2010 Dec]Exercise 奶牛健美操_贪心_树形dp_二分

Exercise bzoj-2097 Usaco-2010 Dec 题目大意:题目链接 注释:略. 想法:题目描述生怕你不知道这题在考二分. 关键是怎么验证?我们想到贪心的删边. 这样的策略是显然正确的. 之后树形dp的时候维护一下就行. 最后,附上丑陋的代码... ... #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #define N 10001

hdu5338 ZZX and Permutations(贪心、线段树)

转载请注明出处: http://www.cnblogs.com/fraud/          ——by fraud ZZX and Permutations Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)Total Submission(s): 181    Accepted Submission(s): 38 Problem Description ZZX likes

hdu 2852 KiKi&#39;s K-Number (线段树)

hdu 2852 题意: 一个容器,三种操作: (1) 加入一个数 e (2) 删除一个数 e,如果不存在则输出 No Elment! (3) 查询比a大的数中的第k小数,不存在就输出 Not Find! 解法: 关于第三点,可以先查询小于等于a的数的个数cnt,然后直接查询第cnt+k小数就行了 . 二分+树状数组 或者 主席树(有点杀鸡用牛刀的感觉 ...) 也是可以做的  _(:з」∠)_ code:线段树 1 #include <iostream> 2 #include <cst

线段树 - ZYB&#39;s Premutation

ZYB has a premutation P,but he only remeber the reverse log of each prefix of the premutation,now he ask you to restore the premutation. Pair (i, j)(i < j) is considered as a reverse log if Ai > Aj is matched.Input In the first line there is the num