「笔记」反悔贪心

到现在才学我是不是废了

很多思想都是看的题解,在此感谢各位写题解的神仙/qq

这个题单不错 link

反悔贪心的核心思想是:当前的这次决策不一定最优,但我们先拿过来放着,如果以后找到更优的在把它换掉。这样的话只需要保证当前最优就可以了,如果以后更优我们会反悔。而这个过程通常用堆实现。

有几种比较常见的模型:

  • 枚举次数限制元素
  • 枚举所有元素限制次数

题型分类标准:

  • 一种元素只有一单位单方向贡献但被时间限制(+ 选择个数限制)

  • 一种元素有多单位贡献(+ 选择个数限制)

  • 一种元素有多方向贡献(+ 选择个数限制)

大概做完下面的题后会有更细致的感受?


模板题目大体一般是这个样子:

你有 n 个物品需要修理,修理一个物品需要 1 单位时间,修理每个物品可获得 vi 的价值,每个物品要在第 ti 的时间前修理完成,否则报废。问最优价值。

套路的解法是

对所有物品按照时间排序。对于每个物品,如果有时间修就修,如果没时间了,就从已经修过的物品中挑一个价值最小的,如果当前的物品更优,就把这个价值最小的去掉换上这个新的。

对于价值最小的用一个小根堆来维护即可。

时间复杂度 O(nlogn)


下面这四个题都和上面一样套路。

P2949,P4053,P2107,P3545

CF865D Buy Low Sell High

这个题的转化挺妙的。

从前向后遍历,把每个值都压进小根堆里。

在这之前,如果这个值比小根堆里的最小值大,那么 ans += x - q.top() ,表明我要在那个最小值处买入,在这里卖出。然后在 q.push(x)

为什么还要在放进去一个 x 呢,如果后面遇到一个更优的 y,我不在 x 处卖了,我在 y 处卖,那此时的贡献应该为 y-x

/*
Work by: Suzt_ilymtics
Problem: 不知名屑题
Knowledge: 垃圾算法
Time: O(能过)
*/
#include<bits/stdc++.h>
#define int long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 1e5+5;
const int INF = 1e9+7;
const int mod = 1e9+7;

int n, ans = 0;
priority_queue<int, vector<int>, greater<int> > q;

int read() {
    int s = 0, f = 0;
    char ch = getchar();
    while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
    while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0', ch = getchar();
    return f ? -s : s;
}

signed main()
{
	n = read();
	for(int i = 1; i <= n; ++i) {
		int x = read();
		if(!q.empty() && q.top() < x) ans += x - q.top(), q.pop(), q.push(x);
		q.push(x);
	}
	printf("%lld\n", ans);
    return 0;
}

P1484,P1792,P3620,SP1553

这四个是四倍经验,题面略有不同,都是同一套路。

简述题意:

一个长度为 n 的序列 a,选 k 个数,不能相邻,求和最大是多少。

不难发现,当 k=1 时,我们会取最大的 ai

k=2 时,有两种可能:

  • 另取一个与 ai 不相邻的 aj
  • ai1ai+1

我们可以发现,如果 k=1 时选了 ai,那么对于 ai1ai+1 ,它们要么同时被选要么同时不选。

那考虑选了 ai1ai+1 的贡献,这个时候已经不能选 ai 了,因此贡献为 ai1+ai+1ai

因此当 ai 被选时,我们可以删去 ai1ai+1 ,并把 ai 改成 ai1+ai+1ai,重新找最大值。

维护当前序列的最大值可以用优先队列,维护当前序列的状态可以用双向链表。

时间复杂度为 O(klogn)

CF436E Cardboard Box

这个就比前面的复杂多了。

关键在于一次操作可以多单位的进行。

这里有两种做法

One:权衡

考虑不进行反悔贪心,直接权衡选一个两星关卡优还是两个一星关卡优。

开两个堆维护 aibi 的最小值。

