题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6698
题意:现在有 n n n组数字,每组数字里面有两个数 a a a, b b b,你需要从这n组数中取 k ( 1 < = k < = 2 ∗ n ) k(1<=k<=2*n) k(1<=k<=2∗n)个数,如果你要从一组数中取数,你只有三种取法
- 直接取a
- 在取完a的情况下取b
- 同时取a和b
解题心得:
- 首先这里有个贪心的思维,如果我已经取了 k k k个数,现在我要取第 k + 1 k+1 k+1个数我怎么取最大?这里有两种策略,第一种是只取一个进来,从能够单独取的 a a a和 b b b中选一个最大的出来,即第一、二种取法,第二种策略就是将我已经取了的 k k k个数中选一个最小的,并且这个最小的数能够直接还回去(假设某组数的 a a a和 b b b你都要取了,这个时候你不能单独的直接将 a a a还回去),再取进来一个还未取过的最大的 a + b a+b a+b,即第三种取法。
- 第二个问题就是怎么维护相关的值,首先分析一下你需要维护的值,还没有取的数中能够直接单独取一个数的最大值,还没取的数中a+b的最大值,已经取了的数中能够还回去的最小值。如果再分细一点还要区分这个数是 a a a还是 b b b,维护的时候还要注意第二种取法造成的相互影响情况。
- 我是直接用的线段树维护,将所有需要的状态都维护出来,维护了五种状态,能够取 a a a的最大值,能够取 b b b的最大值,能够取 a + b a+b a+b的最大值,能够还回去的 a a a的最小值,能够还回去的 b b b的最小值,将这个五个值分别记为: M a x A MaxA MaxA, M a x B MaxB MaxB, M a x A B MaxAB MaxAB, M i n A MinA MinA, M i n B MinB MinB。当我取了 M a x A MaxA MaxA的时候影响的值有( M a x A MaxA MaxA, M i n A MinA MinA, M a x B MaxB MaxB, M a x A B ) MaxAB) MaxAB),取了 M a x B MaxB MaxB的时候影响的值有( M a x B MaxB MaxB, M i n B MinB MinB, M i n A MinA MinA),取了 M a x A B MaxAB MaxAB的时候影响的有( M a x A B MaxAB MaxAB, M i n B MinB MinB, M a x A MaxA MaxA),当我还回去了 M i n A MinA MinA是影响的有( M i n A MinA MinA, M a x A B MaxAB MaxAB, M a x A MaxA MaxA, M a x B MaxB MaxB),当我还回去了 M i n B MinB MinB的时候影响的有( M a x B MaxB MaxB, M i n B MinB MinB, M i n A MinA MinA)。整理清楚之后直接写线段树维护就行了。
- 线段树维护看起来很复杂的样子,但是我用 s e t set set和优先队列维护永远都是TLE,没办法了啊,直接上万能的线段树。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 1e6+100;
const ll INF = 1e18;
struct Node2 {
ll a, b;
}p[maxn];
struct Node{
ll MaxA, MaxB, MaxAB, MinA, MinB;//Max部分是能够直接取的,Min部分是能够直接还回去的
}node[maxn];
ll n, t, ans[maxn];
void update(ll root) {
ll chl = root<<1, chr = root<<1|1;
node[root].MinB = min(node[chl].MinB, node[chr].MinB);
node[root].MinA = min(node[chl].MinA, node[chr].MinA);
node[root].MaxB = max(node[chl].MaxB, node[chr].MaxB);
node[root].MaxAB = max(node[chl].MaxAB, node[chr].MaxAB);
node[root].MaxA = max(node[chl].MaxA, node[chr].MaxA);
}
void build_tree(ll root, ll l,ll r) {
if(l == r) {
node[root].MaxA = p[l].a;
node[root].MaxAB = p[l].a + p[l].b;
node[root].MaxB = 0;
node[root].MinA = INF;
node[root].MinB = INF;
return ;
}
ll mid = l + r >> 1;
build_tree(root<<1, l, mid);
build_tree(root<<1|1, mid+1, r);
update(root);
}
void init() {
scanf("%lld", &n);
for(ll i=1;i<=n;i++) {
scanf("%lld%lld", &p[i].a, &p[i].b);
}
build_tree(1, 1, n);
}
void changeMaxA(ll root, ll l, ll r, ll va) {
if(l == r) {
node[root].MaxA = 0;
node[root].MaxB = p[l].b;
node[root].MaxAB = 0;
node[root].MinA = p[l].a;
return ;
}
ll mid = l + r >> 1;
ll chl = root<<1, chr = root<<1|1;
if(node[chl].MaxA == node[root].MaxA) {
changeMaxA(chl, l, mid, va);
} else {
changeMaxA(chr, mid+1, r, va);
}
update(root);
}
void changeMaxB(ll root, ll l, ll r, ll va) {
if(l == r) {
node[root].MaxB = 0;
node[root].MinB = p[l].b;
node[root].MinA = INF;
return ;
}
ll mid = l + r >> 1;
ll chl = root<<1, chr = root<<1|1;
if(node[chl].MaxB == node[root].MaxB) {
changeMaxB(chl, l, mid, va);
} else {
changeMaxB(chr, mid+1, r, va);
}
update(root);
}
void changeMinA(ll root, ll l, ll r, ll va) {
if(l == r) {
node[root].MinA = INF;
node[root].MaxAB = p[l].a + p[l].b;
node[root].MaxA = p[l].a;
node[root].MaxB = 0;
return ;
}
ll chl = root<<1, chr = root<<1|1;
ll mid = l + r >> 1;
if(node[chl].MinA == node[root].MinA) {
changeMinA(chl, l, mid, va);
} else {
changeMinA(chr, mid+1, r, va);
}
update(root);
}
void changeMinB(ll root, ll l, ll r,ll va) {
if(l == r) {
node[root].MinB = INF;
node[root].MinA = p[l].a;
node[root].MaxB = p[l].b;
return ;
}
ll mid = l + r >> 1;
ll chl = root<<1, chr = root<<1|1;
if(node[chl].MinB == node[root].MinB) {
changeMinB(chl, l, mid, va);
} else {
changeMinB(chr, mid+1, r, va);
}
update(root);
}
void changeMaxAB(ll root, ll l, ll r) {
if(l == r) {
node[root].MaxAB = 0;
node[root].MinB = p[l].b;
node[root].MaxA = 0;
return ;
}
ll mid = l + r >> 1;
ll chl = root<<1, chr = root<<1|1;
if(node[chl].MaxAB == node[root].MaxAB) {
changeMaxAB(chl, l, mid);
} else {
changeMaxAB(chr, mid+1, r);
}
update(root);
}
void solve() {
ll sum = 0;
for(ll i=1;i<=2*n;i++) {
ll sum1, sum2;
sum1 = sum2 = sum;
ll va1 = node[1].MaxA;
ll va2 = node[1].MaxB;
sum1 += max(va1, va2);
ll va3 = min(node[1].MinA, node[1].MinB);
sum2 = sum2 - va3 + node[1].MaxAB;
if(sum1 >= sum2) {
if(sum1 == sum + va1) {
changeMaxA(1, 1, n, va1);
} else {
changeMaxB(1, 1, n, va2);
}
} else {
if(va3 == node[1].MinA) {
changeMinA(1, 1, n, va3);
} else {
changeMinB(1, 1, n, va3);
}
changeMaxAB(1, 1, n);
}
sum = max(sum1, sum2);
ans[i] = sum;
}
}
int main() {
// freopen("1.in.txt", "r", stdin);
scanf("%lld", &t);
while(t--) {
init();
solve();
for(ll i=1;i<=2*n;i++) {
printf("%lld%c", ans[i], i == 2*n ? '\n': ' ');
}
}
return 0;
}
本文详细解析了HDU 6698题目,采用线段树策略解决从多组数字中选择特定数量数值以最大化总和的问题。讨论了贪心策略下三种取数方式,以及如何通过线段树维护五种关键状态,包括最大值和最小值,确保高效更新与查询。
&spm=1001.2101.3001.5002&articleId=100740979&d=1&t=3&u=8fbb1dcc3b7446de855cd477d7f67368)
627

被折叠的 条评论
为什么被折叠?



