课课通上的一些题
例1、独木舟
【问题描述】 旅行社计划组织一个独木舟旅行。租用的独木舟都是一样的,最多乘两人,而且载重有一个限度。现在要节约费用,所以要尽可能地租用最少的舟。本题的任务是读入独木舟的载重量,参加旅行的人数以及每个人的体重,计算出所需要的独木舟数目。 【输入格式】 第 1 行是 w(80≤w≤200),表示每条独木舟最大的载重量。 第 2 行是正整数 n(1≤n≤30000),表示参加旅行的人数。 接下来的 n 行,每行是一个正整数 t i (5≤t i ≤w),表示每个人的重量。 【输出格式】 输出一行一个数,表示最少的独木舟数目。
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=3e4+10; const int INF=1e9; const int mod=1e6; typedef long long LL; typedef unsigned long long ull; int w,n; int a[maxn]; int cnt; int heap[maxn]; //这个写法要熟练 bool cmp(int a,int b){ return a>b; } void pushup(int x){ //大根堆 if(x==1) return ; int fa=x/2; if(heap[fa]<heap[x]){ swap(heap[fa],heap[x]); pushup(fa); } } void pushdown(int x){ int child=x*2; if(child>cnt) return; if(child+1<=cnt&&heap[child]<heap[child+1]) child++; if(heap[x]<heap[child]) { swap(heap[x],heap[child]); pushdown(child); //让小孩接着往上走 } } int main(){ cin>>w>>n; for(int i=1;i<=n;i++) cin>>a[i]; sort(a+1,a+1+n,cmp); int i=0; cnt=1; for(int i=1;i<=n;i++) heap[i]=w; //先假设有n个船,每个船都是满的,这个数组的值表示还能装载的重量 while(i<n){ i++; if(heap[1]>=a[i]) { //堆顶是能够承载 重量最大的 if(heap[1]!=w) heap[1]=0; //只能装两个人 else heap[1]-=a[i]; pushdown(1); } else{ cnt++; heap[cnt]-=a[i]; pushup(cnt); } } cout<<cnt<<endl; return 0; }
例4、最大整数
【问题描述】 设有 n(n≤20)个正整数(小于或等于 2147483647),将它们连接成一排,组成一个最大的多位整数。例如 n=3,3 个整数为 13、312 和 343,连接成的最大整数为 34331213。
【输入格式】 第 1 行 1 个整数 n。 第 2 行为 n 个正整数,每两个数之间用一个空格隔开。
【输出格式】 一行一个数,表示连接成的最大整数。
【输入样例】 4 7 13 4 246 【输出样例】 7424613
【问题分析】 首先,自然会想到大的字符串应该排在前面,因为如果 A 与 B 是两个由数字字符构成的字符串,且 A>B,一般情况下有 A+B>B+A。但是,当 A=B+C 时,按字符串的大小定义有 A>B,此时有可能出现 A+B<B+A 的情况。如 A= ‘ 121 ’ ,B= ‘ 12 ’ ,则 A+B= ‘ 12112 ’ ,B+A= ‘ 12121 ’ ,所以A+B<B+A。 为了解决这个问题,根据题意引进另一种字符串比较办法,将 A+B 与 B+A 相比较,如果前者大于后者,则认为 A>B。按这一定义将所有的数字字符串从大到小排序后连接起来所得到的数字字符串即是问题的解。排序时先将所有字符串中的最大值选出来存在数组的第一个元素中,再从第二至最后一个元素中最大的字符串选出来存在数组的第二个元素中,直到从最后两个元素中选出最大的字符串存在数组的倒数第二个元素中为止。
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=3e4+10; const int INF=1e9; const int mod=1e6; typedef long long LL; typedef unsigned long long ull; //快速幂,求123--->123000 int qpow(int x,int y){ if(y==0) return 1; if(y==1 ) return x; int z=qpow(x,y/2); if(y%2) return z*z*x; else return z*z; } struct node{ int a,b; }op[21]; bool cmp(node a,node b){ // return a.a*a.b+b.a*b.b>b.a*b.b+a.a*a.b; return a.a*b.b+b.a>b.a*a.b+a.a; } int main(){ int n; cin>>n; for(int i=1;i<=n;i++){ cin>>op[i].a; int x=op[i].a; op[i].b=0; while(x){ x/=10;op[i].b++; } op[i].b=qpow(10,op[i].b); } sort(op+1,op+1+n,cmp); for(int i=1;i<=n;i++) cout<<op[i].a; return 0; }
例5、取火柴游戏
【问题描述】 输入 k 及 k 个整数 n 1 ,n 2 ,…,n k ,表示有 k 堆火柴棒,第 i 堆火柴棒的根数为 n i 。接着便是和计算机对弈游戏,取火柴的规则如下:每次可以从一堆中取走若干根火柴,也可以将一堆全部取走,但不允许跨堆取,也不允许不取。 谁取走最后一根火柴算谁胜利。
例如,k=2,n 1 =n 2 =2,A 代表你,P 代表计算机,若决定 A 先取: A: (2,2)→(1,2) // 从一堆中取一根 P: (1,2)→(1,1) // 从另一堆中取一根 A: (1,1)→(1,0) P: (1,0)→(0,0) //P 胜利 如果决定 A 后取: P: (2,2)→(2,0) A: (2,0)→(0,0) //A 胜利 又如 k=3,n 1 =1,n 2 =2,n 3 =3,A 决定后取: P: (1,2,3)→(0,2,3) A: (0,2,3)→(0,2,2) A 已将游戏归结为(2,2)的情况,不管 P 如何取 A 都必胜。
【输入样例】 3 3 6 9 【输出样例】 4 3 // 表示第 1 次从第 3 堆取 4 个出来必胜 3 6 5 // 第 1 次取后的状态 【输入样例】 4 15 22 19 10 【输出样例】 lose // 先取必败
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=3e4+10; const int INF=1e9; const int mod=1e6; typedef long long LL; typedef unsigned long long ull; //取石头游戏,nim游戏,奇状态、偶状态 //如果先手为偶状态,那么就必输,如果为奇状态,那就先走,并且走的那个是能够把整个状态变成偶状态的 int n,a[maxn]; int p[32],aw[32]; int main(){ cin>>n; p[0]=1; for(int i=1;i<=31;i++) p[i]=2*p[i-1]; for(int i=1;i<=n;i++){ cin>>a[i]; for(int j=31;j>=0;j--){ if(a[i]&p[j]) aw[j]++; //记录所有的二进制中这一位的数量 } } int maxq=-1; for(int i=1;i<=31;i++) { if(aw[i]%2==1) maxq=i; } if(maxq==-1) cout<<"lose"<<endl; else{ for(int i=1;i<=n;i++){ if((a[i]&p[maxq])==p[maxq]){ int f=a[i]; for(int j=maxq;j>=0;j--){ if(aw[j]%2==1) f^=p[j]; //让他变成奇状态 } cout<<a[i]-f<<" "<<i<<endl; a[i]=f; for(int i=1;i<n;i++) cout<<a[i]<<" " ; cout<<a[n]<<endl; return 0; } } } }
例7、餐巾
一个餐厅在相继的n天里,第i天需要块餐巾(i=1,2, ....餐厅可以从三种途径获得餐巾。
(1)购买新的餐巾,每块需要p分。
(2)把用过的餐巾送到快洗部,洗一块需要 m天,费用需要f分({<p)。如m=1时,第一一天送到快洗部的餐巾第二天就可以使用了,送慢洗的情况也如此。
(3)把餐巾送到慢洗部,洗块需n天(n>m),费用需s分(s<)。
在每天结束时,餐厅必须决定多少块用过的餐巾送到快洗部,多少块送慢洗部。在每天开始时,餐厅必须决定是否购买新餐巾及多少,使洗好的和新购的餐巾之和满足当天的需求量T,并使n天总的费用最小。
[输入格式]
输人文件共3行,第1行为总天数,最多100天。
第2行为每天所需的餐巾块数。
第3行为每块餐中的新购费用p,快洗所需天数m.快洗所需费用1,慢洗所需天数n,慢选所需费用S。
[输出格式]
输出文件共n+1行。第1行为最小的费用。
下面的n行,表示从第1天开始每天需要的总餐中数需要购买的新餐巾数结束时往快、慢洗部送洗的餐巾数以及用到的来自1快洗的餐巾数和来自慢洗的餐巾数。
[输入样例1]
3
3 2 4
10 1 6 2 3
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=3e4+10; const int INF=1e9; const int mod=1e6; typedef long long LL; typedef unsigned long long ull; const int maxday=101; int totday,buy,fd,fc,sd,sc,ans; struct node{ int need,buy,fwash,swash,fuse,suse; //记录结果 }cnt[maxday]; struct node1{ int date,store; //每一天的日期和存量 }que[maxday]; //三分的是需要买的毛巾 LL getval(int totbuy){ LL totprice=totbuy; totprice*=buy; cnt[0].buy=cnt[0].need; totbuy-=cnt[0].need; cnt[0].fwash=cnt[0].swash=0; int head=0,tail=-1; for(int i=1;i<totday;i++){ if(i>=fd){ que[++tail].date=i-fd; //把超过快洗所需时间的后面的节点入队,其实是前面的 que[tail].store=cnt[i-fd].need; } cnt[i].fuse=cnt[i].fwash=cnt[i].suse=cnt[i].swash=0; int remain; if(totbuy>=cnt[i].need){ totbuy-=cnt[i].need; cnt[i].buy=cnt[i].need; remain=0; continue; } remain=cnt[i].need-totbuy; cnt[i].buy=totbuy; totbuy=0; //除去买的,先考虑慢洗的,在考虑快洗的 while(remain&&head<=tail&&que[head].date<=i-sd){ int use=remain; if(use>que[head].store) use=que[head].store; cnt[que[head].date].swash+=use; //是+= cnt[i].suse+=use; totprice+=use*sc; remain-=use; que[head].store-=use; if(!que[head].store) head++; //没有剩下的出队 } while(remain&&tail>=head){ int use=remain; if(use>que[tail].store) use=que[tail].store; cnt[que[tail].date].fwash+=use; cnt[i].fuse+=use; totprice+=use*fc; remain-=use; que[tail].store-=use; if(!que[tail].store) tail--; } if(remain) return INF; } return totprice; } int main(){ cin>>totday; int base=0,summ=0; memset(cnt,0,sizeof(cnt)); for(int i=0;i<totday;i++){ cin>>cnt[i].need; if(cnt[i].need>base) base=cnt[i].need; summ+=cnt[i].need; } cin>>buy>>fd>>fc>>sd>>sc; int l=base,r=summ; LL left=getval(l); LL right=getval(r); while(r-l>=3){ //三分 int delta=(r-l)/3; int a=l+delta,b=r-delta; LL x=getval(a); LL y=getval(b); if(left<=x&&x<=y||left>=x&&y>=x){ r=b;right=y; } else{ l=a;left=x; } } LL minn=left; ans=l; for(int i=l+1;i<=r;i++){ LL tmp=getval(i); if(tmp<minn) { minn=tmp; ans=i; } } cout<<getval(ans)<<endl; for(int i=0;i<totday;i++){ cout<<cnt[i].need<<" "<<cnt[i].buy<<" "<<cnt[i].fwash<<" "<<cnt[i].swash<<" "<<cnt[i].fuse<<" "<<cnt[i].suse<<endl; } return 0; }
例8、大神排队
每个人都有两个参数,影响力和承受能力,每个人的心理创伤等于前面所有人的影响力减去他的承受能力,求一个排序方式求收到床上最大的同学的创伤最小。
其实就是比较方式。
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=5e4+10; const int INF=1e9; const int mod=1e6; typedef long long LL; typedef unsigned long long ull; int n; struct peo{ int a,b; }po[maxn]; bool cmp(peo a,peo b){ if(a.a+a.b<b.a+b.b) return 1; if(a.a+a.b>b.a+b.b) return 0; if(a.a<b.a) return 1; //如果上面的比值相等,那就把影响力小的放前面(我就不会考虑这种。。。 return 0; } int main(){ cin>>n; for(int i=1;i<=n;i++){ cin>>po[i].a>>po[i].b; } sort(po+1,po+1+n,cmp); int tot=0,ans=-INF; for(int i=1;i<=n;i++){ ans=max(ans,tot-po[i].b); tot+=po[i].a; } cout<<ans<<endl; return 0; }
一本通
贪心问题:选择局部最优解,所需问题具有无后效性:某个状态以后的状态不会影响以前的状态,至于当前状态有关
一、均分纸牌,经典的,注意删除哪些地方的多余的0
int main(){ cin>>n; int ave=0,step=0; for(int i=1;i<=n;i++) { cin>>a[i]; ave+=a[i]; } ave/=n; for(int i=1;i<=n;i++) a[i]-=ave; //每一堆需要移动的 int s=1,e=n; while(s<=n&&a[s]==0) s++; //清除开始和结尾的0 while(e>=1&&a[e]==0) e--; while(s<e){ a[s+1]+=a[s]; a[s]=0; step++; s++; while(a[s]==0&&s<e) s++; //防止中间有0 } cout<<step<<endl; return 0; }
1321:【例6.3】删数问题(Noip1994)
方法:删除递减区间第一个,递增区间最后一个(注意判0)还有字符串长度的递减
#include<iostream> #include<cstdio> #include<cstring> using namespace std; char a[241]; int main(){ int sum; cin>>a; cin>>sum; int len=strlen(a); for(int i=1;i<=sum;i++){ for(int j=0;j<len-1;j++){ if(a[j]>a[j+1]){ for(int k=j;k<len-1;k++){ a[k]=a[k+1]; } break; } } len--; } int flag=0; for(int i=0;i<len;i++){ if(a[i]!='0') flag=1; if(flag) cout<<a[i]; } return 0; }
1322:【例6.4】拦截导弹问题(Noip1999)
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> using namespace std; int a[1001],b[1001];//导弹和能拦截的高度 int main(){ int i=1; while(scanf("%d",&a[i++])!=EOF); int k=1;b[k]=a[1]; for(int j=2;j<=i;j++){ int f=0; for(int l=1;l<=k;l++){ if(b[l]>=a[j]) { if(f==0) f=l; else if(b[l]<b[f]) f=l;//这里是贪心策略,选择在能拦截的系统里面高度最小的 } } if(f==0) { k++;b[k]=a[j]; } else b[f]=a[j]; } cout<<k<<endl; return 0; }
1323:【例6.5】活动选择
先按照结束时间排序,然后如果前一个的结束时间早于下一个的开始时间就可以选
复习:结构体的排序
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> using namespace std; int n; struct acti{ int st; int ed; }hei[1001]; bool cmp(acti a,acti b){ return a.ed<b.ed; } int main(){ cin>>n; for(int i=1;i<=n;i++) cin>>hei[i].st>>hei[i].ed; sort(hei+1,hei+n+1,cmp); int sum=0; int temp=-11111; for(int i=1;i<=n;i++){ if(hei[i].st>=temp) { //为什么从1开始:并设置temp初始值那么小:为了使第一个也能一样去计算 sum++; temp=hei[i].ed;//还有这里是能够保证这个活动算进去后temp才能改变 } //因为这个不行,下一个说不定行,但是如果每循环一次就改变temp那么下一个就永远不行!!!!!!!注意位置 } cout<<sum<<endl; return 0; }
1324:【例6.6】整数区间
这个题也是按照右端点排序,然后做法差不多
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> using namespace std; int n; struct qu{ int s; int t; }num[10001]; bool cmp(qu a,qu b){ return a.t<b.t; } int main(){ cin>>n; for(int i=1;i<=n;i++) cin>>num[i].s>>num[i].t; sort(num+1,num+n+1,cmp); int sum=0; int temp=-11111; for(int i=1;i<=n;i++){ if(num[i].s<=temp) continue; sum++; temp=num[i].t; } cout<<sum<<endl; return 0; }
1223:An Easy Problem
这个简单,其实直接暴力枚举就行了,反正也差不了好多
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> using namespace std; int x; int main(){ while(cin>>x){ if(x==0) break; int ans=0; int temp1=x; while(temp1!=0){ ans+=temp1%2; temp1/=2; } int s=0,temp; for(int i=x+1;;i++){ s=0; temp=i; while(temp!=0){ s+=temp%2; temp/=2; } if(s==ans) { cout<<i<<endl; break; } } } return 0; }
二、最大子矩阵,注意下标的选择和细节
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) { cin>>map[i][j]; map[i][j]+=map[i][j-1]; //到i与j这里的总和 } int ans=0; for(int i=0;i<=n-1;i++){//这里的下标必须为0和i-1因为可能只有一列!所有记得,不然会错 for(int j=i+1;j<=n;j++){ ans=0;//这里要清零!!!!!!!!注意 for(int k=1;k<=n;k++){ ans+=map[k][j]-map[k][i]; //还有这里是+= if(max<ans) max=ans; if(ans<0) ans=0; //这一句也得记住啊!! } } } cout<<max<<endl;
1225:金银岛
这个也是拿价值最大的,不是背包,因为东西可以切割就是可以只拿一部分,这个就是需要求平均价值,然后就easy
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> using namespace std; int k,n,w; struct go{ double value; double weight; double ave; }bao[101]; bool cmp(go a,go b){ return a.ave>b.ave; } int main(){ cin>>k; while(k--){ memset(bao,0,sizeof(bao)); cin>>w; cin>>n; for(int i=1;i<=n;i++){ cin>>bao[i].weight>>bao[i].value; bao[i].ave=bao[i].value/bao[i].weight; } sort(bao+1,bao+n+1,cmp); double sum=0; int i=1; while(w>0&&i<=n){ if(w-bao[i].weight>0) { sum+=bao[i].value; w-=bao[i].weight; } else { sum+=w*bao[i].ave; w=0; } i++; } printf("%.2f\n",sum); } return 0; }
三、Ride to office
这道题当时直到现在都在折磨着我的认知,知道我看了一片题解才发现,,,,原来如此好想我真是猪脑子
此题用贪心,所以投机取巧,转换思路,主人公一定是随着最快的那个人到达的。因为一旦后面有快的人跟上来,就会带着主人公飞了。
扰乱视线的:提前起跑的人。
1、如果提前起跑的这个人很快、最快的话,那主人公也不会追上他,这个人对主人公来说没有用。
2、如果这个人比较慢,后来被主人公追上的话,就说明现在主人公跟着的那个人比较快,还是不需要提前起跑的那个人。
综上,提前起跑的人没有用。
我记得当时我还去分析了提前跑的人后来带着这个人走了balabala。。。
while(cin>>n){ if(n==0) break; ans=99999; s=0; for(int i=1;i<=n;i++) { cin>>v>>t; if(t>=0){ //提前出发的不考虑!!! double time=4500/(v*10.0/36.0); if(time-(int)time>0) time++; //将所求的时间向上取整 s=time+t; //还要加上迟出发时间 if(ans>s) ans=s;//更新最优值 } } printf("%d\n",ans); }
四、电池的寿命
这道题其实比想象的简单多了,我发现很多贪心的题目从宏观上面来看的话,就会觉得简化了许多,就比如这道题,根本不需要一一匹配,直接算就可以了。
while(cin>>n){ sum=0; mmax=0; for(int i=1;i<=n;i++){ cin>>power[i]; sum+=power[i]; if(mmax<power[i]) mmax=power[i]; //取时间最长 } if(sum-mmax<mmax) printf("%.1lf\n",(double)(sum-mmax)); //如果剩余时间都小于最大 else printf("%.1lf\n",sum/2.0); //不然就是总时长除以二 }
五、Crossing River
每组操作都将剩下的人中最慢和次慢的人(贪心)送过去。
操作中有两种方法:(都是想办法让剩下人中n和n-1过去)
第一种:最快的人1带动慢的n和n-1,这样运过n和n-1共需要A1+An+A(n-1)+A1时间。
第二种:最快的1先和次快的2过去,然后最快的1回来,让n和n-1一起过去,然后让刚开始去的2回来,时间为A2+A1+An+A2。
看一下这两种哪一种时间小,也就是说比较 A(n-1)+A1与2*A2的关系。
总之,每组操作之后,在岸对面留下的都是慢的人,因为不要让他们回来,所以回来的都是快的。
cin>>t; while(t--){ cin>>n; int sum=0; for(int i=0;i<n;i++) cin>>a[i]; sort(a,a+n); int i; if(n==1) sum=a[1]; else if(n==2) sum=a[1]+a[2]; else{ for( i=n-1;i>2;i-=2){ if(a[0]+a[0]+a[i]+a[i-1]<a[1]+a[0]+a[i]+a[1]) sum+=a[0]+a[0]+a[i]+a[i-1];//两种情况 else sum+=a[1]+a[0]+a[i]+a[1]; } } if(i==2) sum+=a[0]+a[1]+a[2];//还有后面的处理!!!! 3个人 else if(i==1) sum+=a[1]; //2个人 else sum+=a[0]; cout<<sum<<endl; }
一本通提高篇
1423:【例题2】种树
现在我们国家开展新农村建设,农村的住房建设纳入了统一规划,统一建设,政府要求每一住户门口种些树。门口路边的地区被分割成块,并被编号成1..N。每个部分为一个单位尺寸大小并最多可种一棵树。每个居民房子门前被指定了三个号码B,E,T。这三个数表示该居民想在B和E之间最少种T棵树。当然,B≤E,居民必须记住在指定区不能种多于区域地块数的树,所以T≤E-B+l。居民们想种树的各自区域可以交叉。你的任务是求出能满足所有要求的最少的树的数量,尽量较少政府的支出。
//种树要种得少,就要使一棵树给多个区间使用。这样,尽量在重叠区间种树即可,而重叠位置一定是在区间尾部。 //处理问题时,先按所有区间的结束位置排序,之后依次处理每个区间,先在第一个区间尾部种满足要求的树,对下一个区间,看差多少棵就在该区间尾部种多少 int m,n; struct node{ int s,e,num; }poe[maxn]; int vis[30001]={0}; bool cmp(node a,node b){ return a.e<b.e; } int main(){ cin>>m>>n; memset(vis,0,sizeof(vis)); int ans=0; for(int i=1;i<=n;i++){ cin>>poe[i].s>>poe[i].e>>poe[i].num; } sort(poe+1,poe+1+n,cmp); int k=0; for(int i=1;i<=n;i++){ k=0; for(int j=poe[i].s;j<=poe[i].e;j++){ if(vis[j]) k++; } if(k>=poe[i].num) continue; //如果种满了就退出 else{ for(int j=poe[i].e;j>=poe[i].s;j--){ //从后面开始种树 if(!vis[j]){ vis[j]=1; ans++; k++; if(k>=poe[i].num) break; } } } } cout<<ans<<endl; return 0; }
九、喷水装置
这道题写的时候没有把握住细节(数学)和方法,根据能不能更新来判断,计算
int n,cnt,L,h,x,r; struct SEG{ double x,y; }a[20005]; bool cmp(const SEG &x,const SEG &y){ return x.x<y.x; } void Read(){ cin>>n>>L>>h; cnt=0; for(int i=1;i<=n;i++){ cin>>x>>r; if(r<=h/2) continue; cnt++; a[cnt].x=x-sqrt(r*r-h*h/4.0); a[cnt].y=x+sqrt(r*r-h*h/4.0); } } void solve(){ double t=0; int ans=0,bj=1,i=1; while(t<L){ //顺序扫描 ans++; double s=t; for(;a[i].x<=s&&i<=cnt;i++)//依次找到覆盖l的最大右端 if(t<a[i].y) t=a[i].y; if(t==s&&s<L){ cout<<-1<<endl; bj=0; break; } //无解判断 } if(bj) cout<<ans<<endl; } int main(){ int T; cin>>T; while(T--){ Read(); sort(a+1,a+1+cnt,cmp); solve(); } return 0; }
1425:【例题4】加工生产调度
有两条线:基础思想很简单,就是让两台机器总的等待时间最少
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=10010; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; //一下子没什么头绪 //但是想一想怎样让总时间最短?机器的等待时间最短!!从这里入手 //所以要让机器的运行时间最长 //在一开始和结束的时候,总是会有机器在等待,一开始是b机器等待,所以要让a上面时间最短的先执行,最后的时候让b上时间最短的执行 /* 可以大胆猜想,要使机器总的空闲时间最短,就要把在A机器上加工时间最短的部件最先加工,这样使得B机器能在最短的空闲时间内开始加工; 把在B机器上加工时间最短的部件放在最后加工,这样使得A机器用最短时间等待B机器完工 于是我们可以设计出这样的贪心策略: 将M按照从小到大的顺序排序,然后从第1个开始处理,若Mi=ai,则将它排在从头开始的作业后面,若Mi=bi,则将它排在从尾开始的作业前面。 */ int n; int mi[maxn],a[maxn],b[maxn],inde[maxn],ans[maxn]; int main(){ scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&a[i]); } for(int i=1;i<=n;i++) scanf("%d",&b[i]); for(int i=1;i<=n;i++){ mi[i]=min(a[i],b[i]); inde[i]=i; } //sort(mi+1,mi+1+n); //不能直接这样排序 for(int i=1;i<n;i++){ for(int j=i+1;j<=n;j++){ if(mi[i]>mi[j]){ swap(mi[i],mi[j]); swap(inde[i],inde[j]); } } } int k=0,t=n+1; for(int i=1;i<=n;i++){ if(mi[i]==a[inde[i]]){ ++k; ans[k]=inde[i]; } else{ --t; ans[t]=inde[i]; } } int t1=0,t2=0; //计算总时间 for(int i=1;i<=n;i++){ t1+=a[ans[i]]; if(t2<t1) t2=t1; t2+=b[ans[i]]; //从更大时间算起 } printf("%d\n",t2); for(int i=1;i<=n;i++) printf("%d ",ans[i]); return 0; }
1426:【例题5】智力大冲浪
很简单的题目,怎么这种题目都想不出思路?
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; //按照罚款从大到小排序 //排在最前面的,从那个时间开始算到0,占一个位置就好了,如果占不到就需要累加这个惩罚 struct node{ int tim,w; }a[510]; int vis[510]; int m,n; bool cmp(node a,node b){ return a.w>b.w; } int main(){ scanf("%d %d",&m,&n); for(int i=1;i<=n;i++){ scanf("%d",&a[i].tim); } for(int i=1;i<=n;i++){ scanf("%d",&a[i].w); } sort(a+1,a+1+n,cmp); int ans=0; for(int i=1;i<=n;i++){ bool flag=1; for(int j=a[i].tim;j>=1;j--){ if(!vis[j]){ vis[j]=1; flag=false; break; } } if(flag==1) ans+=a[i].w; } printf("%d\n",m-ans); return 0; }
1430:家庭作业
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1e6+10; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; int n; //这道题没有写明数据量,看了题解说是很大 //需要优化 //自设稍大的测试数据跟踪过程发现当前面期限排满后,程序依然会依次扫描所有时间轴,如果终止扫描的时间后推,那就不用把前面排满的时间轴再去扫描,大量的数据可以节约大把时间 struct node{ int t,w; bool operator <(const node a)const{ return w<a.w; } }; priority_queue<node> q; int vis[maxn]; int main(){ scanf("%d",&n); node tmp; for(int i=1;i<=n;i++){ scanf("%d %d",&tmp.t,&tmp.w); q.push(tmp); } LL ans=0,tt=0; while(!q.empty()){ tmp=q.top(); q.pop(); int i; for(i=tmp.t;i>tt;i--){ if(!vis[i]){ vis[i]=1;ans+=tmp.w; break; } } if(i==tt&&tt<tmp.t) tt=tmp.t; } printf("%lld\n",ans); return 0; }
1431:钓鱼
这道题不难,这是细节多多,建议多写几次
直接枚举每个点作为终点,然后取最优值
该题的难点主要在最后可在任意湖边停住,而且不能往回走,在一个湖钓鱼时的效率还会越来越少。常规的思路看来是不行的了,题目好多动态未知的量,唯有我们更换角度,“化动为静”:
即然最后不知道停在哪个湖,那就分类讨论呗。把停在每个湖的最优解全部求出,在最后取个最优解不就行了吗?发现当我们知道主人公最后停在哪个湖后,她的路径也就唯一确定了(例如佳佳最后停在了第i个湖,那么她的路径一定是1—》2—》3—》。。。—》i),同时她的纯钓鱼时间可由总空闲时间减去行程时间唯一确定。考虑从哪个湖钓鱼一个5分钟,就相当于在路径1—》2—》3—》。。。—》i中的一个节点上“堆”上一个标记表示在这个湖又钓了5分钟的鱼,显然这里可用贪心策略,每次标记目前为止五分钟钓鱼数目最大的那个湖,并使当前记录答案的sumi+=在那个湖又钓的鱼数。最后比较所有的sumi(i=1,2,...,n)取最大的输出就行了。
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; /* 该题的难点主要在最后可在任意湖边停住,而且不能往回走,在一个湖钓鱼时的效率还会越来越少。常规的思路看来是不行的了,题目好多动态未知的量,唯有我们更换角度,“化动为静”: 即然最后不知道停在哪个湖,那就分类讨论呗。把停在每个湖的最优解全部求出,在最后取个最优解不就行了吗?发现当我们知道主人公最后停在哪个湖后,她的 路径也就唯一确定了(例如佳佳最后停在了第i个湖,那么她的路径一定是1—》2—》3—》。。。—》i),同时她的纯钓鱼时间可由总空闲时间减去行程时间唯一确定。 考虑从哪个湖钓鱼一个5分钟,就相当于在路径1—》2—》3—》。。。—》i中的一个节点上“堆”上一个标记表示在这个湖又钓了5分钟的鱼,显然这里可用贪心策略, 每次标记目前为止五分钟钓鱼数目最大的那个湖,并使当前记录答案的sumi+=在那个湖又钓的鱼数。最后比较所有的sumi(i=1,2,...,n)取最大的输出就行了。 */ int n,h; vector<int> fish,lesss,t; ////每个湖第一个 5 分钟能钓到鱼的数量,每个湖每钓鱼5分钟较前5分钟钓的鱼数减少的数量,如题意 vector<int> dis,tempfish; //从第一个湖走到第i个湖所需时间,每个湖的当前5分钟能钓到的鱼数 void inti(){ fish.push_back(0); lesss.push_back(0); t.push_back(0); //题目输入的三个 dis.push_back(0); dis.push_back(0); //注意get数组在主函数是从下标为2的开始处理的,因此需要多填一个0。 tempfish.push_back(0); } ////为什么要填0?为了与普通全局数组的性质相同(定义时默认全初始化为0) int main(){ inti(); scanf("%d %d",&n,&h); h*=12; int x; for(int i=1;i<=n;i++){ scanf("%d",&x);fish.push_back(x); } for(int i=1;i<=n;i++){ scanf("%d",&x);lesss.push_back(x); } for(int i=1;i<n;i++){ scanf("%d",&x);t.push_back(x); } for(int i=2;i<=n;i++) dis.push_back(dis[i-1]+t[i-1]); int maxf,maxin,tmpt,res=0; //当前贪心找到的最大值,当前贪心找到的最大值对应的下标(即湖的编号),当前纯钓鱼时间,最后的答案。 for(int i=1;i<=n;i++) tempfish.push_back(0); for(int k=1;k<=n;k++){ //最后停留的地方 if(h>dis[k]){ tmpt=h-dis[k]; for(int i=1;i<=k;i++) tempfish[i]=fish[i]; //fish不要变 int summ=0; //tempfish的变化全在这下面 while(tmpt>0){ maxf=-INF; maxin=0; for(int i=1;i<=k;i++){ if(tempfish[i]>maxf){ maxf=tempfish[i]; maxin=i; } } if(maxf<0) break; //没鱼掉了 summ+=maxf; if(tempfish[maxin]>lesss[maxin]){ tempfish[maxin]-=lesss[maxin]; } else tempfish[maxin]=0; tmpt--; //别忘了这个 } if(summ>res) res=summ; } else break; } printf("%d",res); return 0; }
1432:糖果传递
这个可以算是环形的均分纸牌,其实代码很简单,但是公式推导可以列出来也不简单
我们将 a[i] 表示原来所拥有的糖 , x[i] 表示为 i 传递的(正数表示得到,负数表示给出)
我们有 a[i] + x[i] +x[i-1] =ave
所以有 x[i] 的递推公式 ,只需要求x[i] 与某个 x[k] 之差的和最小即可
贪心考虑 x[i] 的中位数
P2512 [HAOI2008]糖果传递 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int INF=0x3fffffff; typedef long long ll; const int mmax=1e6+10; int a[mmax],s[mmax]; /* 这題没有任何警告,采用的是long long型。 与均分纸牌不同在于,纸牌是一条线之中问移动了多少次,这里是一个环之中问移动的代价(糖果数)。 提示:这題形同“均分纸牌”,不过是一个环,而且每次只能移一个,等于是“数轴上距离的远近”,命題变成:有多个点,找一个点, 令到它们到这个点的距离之和最短。那就是找诸点中的中间点,类似“山区建小学”。 */ int main() { int n; ll sum=0; cin>>n; for(int i=1;i<=n;i++) cin>>a[i],sum+=a[i]; ll ave=sum/n,ans=0; for(int i=1;i<=n;i++) s[i]=a[i]+s[i-1]-ave;//减平均值后的前缀和 sort(s+1,s+1+n);//为求中位数 for(int i=1;i<=n;i++) ans+=abs(s[i]-s[(n+1)>>1]); cout<<ans<<endl; return 0; }
贪心策略:总是考虑在当前状态下局部最优的策略,一定满足最优子结构,不断地把问题归纳为更小的相似地子问题
拟阵:许多用贪心算法求解的问题,可以表示求带权拟阵的最大权独立子集问题
最少硬币问题:
给定面值,给定要付的钱,求最少需要多少硬币
!如果面值为1、2、5或者1、2、4、8这种才可以用贪心计算,只有面值任一硬币面值都大于比它小的的和,才能计算
//先排序 int mon[10]={1,2,5}; int all; cin>>all; int num[10]; for(int i=0;i<3;i++){ num[i]=all/mon[i]; all=all-mon[i]*num[i]; }
对于任意面值的求解:动态规划
1、打印最少硬币组合
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int INF=0x3fffffff; typedef long long LL; int money=251; int tot=5; int type[5]={1,5,10,25,50}; int num[maxn]; int path[maxn]; void prin(int *path,int s){ while(s){ cout<<path[s]<<" "; s-=path[s]; } } void sovle(){ for(int i=0;i<money;i++) num[i]=INF; num[0]=0; for(int j=0;j<tot;j++){ for(int i=type[j];i<money;i++){ if(num[i]>num[i-type[j]]+1){ num[i]=num[i-type[j]]+1; path[i]=type[j]; } } } } int main(){ int s; sovle(); while(cin>>s){ cout<<num[s]<<endl; prin(path,s); } return 0; }
2、每种硬币有数量限制
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int INF=0x3fffffff; //所有硬币组合,而且对硬币的数量有限制 int a[5]={1,5,10,25,50}; int dp[251][110]; int n; //先打表 void sovle(){ dp[0][0]=1; for(int i=0;i<5;i++){ for(int j=1;j<101;j++){ for(int k=a[i];k<251;k++) dp[k][j]+=dp[k-a[i]][j-1]; } } } int ans[251]={0}; int main(){ sovle(); for(int i=0;i<251;i++){ for(int j=0;j<101;j++) ans[i]+=dp[i][j]; //从0开始,因为dp[0][0]=1 } while(cin>>n){ cout<<ans[n]<<endl; } return 0; }
区间贪心:
一、区间不相交问题
总是选择左端点最大的区间
struct node{ int x,y; }a[maxn]; bool cmp(node a,node b){ if(a.x!=b.x) return a.x>b.x; //先按照左端点从大到小排序 else return a.y<b.y; //不然就是右端点从小到大 } sort(a,a+n,cmp); int ans=1,lastx=a[0].x; for(int i=1;i<n;i++){ if(a[i].y<=lastx){ ans++; lastx=a[i].x; } }
二、区间选点问题
最少确定多少个点,才能使每个闭区间都至少存在一个点,和区间不相交问题一样,只需要改成a[i].y<lastx就可以了
最小生成树算法:常用的是prim和kruskal算法,都是采用了贪心的思想,但是策略不一样。
三、活动安排问题
按照结束时间排序
struct node{ int st,ed; }ac[maxn]; bool cmp(node a,node b){ return a.ed<b.ed; } void solve(){ int n;cin>>n; for(int i=0;i<n;i++) cin>>ac[i].st>>ac[i].ed; sort(ac,ac+n,cmp); int ans=0; int lastend=-1; for(int i=0;i<n;i++){ if(ac[i].st>=lastend){ ans++; lastedn=ac[i].ed; } } return ans; }
四、区间覆盖问题
给出线段的左右端点,求最少要多少线段覆盖整个区间
先按照左端点排序,然后在剩下的里面选择左端点在R且右端点最大的
五、最优装载问题
六、多机调度问题
最长处理时间优先
背包问题
一、部分背包问题:可以选择拿一件物品地部分东西,这个完全可以用贪心,选择当前单位价值最大的
二、01背包问题:因为要和选和不选的两种情况比较,所以产生的子问题互为重叠,需要用动态规划