qa 堆的最小值为 aminqb 堆的最小值为 bmin,可以假设他们不是同一个 i,其实这个无所谓(

直接比较 aminbmin 是不对的,因为 amin 所对应的 b 值可能比 amin 大不了多少。

所以我们要取出 qa 堆的次小值 amin2

  • amin+amin2bmin 时:

选两个一星,然后把其对应的编号标记,如果在 qb 堆中遇到就要跳过去。

bmin 在放回去。

如果其中某个位置 i 是第一次选,再把 biai 放进 qa ,表示增加 biai 的代价可以多得到 1 个星星,与普通星星无异。

  • amin+amin2>bmin 时:

直接取一个 2 星,将 aminamin2 重新入队。并在 qa 中删除 bmin 中所对应的点。

几个细节

  • 如果 w 是奇数则在最开始向 qa 中塞一个编号为 0 权值为 0 的点,并把 0 号点标记为使用过一次,以免再次入堆。
  • w=2×n 时进行特判全选 b
  • 如果队中无元素 amin (或 bmin )初值要付极大值。
  • 取某个值之前先把已标记的在堆顶的点删除。
/*
Work by: Suzt_ilymtics
Problem: 不知名屑题
Knowledge: 垃圾算法
Time: O(能过)
*/
#include<bits/stdc++.h>
#define int long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 3e5+5;
const int INF = 2e9+7;
const int mod = 1e9+7;

struct node {
    int val, id;
    bool operator < (const node &b) const { return val > b.val; }
};

int n, K, ans = 0;
int a[MAXN], b[MAXN];
int cnt[MAXN];
priority_queue<node> qa, qb;
bool visa[MAXN], visb[MAXN];

int read() {
    int s = 0, f = 0;
    char ch = getchar();
    while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
    while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0', ch = getchar();
    return f ? -s : s;
}

signed main()
{
    n = read(), K = read();
    for(int i = 1; i <= n; ++i) a[i] = read(), b[i] = read(), qa.push((node){a[i], i}), qb.push((node){b[i], i});
    if(K == 2 * n) {
        for(int i = 1; i <= n; ++i) ans += b[i];
        printf("%lld\n", ans);
        for(int i = 1; i <= n; ++i) printf("2");
        return puts(""), 0;
    }
    if(K & 1) ++ K, qa.push((node){0, 0}), cnt[0] = 1;
    for(int i = 1; i <= K; i += 2) {
        node a1, a2, b1;
        while(!qa.empty() && visa[qa.top().id]) qa.pop();
        !qa.empty() ? a1 = qa.top(), qa.pop(), 0 : a1.val = INF, 0;
        while(!qa.empty() && visa[qa.top().id]) qa.pop();
        !qa.empty() ? a2 = qa.top(), qa.pop(), 0 : a2.val = INF, 0;
        while(!qb.empty() && visb[qb.top().id]) qb.pop();
        !qb.empty() ? b1 = qb.top(), qb.pop(), 0 : b1.val = INF, 0;
        if(a1.val + a2.val <= b1.val) {
            visb[a1.id] = visb[a2.id] = true;
            cnt[a1.id] ++, cnt[a2.id] ++;
            if(cnt[a1.id] == 1) qa.push((node){b[a1.id] - a[a1.id], a1.id});
            if(cnt[a2.id] == 1) qa.push((node){b[a2.id] - a[a2.id], a2.id});
            qb.push(b1);
            ans += a1.val + a2.val;
        } else {
            visa[b1.id] = true;
            cnt[b1.id] += 2;
            qa.push(a1), qa.push(a2);
            ans += b1.val;
        }
    }
    printf("%lld\n", ans);
    for(int i = 1; i <= n; ++i) printf("%lld", cnt[i]);
    puts("");
    return 0;
}

Two:反悔

如果要用反悔贪心,最牛逼的地方就在于转化成一次操作只多得一个星。

一共分四种情况讨论。

如果直接贪有两种情况:

  • 把一个零星变成一星,花费 ai
  • 把一个一星变成两星,花费 biai

反悔也有两种情况:

  • 把一个一星变成零星,然后选一个二星,花费 bjai
  • 把一个二星变成一星,然后选一个二星,花费 bj+aibi

因此为了维护到所有情况我们需要维护五个堆

分别是 aibiaiaibiaibi

分别代表了零星到一星,一星到二星,一星到零星,零星到二星,二星到一星的代价。

每次选择的时候,选四种情况的最小值即可。

上述两种做法复杂度均为 O(nlogn) ,不同做法和实现所用常熟有较大略微差别

/*
Work by: Suzt_ilymtics
Problem: 不知名屑题
Knowledge: 垃圾算法
Time: O(能过)
*/
#include<bits/stdc++.h>
#define int long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 3e5+5;
const int INF = 1e9+7;
const int mod = 1e9+7;
const int op1 = 0, op2 = 1, op3 = 1, op4 = 0, op5 = 2;

struct node {
    int val, id;
    bool operator < (const node &b) const { return val > b.val; }
};

int n, K, ans = 0;
int a[MAXN], b[MAXN], cnt[MAXN];
priority_queue<node> q1, q2, q3, q4, q5;

int read() {
    int s = 0, f = 0;
    char ch = getchar();
    while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
    while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0', ch = getchar();
    return f ? -s : s;
}

signed main()
{
    n = read(), K = read();
    for(int i = 1; i <= n; ++i) a[i] = read(), b[i] = read();
    for(int i = 1; i <= n; ++i) q1.push((node){a[i], i}), q4.push((node){b[i], i});
    for(int i = 1; i <= K; ++i) {
        int Min = INF, opt = false, p, q;
        while(!q1.empty() && cnt[q1.top().id] != op1) q1.pop();
        if(!q1.empty() && q1.top().val < Min) {
            Min = q1.top().val;
            p = q1.top().id;
            opt = 1;
        }
        while(!q2.empty() && cnt[q2.top().id] != op2) q2.pop();
        if(!q2.empty() && q2.top().val < Min) {
            Min = q2.top().val;
            p = q2.top().id;
            opt = 2;
        }
        while(!q3.empty() && cnt[q3.top().id] != op3) q3.pop();
        while(!q4.empty() && cnt[q4.top().id] != op4) q4.pop();
        if(!q3.empty() && !q4.empty() && q3.top().val + q4.top().val < Min) {
            Min = q3.top().val + q4.top().val;
            p = q3.top().id, q = q4.top().id;
            opt = 3;
        }
        while(!q4.empty() && cnt[q4.top().id] != op4) q4.pop();
        while(!q5.empty() && cnt[q5.top().id] != op5) q5.pop();
        if(!q4.empty() && !q5.empty() && q4.top().val + q5.top().val < Min) {
            Min = q4.top().val + q5.top().val;
            q = q4.top().id, p = q5.top().id;
            opt = 4;
        }
        ans += Min;
        if(opt == 1) {
            cnt[p] ++;
            q2.push((node){b[p] - a[p], p});
            q3.push((node){-a[p], p});
        } else if(opt == 2) {
            cnt[p] ++;
            q5.push((node){a[p] - b[p], p});
        } else if(opt == 3) {
            cnt[p] -- , cnt[q] += 2;
            q1.push((node){a[p], p});
            q5.push((node){a[q] - b[q], q});
        } else if(opt == 4) {
            cnt[p] --, cnt[q] += 2;
            q2.push((node){b[p] - a[p], p});
            q3.push((node){- a[p], p});
            q5.push((node){a[q] - b[q], q});
        } 
    }
    printf("%lld\n", ans);
    for(int i = 1; i <= n; ++i) printf("%lld", cnt[i]);
    return 0;
}

CF730I Olympiad in Programming and Sports

  • 一种元素有多方向贡献(+ 选择个数限制)

做法好像还蛮多的说?

费用流?DP?贪心也有很多方向。

这里只介绍一种(只学了一种

考虑先把编程团队都填满,选 p 个最大的 ai

然后再选 s 个人加入到体育团队中。

显然有两种选择:

  • 直接从未选的人中选一个加进去,贡献为 bi
  • 从已加入编程团队的人中选一个加入到体育团队中,在从未选的人中选一个加入编程团队中,贡献为 aj+biai

因此我们需要维护三个堆,分别维护 ai,,bibiai

分别代表一个人加入编程的贡献,加入体育的贡献,从编程转到体育的贡献。

复杂度为 O(nlogn)

/*
Work by: Suzt_ilymtics
Problem: 不知名屑题
Knowledge: 垃圾算法
Time: O(能过)
*/
#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define LL long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 1e5+5;
const int INF = 1e9+7;
const int mod = 1e9+7;

struct node {
    int val, id;
    bool operator < (const node &b) const { return val > b.val; }
    bool operator > (const node &b) const { return val < b.val; }
}a[MAXN], b[MAXN];
bool cmp(node x, node y) { return x.id < y.id; }

int n, P, S, ans = 0;
priority_queue<node, vector<node>, greater<node> > q1, q2, q3;
int vis[MAXN];

int read() {
    int s = 0, f = 0;
    char ch = getchar();
    while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
    while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0', ch = getchar();
    return f ? -s : s;
}

int main()
{  
    n = read(), P = read(), S = read();
    for(int i = 1; i <= n; ++i) a[i].val = read(), a[i].id = i;
    for(int i = 1; i <= n; ++i) b[i].val = read(), b[i].id = i;
    sort(a + 1, a + n + 1);
    for(int i = 1; i <= P; ++i) vis[a[i].id] = true, ans += a[i].val;
    sort(a + 1, a + n + 1, cmp);
    for(int i = 1; i <= n; ++i) {
        q1.push((node){a[i].val, i});
        q2.push((node){b[i].val, i});
        q3.push((node){b[i].val - a[i].val, i});
    }
    for(int i = 1; i <= S; ++i) {
        int Max = 0, opt = 0, p, q;
        while(!q2.empty() && vis[q2.top().id] != 0) q2.pop();
        if(!q2.empty() && q2.top().val > Max) {
            Max = q2.top().val;
            opt = 1, p = q2.top().id; 
        }
        while(!q1.empty() && vis[q1.top().id] != 0) q1.pop();
        while(!q3.empty() && vis[q3.top().id] != 1) q3.pop();
        if(!q1.empty() && !q3.empty() && q1.top().val + q3.top().val > Max) {
            Max = q1.top().val + q3.top().val;
            opt = 2, p = q1.top().id, q = q3.top().id;
        }
        ans += Max;
        if(opt == 1) {
            q2.pop(), vis[p] = 2;
        } else {
            q1.pop(), q3.pop();
            vis[p] = 1, vis[q] = 2;
            q3.push((node){b[p].val - a[p].val, p});
        }
    }
    printf("%d\n", ans);
    for(int i = 1; i <= n; ++i) if(vis[i] == 1) printf("%d ", i); puts("");
    for(int i = 1; i <= n; ++i) if(vis[i] == 2) printf("%d ", i); puts("");
    return 0;
}

AT2672 [AGC018C] Coins

这个题的每个元素有三个方向可以选择,所幸的是还有另外一个限制条件 n=X+Y+Z

否则可能会更麻烦?

一开始我考虑的是先选最大的 X 个金币,在按照上面的套路选贪 Y 个银币,在按照上面的套路贪 Z 个铜币。

但这样考虑会漏掉很多情况,例如在贪铜币的过程中,对于某个人,可以选其金币,让金币中的某个人选银币,让银币中的某个人选铜币。


为了用上 n=X+Y+Z 的条件,就有了这个做法。

(a,b,c) 三元组替换为 (e=ba,f=ca) 二元组。

即默认所有人拿金币,然后在 n 个人中选 Y 个拿银币,选 Z 个拿铜币。

剩下的做法就和上一个题一样啦。


另一种做法是:先随便分配。

然后考虑每次变换去调整。

一共有五种情况:

xyzx

xzyx

xyx

xzx

yzy

开六个堆维护即可。

感觉蛮麻烦的,我也就没有写

时间复杂度都是 O(nlogn) ,当然有常数上的差距。

/*
Work by: Suzt_ilymtics
Problem: 不知名屑题
Knowledge: 垃圾算法
Time: O(能过)
*/
#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define int long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 1e5+5;
const int INF = 1e12+7;
const int mod = 1e9+7;

struct node {
    int val, id;
    bool operator < (const node &b) const { return val < b.val; }
}a[MAXN], b[MAXN], c[MAXN];

int n, X, Y, Z, ans = 0;
int vis[MAXN];
priority_queue<node> q1, q2, q3; 

int read() {
    int s = 0, f = 0;
    char ch = getchar();
    while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
    while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0', ch = getchar();
    return f ? -s : s;
}

bool cmp1(node x, node y) { return x.val > y.val; }
bool cmp2(node x, node y) { return x.id < y.id; }

signed main()
{
    X = read(), Y = read(), Z = read();
    n = X + Y + Z;
    for(int i = 1; i <= n; ++i) {
        a[i].val = read(), b[i].val = read(), c[i].val = read();
        a[i].id = b[i].id = c[i].id = i;
        ans += a[i].val;
    }
    for(int i = 1; i <= n; ++i) {
        int x = b[i].val;
        b[i].val = c[i].val - a[i].val;
        a[i].val = x - a[i].val;
    }
    sort(a + 1, a + n + 1, cmp1);
    for(int i = 1; i <= Y; ++i) vis[a[i].id] = 1, ans += a[i].val;
    sort(a + 1, a + n + 1, cmp2);
    for(int i = 1; i <= n; ++i) {
        q1.push((node){a[i].val, i});
        q2.push((node){b[i].val, i});
        q3.push((node){b[i].val - a[i].val, i});
    }
    for(int i = 1; i <= Z; ++i) {
        int Max = -INF, opt, p, q;
        while(!q2.empty() && vis[q2.top().id] != 0) q2.pop();
        if(!q2.empty() && q2.top().val > Max) Max = q2.top().val, p = q2.top().id, opt = 1;
        while(!q1.empty() && vis[q1.top().id] != 0) q1.pop();
        while(!q3.empty() && vis[q3.top().id] != 1) q3.pop();
        if(!q1.empty() && !q3.empty() && q1.top().val + q3.top().val > Max) {
            Max = q1.top().val + q3.top().val;
            p = q1.top().id, q = q3.top().id, opt = 2;
        }
        ans += Max;
        if(opt == 1) {
            vis[p] = 2, q2.pop();
        } else {
            vis[p] = 1, vis[q] = 2, q1.pop(), q3.pop();
            q3.push((node){b[p].val - a[p].val, p});
        }
    }
    printf("%lld\n", ans);
    return 0;
}

CF802N April Fools' Problem (medium)

题解区的做法只有费用流。不过费用流的本质就是反悔的思想。

n 个中转点,i 连向 i+1 流量为 INF 花费为 0

对于每个 ai,源点向 i 连边,流量为 1 花费为 ai

对于每个 biiT 连边,流量为 1 花费为 bi

怎么控制只选 k 个点?在另设一个汇点 G ,然后 TG 连一条流量为 k 花费为 0 的点。

时间复杂度为 O(nmf),但费用流一般跑不满,因此能卡过去。

CF802O April Fools' Problem (hard)

这道题是前面那道题的加强版。

显然这个数据范围费用流就跑不过去了。

由于最小费用最大流的每次增广值单调不减,从而随着 k 的增大,f(k)f(k1) 单调不减,即 y=f(x) 下凸。所以可以考虑 wqs 二分。

我们二分一个斜率 s ,表示切线的斜率。

然后我们采用和CF865D这个题类似的套路。

将匹配 (ax,bi) 换成 (ax,bj) 时所产生的贡献是:

(ax+bjs)(ax+bis)=bjbi

(ax,bj) 配对所产生的贡献是

bjs+ax

因此每次我们把 ai 扔进堆里,维护一个大根堆。

每次取出堆顶的值 v,如果 bisv<0 就相当于完成一次匹配或者一次替换,加上它的贡献然后将 bis 入堆。堆内的每个值都要标记一下它是已匹配的还是未匹配的。

如果堆内已匹配的数量 =k ,直接输出记录的答案 ans+k×s 即可。

否则调整边界 l,r

时间复杂度 O(nlognlogV)

另外这篇题解link用了最短路优化费用流,复杂度是吊打上面算法的 O(klogn)

/*
Work by: Suzt_ilymtics
Problem: 不知名屑题
Knowledge: 垃圾算法
Time: O(能过)
*/
#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define int long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 5e5+5;
const int INF = 1e9+7;
const int mod = 1e9+7;

struct node {
    int val, type;
    bool operator < (const node &b) const { return val < b.val; }
};

int n, K, sum, cnt;
int a[MAXN], b[MAXN];
priority_queue<node> q;

int read() {
    int s = 0, f = 0;
    char ch = getchar();
    while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
    while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0', ch = getchar();
    return f ? -s : s;
}

signed main()
{
    n = read(), K = read();
    for(int i = 1; i <= n; ++i) a[i] = read();
    for(int i = 1; i <= n; ++i) b[i] = read();
    int l = 0, r = 2e9, mid;
    while(l <= r) {
//        cout << l << " " << mid << " " << r << "\n";
        mid = l + r >> 1, sum = cnt = 0;
        for(int i = 1; i <= n; ++i) {
            q.push((node){- a[i], 0});
            int tmp = b[i] - mid - q.top().val;
            if(tmp < 0) sum += tmp, q.pop(), q.push((node){b[i] - mid, 1});
        }
        while(!q.empty()) cnt += q.top().type, q.pop();
        if(cnt == K) return printf("%lld\n", sum + K * mid), 0;
        if(cnt < K) l = mid + 1;
        else r = mid - 1;
    }
    return 0;
}

P5470 [NOI2019] 序列

djy 神仙的题解讲的太明白了%%%

如果 l 个公共部分已经确定了,那么直接选 kl 个最大的就是最优的。

那么我们先选 k 个最大的,然后通过反悔贪心,每一步让公共部分多一个,直到变成 l 个,这样就是最优的。

考虑有哪几种操作能让公共部分多一个。

  • 找一个选了的 ai,和一个选了的 bj,然后用 bi 代替 bj,代价是 bibj
  • 找一个选了的 bi,和一个选了的 aj,然后用 ai 代替 aj,代价是 aiaj
  • 找一个选了的 aibj,再找一个没选的位置 p,选 apbp 代替 aibj,代价是 ap+bpaibj
  • 找一个选了的 aibj,再找一个都选的位置 p,选 biaj 代替 apbp,代价是 bi+ajapbp

拆一下上面的式子,用六个堆分别维护 aiaibibiai+biaibi

时间复杂度 O(nlogn)

/*
Work by: Suzt_ilymtics
Problem: 不知名屑题
Knowledge: 垃圾算法
Time: O(能过)
*/
#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define int long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 2e5+5;
const int INF = 1e9+7;
const int mod = 1e9+7;

struct node {
    int val, id;
    bool operator < (const node &b) const { return val < b.val; }
}a[MAXN], b[MAXN];

int T, n, K, L, ans = 0;
int vis[MAXN];
priority_queue<node> q1, q2, q3, q4, q5, q6;

int read() {
    int s = 0, f = 0;
    char ch = getchar();
    while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
    while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0', ch = getchar();
    return f ? -s : s;
}

bool cmp1(node x, node y) { return x.val > y.val; }
bool cmp2(node x, node y) { return x.id < y.id; }

void Clear() {
    while(!q1.empty()) q1.pop();
    while(!q2.empty()) q2.pop();
    while(!q3.empty()) q3.pop();
    while(!q4.empty()) q4.pop();
    while(!q5.empty()) q5.pop();
    while(!q6.empty()) q6.pop();
    memset(vis, false, sizeof vis);
    ans = 0;
}

signed main()
{
    T = read();
    while(T--) {
        Clear();
        n = read(), K = read(), L = read();
        for(int i = 1; i <= n; ++i) a[i].val = read(), a[i].id = i;
        for(int i = 1; i <= n; ++i) b[i].val = read(), b[i].id = i;
        sort(a + 1, a + n + 1, cmp1), sort(b + 1, b + n + 1, cmp1);
        for(int i = 1; i <= K; ++i) {
            ans += a[i].val + b[i].val;
            vis[a[i].id] ++, vis[b[i].id] += 2;
        }
        for(int i = 1; i <= n; ++i) L -= (vis[i] == 3); 
        sort(a + 1, a + n + 1, cmp2), sort(b + 1, b + n + 1, cmp2);
        for(int i = 1; i <= n; ++i) {
            q1.push((node){a[i].val, i});
            q2.push((node){-a[i].val, i});
            q3.push((node){b[i].val, i});
            q4.push((node){-b[i].val, i});
            q5.push((node){a[i].val + b[i].val, i});
            q6.push((node){- a[i].val - b[i].val, i});
        }
        while(L-- > 0) {
            int Max = -INF, i, j, k, opt = 0;
            while(!q3.empty() && vis[q3.top().id] != 1) q3.pop();
            while(!q4.empty() && vis[q4.top().id] != 2) q4.pop();
            if(!q3.empty() && !q4.empty()) {
                int res = q3.top().val + q4.top().val;
                if(res > Max) {
                    Max = res;
                    i = q3.top().id, j = q4.top().id;
                    opt = 1;
                }
            }
            while(!q1.empty() && vis[q1.top().id] != 2) q1.pop();
            while(!q2.empty() && vis[q2.top().id] != 1) q2.pop();
            if(!q1.empty() && !q2.empty()) {
                int res = q1.top().val + q2.top().val;
                if(res > Max) {
                    Max = res;
                    i = q1.top().id, j = q2.top().id;
                    opt = 2;
                }
            }
            while(!q5.empty() && vis[q5.top().id] != 0) q5.pop();
            while(!q2.empty() && vis[q2.top().id] != 1) q2.pop();
            while(!q4.empty() && vis[q4.top().id] != 2) q4.pop();
            if(!q5.empty() && !q2.empty() && !q4.empty()) {
                int res = q5.top().val + q2.top().val + q4.top().val;
                if(res > Max) {
                    Max = res;
                    i = q5.top().id, j = q2.top().id, k = q4.top().id;
                    opt = 3;
                }
            }
            while(!q6.empty() && vis[q6.top().id] != 3) q6.pop();
            while(!q1.empty() && vis[q1.top().id] != 2) q1.pop();
            while(!q3.empty() && vis[q3.top().id] != 1) q3.pop();
            if(!q6.empty() && !q1.empty() && !q3.empty()) {
                int res = q6.top().val + q1.top().val + q3.top().val;
                if(res > Max) {
                    Max = res;
                    i = q6.top().id, j = q1.top().id, k = q3.top().id;
                    opt = 4;
                }
            }
            ans += Max;
            if(opt == 1) {
                vis[i] += 2, vis[j] -= 2;
                q3.pop(), q4.pop();
                q5.push((node){a[j].val + b[j].val, j});
                q6.push((node){- a[i].val - b[i].val, i});
            } else if(opt == 2) {
                vis[i] += 1, vis[j] -= 1;
                q1.pop(), q2.pop();
                q5.push((node){a[j].val + b[j].val, j});
                q6.push((node){- a[i].val - b[i].val, i});
            } else if(opt == 3) {
                vis[i] += 3, vis[j] -= 1, vis[k] -= 2;
                q5.pop(), q2.pop(), q4.pop();
                q6.push((node){- a[i].val - b[i].val, i});
                q5.push((node){a[j].val + b[j].val, j});
                q5.push((node){a[k].val + b[k].val, k});
            } else {
                vis[i] -= 3, vis[j] += 1, vis[k] += 2;
                q5.push((node){a[i].val + b[i].val, i});
                q6.push((node){- a[j].val - b[j].val, j});
                q6.push((node){- a[k].val - b[k].val, k});
            }
        }
        printf("%lld\n", ans);
    }
    return 0;
}

CF280D k-Maximum Subsequence Sum

这个题的代码真 tm 恶心/tuu

这个题的思路和种树挺像的。

考虑 k=1 的情况,肯定选 [l,r] 中的最大子段和 [l1,r1]

考虑 k=2 的情况,一种情况是选 [l,l1) 或者 (r1,r] 中的最大子段和,另一种情况是减去选 [l1,r1] 中的最小子段和,即把原来的 [l1,r1] 分成两段。

因此如果再选了 [l1,r1] 后将这一段区间取反,然后再查询最大子段和,求的最大子段和如果包括这些被取反的数,就相当于撤销了这些方案。那么这样的得到的一定是最优解。

对于每个询问直接操作 k 次即可。如果某次的最大字段和 0,可以直接退出。

时间复杂度 O(mklogn)

/*
Work by: Suzt_ilymtics
Problem: 不知名屑题
Knowledge: 垃圾算法
Time: O(能过)
*/
#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define LL long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 1e5+5;
const int INF = 1e9+7;
const int mod = 1e9+7;

int n, m, ans, sc;
int a[MAXN];

int read() {
    int s = 0, f = 0;
    char ch = getchar();
    while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
    while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0', ch = getchar();
    return f ? -s : s;
}

namespace Seg {
    #define lson i << 1
    #define rson i << 1 | 1
    struct Tree {
        int l, r, sum, maxv, maxl, maxr, minv, minl, minr, lmax, lmaxid, lmin, lminid, rmax, rmaxid, rmin, rminid;
        bool rev;
//        Tree () { l = r = sum = maxv = maxl = maxr = minv = minl = minr = lmax = lmaxid = lmin = lminid = rmax = rmaxid = rmin = rminid = rev = 0; }
        Tree operator + (const Tree &b) const {
            Tree res;
            res.l = this->l, res.r = b.r;
            res.sum = this->sum + b.sum;
            if(this->lmax > this->sum + b.lmax) res.lmax = this->lmax, res.lmaxid = this->lmaxid;
            else res.lmax = this->sum + b.lmax, res.lmaxid = b.lmaxid;
            if(this->lmin < this->sum + b.lmin) res.lmin = this->lmin, res.lminid = this->lminid;
            else res.lmin = this->sum + b.lmin, res.lminid = b.lminid;
            
            if(this->rmax + b.sum < b.rmax) res.rmax = b.rmax, res.rmaxid = b.rmaxid;
            else res.rmax = this->rmax + b.sum, res.rmaxid = this->rmaxid;
            if(this->rmin + b.sum > b.rmin) res.rmin = b.rmin, res.rminid = b.rminid;
            else res.rmin = this->rmin + b.sum, res.rminid = this->rminid;
            
            res.maxv = this->rmax + b.lmax, res.maxl = this->rmaxid, res.maxr = b.lmaxid;
            if(res.maxv < this->maxv) res.maxv = this->maxv, res.maxl = this->maxl, res.maxr = this->maxr;
            if(res.maxv < b.maxv) res.maxv = b.maxv, res.maxl = b.maxl, res.maxr = b.maxr;
            
            res.minv = this->rmin + b.lmin, res.minl = this->rminid, res.minr = b.lminid;
            if(res.minv > this->minv) res.minv = this->minv, res.minl = this->minl, res.minr = this->minr;
            if(res.minv > b.minv) res.minv = b.minv, res.minl = b.minl, res.minr = b.minr;
            res.rev = false;
            return res;
        }
    }tree[MAXN << 2], tmp[50];
    void Push_up(int i) { tree[i] = tree[lson] + tree[rson]; }
    void Rev(int i) {
        tree[i].rev ^= 1;
        tree[i].sum = - tree[i].sum;
        swap(tree[i].lmax, tree[i].lmin), swap(tree[i].lmaxid, tree[i].lminid);
        tree[i].lmax = - tree[i].lmax, tree[i].lmin = - tree[i].lmin;
        swap(tree[i].rmax, tree[i].rmin), swap(tree[i].rmaxid, tree[i].rminid);
        tree[i].rmax = - tree[i].rmax, tree[i].rmin = - tree[i].rmin;
        swap(tree[i].maxv, tree[i].minv), swap(tree[i].maxl, tree[i].minl), swap(tree[i].maxr, tree[i].minr);
        tree[i].maxv = - tree[i].maxv, tree[i].minv = - tree[i].minv;
    }
    void Build(int i, int l, int r) {
        tree[i].l = l, tree[i].r = r;
        if(l == r) {
            tree[i].sum = tree[i].maxv = tree[i].minv = tree[i].lmax = tree[i].lmin = tree[i].rmax = tree[i].rmin = a[l];
            tree[i].l = tree[i].r = tree[i].maxl = tree[i].maxr = tree[i].minl = tree[i].minr = tree[i].lmaxid = tree[i].lminid = tree[i].rmaxid = tree[i].rminid = l;
            tree[i].rev = false;
            return ;
        }
        int mid = (l + r) >> 1;
        Build(lson, l, mid), Build(rson, mid + 1, r);
        Push_up(i);
    }
    void Push_down(int i) {
        if(!tree[i].rev) return ;
        Rev(lson), Rev(rson);
        tree[i].rev = false;
    }
    void Reverse(int i, int l, int r, int L, int R) {
        if(L <= l && r <= R) return (void)(Rev(i));
        Push_down(i);
        int mid = (l + r) >> 1;
        if(mid >= L) Reverse(lson, l, mid, L, R);
        if(mid < R) Reverse(rson, mid + 1, r, L, R);
        Push_up(i);
    }
    void Modify(int i, int l, int r, int pos, int val) {
        if(l == r) return (void)(tree[i].sum = tree[i].maxv = tree[i].minv = tree[i].lmax = tree[i].lmin = tree[i].rmax = tree[i].rmin = val);
        Push_down(i);
        int mid = (l + r) >> 1;
        if(mid >= pos) Modify(lson, l, mid, pos, val);
        else Modify(rson, mid + 1, r, pos, val);
        Push_up(i);
    }
    Tree Query(int i, int l, int r, int L, int R) {
        if(L <= l && r <= R) return tree[i];
        Push_down(i);
        int mid = (l + r) >> 1, p = false; Tree ans;
        if(mid >= L) ans = Query(lson, l, mid, L, R), p = true;
        if(mid < R) {
            Tree res = Query(rson, mid + 1, r, L, R);
            ans = p ? ans + res : res;
        }
        return ans;
    }
}
using namespace Seg;

int main()
{
    n = read();
    for(int i = 1; i <= n; ++i) a[i] = read();
    Build(1, 1, n);
    m = read();
    for(int i = 1, opt, l, r, x, v, k; i <= m; ++i) {
        opt = read();
        if(!opt) {
            x = read(), v = read();
            Modify(1, 1, n, x, v);
        } else {
            l = read(), r = read(), k = read();
            ans = 0, sc = 0;
            for(int j = 1; j <= k; ++j) {
                tmp[++sc] = Query(1, 1, n, l, r);
                if(tmp[sc].maxv <= 0) {sc --; break;}
                ans += tmp[sc].maxv;
                Reverse(1, 1, n, tmp[sc].maxl, tmp[sc].maxr);
            }
            for(int j = 1; j <= sc; ++j) Reverse(1, 1, n, tmp[j].maxl, tmp[j].maxr);
            printf("%d\n", ans);
        }
    }
    return 0;
}
posted @   Suzt_ilymtics  阅读(256)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
历史上的今天:
2021-01-06 P6739 [BalticOI 2014 Day1] Three Friends 题解
点击右上角即可分享
微信分享提示