闲话 22.10.22

闲话

突然发现我手头的图似乎好少的样子
???怎么都有动态壁纸
感觉生活水准有待提高(

在大巴上念叨美影日记没有pv
然后就看到美影日记加长到3:16然后出了pv
美影的裙子好看!
但是确实更伤感了点

美影日记单曲专
好耶是加长了的美影日记!
好耶是饭的版本!
欸等等为什么饭版是演唱会切片啊
azazazaz

srds 切出了第三心脏的片
瑞瓶 第三心脏可以说是演唱会里表现效果最好的歌之一了
不切出第三心脏还能切哪首歌啊啊啊

[今天推的是美影日记!]

可反悔贪心

我突然发现我没写过这样的题
所以去做了几道题

可反悔贪心,顾名思义,是可以反悔的贪心。我们知道,正常的贪心就是排个序,记个答案,顺着扫一遍,就这么一条道走到黑。然而这样做的正确性基于局部最优定能导出全局最优。
当局部最优很可能使得决策误入歧途时应当怎么做呢?我们应当在发现异常的第一时刻调整当前答案,以期得到最优解。这时就需要用到反悔。

总而言之,反悔操作是当发现该步操作无法导出全局最优解时,回退一步(或消除无法导出全局最优解的操作的影响),以在更优的决策路线上导出答案。
按照反悔方式的不同可以分为两种。

  1. 反悔堆
    通过一些快速取得当前状态最优解的数据结构维护对最优情况的大量访问。这使得 \(O(1)\) 查询+快速修改的堆成为了首选。
    使用堆维护当前所有决策即可。
  2. 反悔自动机
    通常采用差值抵消的方式,使得反悔时能迅速删除劣解。
    每次选择最接近全局最优解的方案。如果需要反悔就会自动调整使得产生正确的反悔策略。这也是其被称为自动机的原因。

下面结合例题。

[USACO09OPEN]Work Scheduling G

\(n\) 项工作,每项工作有截止时间和利润,进行工作花费单位时间。

考虑按截止时间排序。

维护一个按利润排序的小根堆,维护当前已选任务集合。
每次考虑如果当前任务的截止时间小于堆大小就直接加入,反之删除当前决策对象中最小利润元素。

还是比较简单的。

code
#include <bits/stdc++.h>
using namespace std;
#ifdef ONLINE_JUDGE
    char buf[1<<21], *p1 = buf, *p2 = buf;  inline char getc() { return (p1 == p2 and (p2 = (p1 = buf) + fread(buf, 1, 1<<21, stdin), p1 == p2) ? EOF : *p1++); }
    #define getchar getc
#endif
template <typename T> inline void get(T & x){
	x = 0; char ch = getchar(); bool f = 0; while (ch < '0' or ch > '9') f = f or ch == '-',  ch = getchar();
	while (ch >= '0' and ch <= '9') x = (x<<1) + (x<<3) + (ch^48), ch = getchar(); f and (x = -x);
} template <typename T, typename ... Args> inline void get(T & x, Args & ... _Args) { get(x); get(_Args...); }
#define rep(i, a, b) for (register int i = (a), i##_ = (b) + 1; i < i##_; ++i)
#define pre(i, a, b) for (register int i = (a), i##_ = (b)-1; i > i##_; --i)
const int N = 3e5 + 10;
int n, tot;

struct task {
	int val, edp;
	bool operator < (const task& b) const {
		return val > b.val;
	}
} a[N]; priority_queue<task> q;
bool cmp(const task& a, const task& b) { return a.edp < b.edp; }

signed main() {
    get(n); rep(i,1,n) get(a[i].edp, a[i].val);
	sort(a+1, a+1+n, cmp);
	rep(i,1,n) {
		if (a[i].edp > q.size()) {
			q.push(a[i]); tot += a[i].val;
		} else if (a[i].val > q.top().val) {
			tot += a[i].val - q.top().val;
			q.pop(); q.push(a[i]);
		}
	} cout << tot;
}

[JSOI2007] 建筑抢修

\(n\) 项工作,每项工作有截止时间和花费时间,进行工作得到单位利润。

考虑按截止时间排序。

维护一个按花费时间排序的大根堆。记录一个当前总花费时间。
如果做当前任务没有到达当前的截止时间,那就直接加入。反之删除当前决策对象中最大花费元素。

也是比较简单的。

code
#include <bits/stdc++.h>
using namespace std;
#ifdef ONLINE_JUDGE
    char buf[1<<21], *p1 = buf, *p2 = buf;  inline char getc() { return (p1 == p2 and (p2 = (p1 = buf) + fread(buf, 1, 1<<21, stdin), p1 == p2) ? EOF : *p1++); }
    #define getchar getc
#endif
template <typename T> inline void get(T & x){
	x = 0; char ch = getchar(); bool f = 0; while (ch < '0' or ch > '9') f = f or ch == '-',  ch = getchar();
	while (ch >= '0' and ch <= '9') x = (x<<1) + (x<<3) + (ch^48), ch = getchar(); f and (x = -x);
} template <typename T, typename ... Args> inline void get(T & x, Args & ... _Args) { get(x); get(_Args...); }
#define rep(i, a, b) for (register int i = (a), i##_ = (b) + 1; i < i##_; ++i)
#define pre(i, a, b) for (register int i = (a), i##_ = (b)-1; i > i##_; --i)
const int N = 3e5 + 10;
int n, tot;

struct task {
	int tme, edp;
	bool operator < (const task& b) const {
		return edp < b.edp;
	}
} a[N]; 

struct pid {
    int val, id;
    pid() = default;
    pid(int _v, int _i) : val(_v), id(_i) {}
    bool operator < (const pid& b) const { return val < b.val; }
} ; priority_queue<pid> que;

signed main() {
    get(n); rep(i,1,n) get(a[i].tme, a[i].edp);
	sort(a+1, a+1+n);
	rep(i,1,n) {
		if (tot + a[i].tme <= a[i].edp) {
			que.emplace(a[i].tme, i);
			tot += a[i].tme;
		} else {
			if (que.top().val > a[i].tme) {
				tot += a[i].tme - que.top().val;
				que.pop();
				que.emplace(a[i].tme, i);
			}
		}
	} cout << que.size();
}

CF865D

倒卖股票。每天买入卖出价格相同,每天最多只能交易一股。已知 \(n\) 天里股票价格 \(C_i\),问 \(n\) 天后最大利润。

考虑不排序。

假设第 \(u\) 天买入,第 \(v\) 天卖出能得到最大价值 \(C_v - C_u\),则插值得到 \((C_v - C_i) + (C_i - C_u)\)
因此考虑维护一个以股票价格排序的小根堆。顺序访问 \(n\) 天,首先将该天价格放入堆内,如果堆顶值小于当前值则更新答案。为反悔,将当前值再次插入堆内。
容易发现如果最终答案中没有选择 \(i\)\(C_i\) 定被选了两次,因此贪心思路正确。

这也叫做堆反悔自动机。代码实现十分简单。

code
#include <bits/stdc++.h>
using namespace std;
#define int long long
#ifdef ONLINE_JUDGE
    char buf[1<<21], *p1 = buf, *p2 = buf;  inline char getc() { return (p1 == p2 and (p2 = (p1 = buf) + fread(buf, 1, 1<<21, stdin), p1 == p2) ? EOF : *p1++); }
    #define getchar getc
#endif
template <typename T> inline void get(T & x){
	x = 0; char ch = getchar(); bool f = 0; while (ch < '0' or ch > '9') f = f or ch == '-',  ch = getchar();
	while (ch >= '0' and ch <= '9') x = (x<<1) + (x<<3) + (ch^48), ch = getchar(); f and (x = -x);
} template <typename T, typename ... Args> inline void get(T & x, Args & ... _Args) { get(x); get(_Args...); }
#define rep(i, a, b) for (register int i = (a), i##_ = (b) + 1; i < i##_; ++i)
#define pre(i, a, b) for (register int i = (a), i##_ = (b)-1; i > i##_; --i)
const int N = 3e5 + 10;
int n, tot;

struct pid {
    int val;
    pid() = default;
    pid(int _v) : val(_v) {}
    bool operator < (const pid& b) const { return val > b.val; }
} a[N] ; priority_queue<pid> que;

signed main() {
    get(n); rep(i,1,n) get(a[i].val);
	rep(i,1,n) {
		que.push(a[i]);
		if (que.top().val < a[i].val) {
			tot += a[i].val - que.top().val;
			que.pop(); que.push(a[i]);
		}
	} cout << tot;
}

[APIO/CTSC2007] 数据备份

\(n\) 个数轴上的点,选出 \(2k\) 个点组成 \(k\) 组,使得每组点之间的距离和最小。

一个基础的观察就是这些点必定相邻。证明考虑调整法删掉重合的线。

因此考虑将相邻的点两两配对成 \(n-1\) 组,第 \(i\) 个点的点权为配对两点间的距离。我们需要的就是从这些点里选择 \(k\) 个,满足相邻的点不能同时被同时选择。

考虑直接贪心。我们将每个点的点权放入一个大根堆,每次取堆顶并标记非法节点。
这样的贪心思路肯定是错的(
考虑如何反悔。

我们发现,反悔本质上就是需要支持“删除原先已选的某个节点对答案的贡献”。
观察到,如果我们选择了点 \(i\),则关于它的全局更优决策一定是同时选择 \(i-1\)\(i+1\)。证明考虑如果只选一侧就更优则这一侧会更先被选择。因此我们在选择 \(i\) 的同时应当加入“不选 \(i\) 节点并选择 \(i-1\) 节点和 \(i+1\) 节点”的策略。
这点还是比较简单的。选择 \(i\) 后给左右打 \(vis\) 标记,并将当前位置的权值改为左右权值加和减当前位置即可。

使用堆维护就能在每次决策完后得到更优解。使用双向链表就能在 \(O(1)\) 时间内确定需要打标记的位置。
迭代 \(k\) 轮得到最优答案。

code
#include <bits/stdc++.h>
using namespace std;
#define int long long
#ifdef ONLINE_JUDGE
    char buf[1<<21], *p1 = buf, *p2 = buf;  inline char getc() { return (p1 == p2 and (p2 = (p1 = buf) + fread(buf, 1, 1<<21, stdin), p1 == p2) ? EOF : *p1++); }
    #define getchar getc
#endif
template <typename T> inline void get(T & x){
	x = 0; char ch = getchar(); bool f = 0; while (ch < '0' or ch > '9') f = f or ch == '-',  ch = getchar();
	while (ch >= '0' and ch <= '9') x = (x<<1) + (x<<3) + (ch^48), ch = getchar(); f and (x = -x);
} template <typename T, typename ... Args> inline void get(T & x, Args & ... _Args) { get(x); get(_Args...); }
#define rep(i, a, b) for (register int i = (a), i##_ = (b) + 1; i < i##_; ++i)
#define pre(i, a, b) for (register int i = (a), i##_ = (b)-1; i > i##_; --i)
const int N = 3e5 + 10;
int n, X, ans, sum, a[N], newl[N];
bool vis[N];

struct list_t {
    int pref, next;
} l[N];
struct pid {
    int val, id;
    pid() = default;
    pid(int _v, int _i) : val(_v), id(_i) {}
    bool operator<(const pid& b) const { return val > b.val; }
};
priority_queue<pid> que;

signed main() {
    get(n, X);
	int lst = 0;
    rep(i, 1, n) {
        get(a[i]);
        if (i > 1) {
            newl[i - 1] = a[i] - a[i - 1];
            l[i - 1] = { i - 2, i };
            que.emplace(newl[i - 1], i - 1);
        }
    } sum = ans;
    newl[0] = newl[n] = 1e18;
    rep(i, 1, X) {
        while (vis[que.top().id]) que.pop();
        auto x = que.top();
        que.pop();
        ans += newl[x.id];
        vis[l[x.id].next] = vis[l[x.id].pref] = 1;
        newl[x.id] = newl[l[x.id].next] + newl[l[x.id].pref] - newl[x.id];
        que.emplace(newl[x.id], x.id);
        l[x.id].next = l[l[x.id].next].next;
        l[x.id].pref = l[l[x.id].pref].pref;
        l[l[x.id].next].pref = x.id;
        l[l[x.id].pref].next = x.id;
    } cout << ans << endl;
}
posted @ 2022-10-22 21:06  joke3579  阅读(66)  评论(1编辑  收藏  举报