文章首发于:My Blog 欢迎大佬们前来逛逛
P2240 部分背包问题 P2240 【深基12.例1】部分背包问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
有点像01背包,但是又不是,01背包的物品的是不能再分的,而本题的物品是可以再分的 ,因此我们可以根据某个物品的部分重量和单位价值 算出它的部分最大价值来作为我们选择的物品。
首先计算单位价值 = 总价值 / 总重量
按照贪心的思想,则单位价值大的一定是越优的,因此排序 ,单位价值大的排在前面。
按照单位价值排好序的顺序一个一个物品选,若背包的剩余容量不够则取某个物品的部分最大价值,然后退出即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 int n,T;const int N=1e4 +10 ;struct Node { double w,v,pro; }node[N]; bool cmp (Node a,Node b) { if (a.pro>b.pro) { return true ; } return false ; } signed main () { cin>>n>>T; for (int i=1 ;i<=n;i++) { cin>>node[i].w>>node[i].v; node[i].pro=node[i].v/node[i].w; } sort (node+1 ,node+1 +n,cmp); double ans=0 ; double temp=T; for (int i=1 ;i<=n;i++) { if (temp>=node[i].w) { ans+=node[i].v; temp-=node[i].w; } else { ans+=(temp*node[i].pro); break ; } } printf ("%.2lf" ,ans); #define one 1 return 0 ; }
P1223 排队接水 排队接水
排队接水:一列人排成一队,接水的人有个接水的时间 ,而其他人都要等 这个人接完水,然后再轮流接水,已经接完水的就完成任务了,即不需要算在排队等接水的人当中了。
我们要计算n个人的平均接水时间,因此需要把当前人接水的时间 * 后面等待的总人数
因此我们可以总结出:最先接水的一定是接水所需时间最少的 。为什么?
如果你的接水时间是 1000 ,那么除这个人之外的9个人就需要等待 9 * 1000的时间。
如果你的接水时间是 10 ,那么除这个人之外的9个人就需要等待 9 * 10 的时间。
很显然按照接水时间短的先接水的这种做法一定是最优的。
那么就直接按照时间排序即可,然后计算 平均的等待时间,最后再除以一次总人数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 int n,m;const int N=1e6 +10 ;int nums[N],presum[N];struct Node { int num,val; }node[N]; bool comp (Node a,Node b) { if (a.val<b.val) { return true ; } else if (a.val==b.val) { return a.num<b.num; } return false ; } signed main () { cin>>n; for (int i=1 ;i<=n;i++) { node[i].num=i; cin>>node[i].val; } sort (node+1 ,node+1 +n,comp); double ans=0 ; for (int i=1 ;i<=n;i++) { ans+=(n-i)*node[i].val; cout<<node[i].num<<" " ; } cout<<endl; printf ("%.2lf" ,ans/n); #define one 1 return 0 ; }
P1803 凌乱的yyy / 线段覆盖 P1803 凌乱的yyy / 线段覆盖
这道题其实就是贪心的经典问题:给出任务的开始和结束时间,求总共能完成的任务的最大数量
贪心思路:
过程如下;
因此把每个任务按照结束时间排序,结束时间早的是最优的。
按照结束时间早正序 排序,如果下一个任务的开始时间在上一个任务的进行过程中,则说明还没有完成上一个任务,因此跳过这个任务,只能是下一个任务的开始时间最差的等于上一个任务的结束时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 int n,m;const int N=1e6 +10 ;int nums[N];struct Node { int s,e; }node[N]; bool comp (Node a,Node b) { if (a.e<=b.e) return true ; return false ; } signed main () { cin>>n; for (int i=1 ;i<=n;i++) { cin>>node[i].s>>node[i].e; } int ans=0 ; int fg=0 ; sort (node+1 ,node+1 +n,comp); for (int i=1 ;i<=n;i++) { if (i==1 ) { ans++; fg=i; } else { if (node[i].s>=node[fg].e) { fg=i; ans++; } } } cout<<ans; #define one 1 return 0 ; }
P1090 [NOIP2004 提高组] 合并果子 P1090 [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G
题目让我们求合并两堆的最小花费的总和, 因此我们很容易想到最小的两堆一起合并一定是最优的
我们便可以想到直接排序即可,花费小的在前面,然后前两个合并成一个,然后再把这个合并的放进去再排序,最后知道这个队列只有一个元素为止,因此我们每次所统计的两堆的和就是最后的答案。
这道题目的思想是很容易的。
但是如何做到 将两堆取出来合并后的值再放回去 ? 我们可以想到 堆排序。
进而想到堆排序的一个应用:优先队列
我们要制造 小顶堆 ,即堆顶元素是最小的,然后取出堆的前两个元素,合并后再插入堆
优先队列C++:priority_queue小顶堆形式:
1 priority_queue<int,vector<int>,greater<int>> q;
大顶堆形式:
因此就解决了,当然你也可以手写堆 ,包含构建初始堆和堆的调整过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 int n,m;const int N=1e5 +10 ;int nums[N];signed main () { cin>>n; priority_queue<int ,vector<int >,greater<int >> q; for (int i=1 ;i<=n;i++) { int num; cin>>num; q.push (num); } if (q.size ()==1 ) { cout<<q.top (); return 0 ; } int ans=0 ; while (q.size ()>=2 ) { int num1=q.top (); q.pop (); int num2=q.top (); q.pop (); int sum=num1+num2; ans+=sum; q.push (sum); } cout<<ans; #define one 1 return 0 ; }
P3817 小A的糖果 P3817 小A的糖果
让我们求 在相邻的两个盒子的总数不超过 m 的情况下,至少需要吃的数量。
貌似我们可以进行排序? 从小到大,然后从左往右吃
不可以
我们注意到盒子是有先后顺序的,因此不能够改变位置(当然如果你会排序的做法则当我没说)
我们就直接贪心即可:
从左往右遍历,如果当前的盒子 i 的数量 + 后一个盒子 i+1 的数量 超过了规定 m,则我们一定需要在这两个中吃糖果,如何吃呢?
如果我们选择吃 i ,则 我们只会改变 i+1着一种情况,即 【i,i+1】是一组
但是如果我们吃 i+1,则我们不仅改变了【i,i+1】这一组,还可能改变 【 i+1,i+2】下一组,因此吃后面的一定是最优的(求最少的吃的数量)
遍历到后面的每个 i 盒子的时候,它的 i -1个位置的盒子数量 一定被上一种情况吃了。但是我们的第 1 个盒子怎么办呢,它可没有前一个?
我们直接错一下位即可, 让0(实际不存在,从1开始)号盒子 与 1 号成一组,然后我们吃后面的,这样不就吃到 1 号盒子了吗
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 int n,m;const int N=1e5 +10 ;int nums[N];signed main () { cin>>n>>m; for (int i=1 ;i<=n;i++) { cin>>nums[i]; } int ans=0 ; for (int i=1 ;i<=n;i++) { int a=i-1 ,b=i; if (nums[a]+nums[b]>m) { ans+=nums[a]+nums[b]-m; nums[b]=m-nums[a]; } } cout<<ans; #define one 1 return 0 ; }
P1106 删数问题 P1106 删数问题
在序列中删除 k 位后能够组成的最小的数。
貌似很简单?
直接从左往右,碰到一个 i 位置元素如果比 i-1 位置的元素大 ,则删除 i 位置的元素?
错误的 !!
示例:
1 5 9 8 (2) ,删除两个,按照上面的思路,则删除 5 和 9,最后得到 18,但是实际上 15是最优的(删后两个)
1 2 6 5 9 7 (3),删除三个,按照上面的思路,则删除 2 6 和 9,得到了 1 5 7,但是实际上只要 2没被删除则其他的都比这个小。
那么怎么删呢? 观察一下式子 ,定义:比两边都大,则此位置为山峰
1 5 9(山峰) 8(山峰) :删除山峰 ,得到 1 5
为什么 8 也是山峰,9比8大啊,因为我们提前删除了9,因此 8 的前面是 5。
1 2 6(山峰) 5 9(山峰) 7(山峰):删除山峰,得到 1 2 5
答案:我们删除山峰位置的元素,则最后的数一定是最小的
实际上山峰在此题只需要表示为:比后面的数大即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 int n,k;const int N=1e5 +10 ;char s[N];int p=1 ;bool vis[N];signed main () { cin>>s>>k; int len=strlen (s); while (k--) { for (int i=0 ;i<len-1 ;i++) { if (s[i]>s[i+1 ]) { for (int j=i;j<len-1 ;j++) { s[j]=s[j+1 ]; } break ; } } len--; } int i=0 ; while (i<len && s[i]=='0' ) i++; if (i==len) printf ("0" ); else { for (int j=i;j<len;j++) { cout<<s[j]; } } #define one 1 return 0 ; }