这种“反悔”操作真的很强
模型概述
贪心操作中保证每一步都选取当前最优解,但通过某种转换将一步更改操作转为一个可选取的物品。
一些例题
51nod1380 夹克老爷的逢三抽一
又到了诺德县的百姓孝敬夹克大老爷的日子,带着数量不等的铜板的村民准时聚集到了村口。
夹克老爷是一位很"善良"的老爷,为了体现他的仁慈,有一套特别的收钱的技巧。
1、让所有的村民排成一队,然后首尾相接排成一个圈。
2、选择一位村民收下他的铜钱,然后放过他左右两边的村民。
3、让上述三位村民离开队伍,并让左右两边的其他村民合拢起来继续围成一个圈。
4、重复执行2、3直到村民全部离开。
夹克老爷的家丁早早的组织村民排成一队并清点了村民人数和他们手里的铜钱数量。
作为夹克老爷的首席师爷,你要负责按照夹克老爷的收钱技巧完成纳贡的任务。
聪明的你当然知道夹克老爷并不像他表现出来的那样仁慈,能否收到最多的钱财决定了你是否能够继续坐稳首席师爷的位置。
今年村民的人数是N,恰巧是3的倍数。
提示:第2步选择村民时不需要按照任何顺序,你可以选择任何一位仍然在队伍里的村民收取他手中的钱财并放走他两侧的村民(这就意味着你无法同时收取到这两位的铜钱了)
Input
第一行1个整数N(3 <= N <= 10^5 - 1, N % 3 == 0)
第2 - N + 1行:每行1个数对应村民i手中的铜钱。(0 <= m[i] <= 10^9)
Output
一个整数,说明在夹克老爷的收钱规则下你最多能够为夹克老爷搜刮到多少铜钱
Input示例
6 6 2 3 4 5 9
Output示例
13
题目分析
先来看一个显然很假的做法:将每一个人重新赋一个权值,为他自己的钱数减去他相邻两人的钱数和。然后每一次取所有的最大值,并且动态修改这个权值。
哈……这个果断假的很……比如n=6,7 9 7 1 2 3就能够卡掉。
那么是什么原因导致这个方法不正确呢?
结合上面那个就可以看出,这个新赋的权值代表的只是单个点在周围两个点之间相对的优劣。一个只涉及相邻元素的估价权值,还能指望它起什么作用?……
但是贪心还是要贪的。取了元素有后效性,转移又没什么办法转移,这辈子都不可能动态规划的。
那么首先,每次选取的肯定要是全局里较优的。但是归根结底来说,是什么原因导致了不能每一次都选最大的然后删除其两边元素呢?是因为对于当前最优的操作,过一会有可能发现会有其他方法能够使得以后更优。
看上去这是个贪心思路会遇到的通病,但是路并没有被走绝。如果把“反悔”这一步也看做是一个可以选取的物品,那么之后不就会因为“反悔”在全局更优而“改变历史”吗?(当然前提是获得价值的操作与时间无关,并且题目的限制并不会因为反悔历史操作而被违背)
当然“反悔”这个技巧也是要在特定情境下使用的。例如这题,我们可以发现几个结论:
- 一共选取$n/3$个物品
- 选取的物品互不相邻并且去除相邻物品后仍然不相邻
- 一定有两个相邻的物品之间相隔大于一个不被选取的物品
看上去不就是显然结论吗,但是可以由此推出这个问题的充要条件:选取$n/3$个互不相邻的物品,并且允许将“反悔”作为物品形式选取。
一波酷炫操作之后,换成代码形式就很好理解了……
1 #include<bits/stdc++.h> 2 typedef long long ll; 3 const int maxn = 200035; 4 5 ll n,a[maxn],tot,ans; 6 ll pre[maxn],nxt[maxn]; 7 struct cmp 8 { 9 bool operator () (ll x, ll y) const 10 { 11 return a[x] < a[y]; 12 } 13 }; 14 std::priority_queue<ll, std::vector<ll>, cmp> q; 15 bool fbd[maxn]; 16 17 ll read() 18 { 19 char ch = getchar(); 20 ll num = 0; 21 bool fl = 0; 22 for (; !isdigit(ch); ch = getchar()) 23 if (ch=='-') fl = 1; 24 for (; isdigit(ch); ch = getchar()) 25 num = (num<<1)+(num<<3)+ch-48; 26 if (fl) num = -num; 27 return num; 28 } 29 void deletes(ll id) 30 { 31 ll l = pre[pre[id]], r = nxt[nxt[id]]; 32 a[++tot] = a[pre[id]]+a[nxt[id]]-a[id]; 33 fbd[id] = 1, fbd[pre[id]] = 1, fbd[nxt[id]] = 1; 34 pre[tot] = l, nxt[tot] = r; 35 pre[r] = tot, nxt[l] = tot; 36 q.push(tot); 37 } 38 int main() 39 { 40 n = tot = read(); 41 for (int i=1; i<=n; i++) 42 { 43 pre[i] = i==1?n:i-1, nxt[i] = i==n?1:i+1; 44 a[i] = read(), q.push(i); 45 } 46 for (int i=1; i<=n/3; i++) 47 { 48 int id = q.top(); 49 q.pop(); 50 while (fbd[id]) 51 id = q.top(), q.pop(); 52 deletes(id); 53 ans += a[id]; 54 } 55 printf("%lld ",ans); 56 return 0; 57 }
bzoj1572: [Usaco2009 Open]工作安排Job
Description
Farmer John 有太多的工作要做啊!!!!!!!!为了让农场高效运转,他必须靠他的工作赚钱,每项工作花一个单位时间。 他的工作日从0时刻开始,有1000000000个单位时间(!)。在任一时刻,他都可以选择编号1~N的N(1 <= N <= 100000)项工作中的任意一项工作来完成。 因为他在每个单位时间里只能做一个工作,而每项工作又有一个截止日期,所以他很难有时间完成所有N个工作,虽然还是有可能。 对于第i个工作,有一个截止时间D_i(1 <= D_i <= 1000000000),如果他可以完成这个工作,那么他可以获利P_i( 1<=P_i<=1000000000 ). 在给定的工作利润和截止时间下,FJ能够获得的利润最大为多少呢?答案可能会超过32位整型。
Input
第1行:一个整数N. 第2~N+1行:第i+1行有两个用空格分开的整数:D_i和P_i.
Output
输出一行,里面有一个整数,表示最大获利值。
Sample Input
2 10
1 5
1 7
Sample Output
HINT
第1个单位时间完成第3个工作(1,7),然后在第2个单位时间完成第1个工作(2,10)以达到最大利润