上周刚打完比赛,加上这周还有考试,没怎么缓过神来,这周学了点方便的c++新特性,然后就基本是摸了.....
回到贪心算法,这周的任务是学习贪心。我认为,贪心与其说是一个算法,不如说是一种思维。由局部最优解推出全局最优的一种思维。
与其有相似感觉的就是动态规划,贪心是取得每个小问题的最优解然后汇总成完整问题的最优解,动态规划则是取得相互关联的子问题的最优解然后不断递推得到整个问题的最优解。
贪心的题难度跨度很大,简单的用常识看一眼就解决了,难的则怎么也想不出思路。
其次,贪心其实是需要证明的,不是看出来觉得怎么样就怎么样。这一步非常困难了。我看过的资料里,有个博主是这样证明或者说得出一个贪心思路的。
就是先假设一个非贪心的最优解,然后我们尝试用贪心的策略,将其改进为一个不会更差的结论,经过多次迭代,这个假设的最优解就会成为全局最优解。
这个证明思路给了我一些启发,对于一些难以直接得出结论的贪心题可以尝试构造一些简单的情况,然后对其尝试不同的贪心策略改进算法,最后得到最优解。
这种想法让我想起之前的一道优先队列练习题(其实就是贪心题)
这题的整体思路就是对倒塌时间贪心,因为快倒的房子肯定更优先去修补。但是因为还有修补时间的限制。所以引出一个关键步骤,就是我们先对倒塌时间贪心完后,对得到的这个当前结果继续贪心,但是此时的贪心标准为修补时间,很显然同等情况下。修补时间越短的房子肯定更时候优先修补,所以回溯回之前修补的房子替换修补时间更长的房子,以此腾出更多的时间修补更多的房子
#include<bits/stdc++.h>
#pragma warning (disable:4996);
#define ll long long
#define int ll
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
const int N = 2e5 + 10;
int n,m, k, q, T;
int a, b, c;
int arr[N];
struct node {
int t1, t2;
int operator<(const node& a)const {
return t2 < a.t2;
}
}p[N];
signed main() {
std::ios::sync_with_stdio(0); cout.tie(0);
#ifndef ONLINE_JUDGE
freopen("out.txt", "w", stdout);
#endif
cin >> n;
for (int j = 1; j <= n; j++) {
cin >> p[j].t1 >> p[j].t2;
}
int nowTime = 0;
int ans = 0;
priority_queue<int>q;//维护时间消耗队列
sort(p + 1, p + n + 1);//从结束时间小到大,因为快倒的房子先修要紧
for (int j = 1; j <= n; j++) {
node now = p[j];
if (nowTime <= now.t2 - now.t1) {
nowTime += now.t1;
q.push(now.t1);
ans++;
}
else {
if (q.top() > now.t1) {//如果这个房子修补了比之前消耗大的房子合算,那么放弃原来的//房子转而修补当前这个。
nowTime +=(now.t1-q.top());//返回去不修之前代价大的,而是修现在这个
q.push(now.t1);
q.pop();
}
}
}
cout <<ans << endl;
}
不过这题算难的了,因为有两个贪心的标准,很难直接想到正确思路。
也有一些思路不太明显需要好好想想,但比较简单的题
这题因为问的是相邻,所以看似好像每个问题互有关联,不能贪心。实际上想明白后就很简单了。
因为要最小操作,所以能吃到x就没必要继续吃了。其次,对于前两个数,如果选前一个数,他减少的糖果数只影响这两个相邻的总和,但是如果选第二个数,他减少的糖果将影响两个相邻糖果盒的数量。明显是更优的,然后依次递推下去即可。
还有一些贪心策略则需要通过一些规律来得到
首先要字典序最大,前面的位数要尽量是1这个容易想到,但是保证前面的位数要是1的同时进行的操作数要越少越好,因为这样才有更多的操作去改变后面的数,因为选择更多,可以产生最优解的可能越高。再由推导的规律,当被反转的次数为偶数的时候不改变元素的值,反转的次数为奇数的时候元素被改变。所以讨论一下k的奇偶性然后遍历遇到1就反转对应的最小步数即可。
思路如此,但是这题实际写起来比较麻烦
#include<bits/stdc++.h>
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
#define ll long long
#define int ll
#pragma warning (disable:4996);
const int N = 2e5 + 10;
int T, n, m, q, k;
string s;
int ans[N];//记录每个元素的操作次数
int re(char c) {
if (c == '1')return 0;
return 1;
}
signed main() {
ios::sync_with_stdio(0); cout.tie(0);
#ifndef ONLINE_JUDGE
freopen("out.txt", "w", stdout);
#endif
cin >> T;
while (T--) {
mem(ans, 0);
cin >> n >> k;
m = k;
cin >> s;
if (m % 2 == 1) {
for (int j = 0; j < n&&m!=0; j++) {
if (s[j] == '1' && m) {
ans[j] = 1;
m--;
}
if (m == 0)break;
}
if (m)ans[n - 1] += m;//大体上已经处理完了
//多余的操作全给最后一个元素保证是影响最小
}
else {
for (int j = 0; j < n&&m!=0; j++) {
if (s[j] == '0' && m) {
ans[j] = 1;
m--;
}
}
if (m) ans[n - 1] += m;
}
//输出--
for (int j = 0; j < n; j++) {
if ((k - ans[j]) % 2 == 0)cout << s[j];//按规律判断是否反转
else cout << re(s[j]);
}
cout << endl;
for (int j = 0; j < n; j++)
cout << ans[j] << " ";
cout << endl;
}
}