完结撒花
一.贪心
贪心是什么
有些题目只需要做出在当前看来最好的选择就可以获得最好的结果,而不需要考虑整体上的最优。贪心必须具备无后效性,既某个状态以前的过程不会影响以后的状态。
思路模板
1.看题意创建结构体(也可能不用结构体)
2.看题意用sort和cmp自定义排序(也可能不用排序)
3.开始计算得出结果(如何计算考验思维)
洛谷p2240 部分背包问题
知识点
1.此题判断单个价值比应该是 a.价值 / a.重量,但是按照a.价值 * b.重量和a.重量 * b.价值 可以规避使用浮点数和除法
2.如何判断背包有没有装满:即将装进去的东西>背包剩余容量
那背包还有容量怎么办?背包剩余容量 / 即将装进去的东西 * 价值就装完了
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
int n,mi,vi,t,i;
struct money{
int mi,vi;
}a[1050];
int cmp(money a,money b){
return a.vi*b.mi >b.vi*a.mi;
}
int main(){
cin>>n>>t;
for(int i=1;i<=n;i++){
cin>>a[i].mi>>a[i].vi;
}
sort(a+1,a+1+n,cmp);
double sum=0;
for(i=1;i<=n;i++){
if(a[i].mi>t)break;
sum+=a[i].vi;
t-=a[i].mi;
}
if(i<n){
sum+=1.0* t/a[i].mi *a[i].vi;
}
printf("%.2lf",sum);
return 0;
}
洛谷p1223 排队接水
知识点
1.排队取水,如何排序后如何确定每个人的等水时间:第一个人会被等n-1次,第二个人会被等n-2次…第i个人会被等n-i次
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
int n;
struct water{
int time,num;
}a[1050];
bool cmp(water a,water b){
return a.time<b.time;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i].time;
a[i].num=i;
}
sort(a+1,a+1+n,cmp);
double ans;
for(int i=1;i<=n;i++){
cout<<a[i].num<<" ";
}
cout<<endl;
for(int i=1;i<=n;i++){
ans+=i*a[n-i].time;
}
printf("%.2lf",1.0*ans/n);
return 0;
}
洛谷p1803 线段覆盖
知识点
1.这题显然只需要排序结束时间就是最优解,判断答案++的时候需要创建变量记录更新上一次比赛结束时间
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
int n,ans,l;
struct oi{
int ai,bi;
}a[100005];
int cmp(oi a,oi b){
return a.bi<b.bi;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i].ai>>a[i].bi;
}
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n;i++){
if(a[i].ai>=l){
ans++;
l=a[i].bi;
}
}
cout<<ans;
return 0;
}
洛谷p1090 合并果子
知识点
- priority_queue<int,vector,greater >q; 按升序排序的优先队列
- priority_queue<int,vector,less >q; 按降序排序的优先队列
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
int n,ai,ans;
int main(){
priority_queue<int,vector<int>,greater<int> >q;
cin>>n;
for(int i=1;i<=n;i++){
cin>>ai;
q.push(ai);
}
int a;
for(int i=1;i<n;i++){
a=q.top();
q.pop();
a+=q.top();
q.pop();
ans+=a;
q.push(a);
}
cout<<ans;
return 0;
}
洛谷p3817 小A的糖果
知识点
1.两个相邻的加起来不能大于某个数,肯定是减掉后面的比较赚,因为从第二个数开始都会影响相邻的两个数,第一个数只影响后面的一个数
2.三年竞赛一场空,不开long long见祖宗
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
long long n,ai,x,a[100005],ans;
int main(){
cin>>n>>x;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){//i等于1就可以特判a【0】了
if(a[i]+a[i-1]>x){
ans+=a[i]+a[i-1]-x;
a[i]-=a[i]+a[i-1]-x;
}
}
cout<<ans;
return 0;
}
洛谷p1478 陶陶摘苹果(升级版)
知识点
1.毫无技巧全是感情
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
int n,s,ans,a,b;
struct apple{
int xi,yi;
}aa[105000];
int cmp(apple a,apple b){
return a.yi<b.yi;
}
int main(){
cin>>n>>s>>a>>b;
for(int i=1;i<=n;i++){
cin>>aa[i].xi>>aa[i].yi;
}
sort(aa+1,aa+1+n,cmp);
for(int i=1;i<=n;i++){
if(b+a>=aa[i].xi && aa[i].yi<=s){
ans++;
s-=aa[i].yi;
}
}
cout<<ans;
return 0;
}
洛谷p5019 铺设道路
知识点
1.铺路,值大减去,值小白嫖 ,请仔细审题,注意题解已经是特判了a【1】的
因为
假设现在有一个坑,但旁边又有一个坑。
你肯定会选择把两个同时减1;
那么小的坑肯定会被大的坑“带着”填掉。
但是如果后面的数大于了前面的数,那么前面的数为0,根据题意区间每块填充不能为0,就只能要单独加上大于前面的数值
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
int n,a[1000005],ans;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
if(a[i]>a[i-1]){
ans+=a[i]-a[i-1];
}
}
cout<<ans;
return 0;
}
洛谷p1208 混合牛奶
知识点
1.毫无技巧,全是模板
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
int n,m,ans;
struct milk{
int pi,ai;
}a[50500];
int cmp(milk a,milk b){
return a.pi<b.pi;
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>a[i].pi>>a[i].ai;
}
sort(a+1,a+1+m,cmp);
for(int i=1;i<=m;i++){
if(a[i].ai<=n){
n-=a[i].ai;
ans+=a[i].pi*a[i].ai;
}
else{
ans+=n * a[i].pi;
break;
}
}
cout<<ans;
return 0;
}
洛谷p1094 纪念品分组
知识点
贪心算法并不难,难的是证明。 — 沃夏纪霸索德
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
int w,n,a[100005],ans;
int main(){
cin>>w>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
sort(a+1,a+1+n);
int l=1,r=n;
while(l<=r){
if(a[l]+a[r]<=w){
ans++;
l++;
r--;
}else{
ans++;
r--;
}
}
cout<<ans;
return 0;
}
二.二分
二分是什么
如果序列是有序的,就可以通过二分查找快速定位所需要的数据,除此之外,二分思想还可以求出可行解的最值问题,比如可行解的最大值或最小值
思路模板
1.查找一个值
int f(int b){
int l=1,r=n;
while(l<=r){
int mid=l+(r-l)/2;
if(a[mid]==b)return mid;
if(a[mid]<b)l=mid+1;
else r=mid-1;
}
return -1;
}
2.查找有序数组中第一次出现的位置
int f(int b){
int l=1,r=n;
while(l<r){
int mid=l+(r-l)/2;
if(a[mid]>=b){
r=mid;
}
else l=mid+1;
}
if(a[l]==b)return l;
else return -1;
}
3.查找有序数组中最后一次出现的位置 可以变换得到 最后一个小于等于 某个值 的数
int f(int b){
int l=1,r=n;
while(l<r){
int mid=l+(r-l)/2;
if(a[mid]<=b){
l=mid+1;
}
else r=mid;
}
if(a[l-1]==b)return l-1;
else return -1;
}
洛谷p1102 A-B数对
知识点
1.lower_bound(a,a+n,3)-a找a数组中3第一次出现的位置的下标 (已经排序)
upper_bound(a,a+n,3)-a找a数组中3最后一次出现的位置的后面一位的下标 (已经排序)
那么数出现的次数可以表示为 upper_bound(a,a+n,3)-lower_bound(a,a+n,3)
2.计算出数对A-B=C出现的个数,可以转化为B+C出现了多少次
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<queue>
using namespace std;
long long n,c,sum;
long long a[1000500];
int main(){
cin>>n>>c;
for(int i=1;i<=n;i++){
cin>>a[i];
}
sort(a+1,a+1+n);
for(int i=1;i<=n;i++){
sum+=upper_bound(a+1,a+1+n,a[i]+c) - lower_bound(a+1,a+1+n,a[i]+c);
}
cout<<sum;
return 0;
}
洛谷p1873 砍树
知识点
1.运用函数判断二分的数是否满足条件,满足条件后再判断能不能最小
2.运用变量记录更新答案能不能最小(此题需要数尽量大)
#include<bits/stdc++.h>
using namespace std;
long long n,m,tot;
long long a[1005000];
bool f(long long b){
long long ans=0;
for(int i=1;i<=n;i++){
if(a[i]>b){
ans+=a[i]-b;
}
}
if(ans>=m)return true;
else return false;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
long long l=1,r=1000000000,tot=0;
while(l<=r){
long long mid=l+(r-l)/2;
if(f(mid)){//满足条件了求能不能最小
tot=mid;
l=mid+1;
}
else r=mid-1;
}
cout<<tot;
return 0;
}
洛谷p1678 烦恼的高考志愿
知识点
1.考点:最后一个小于等于 某个值 的数 套用二分模板可得到 a[l-1]是最后一个小于学生成绩的数
a[l]是第一个大于学生成绩的数 ,它们两个数中肯定有正确答案
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
int n,m,a[10000050],b[10000050],ans;
int main(){
cin>>m>>n;
for(int i=1;i<=m;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
cin>>b[i];
}
sort(a+1,a+1+m);
for(int i=1;i<=n;i++){
int l=1,r=m;
while(l<r){//可得到 a[l-1]是最后一个小于学生成绩的数
//a[l]是第一个大于学生成绩的数
int mid=l+(r-l)/2;
if(a[mid]<=b[i]){
l=mid+1;
}else{
r=mid;
}
}
if(b[i]<=a[1])ans+=a[1]-b[i];
else{
ans+=min(abs(b[i]-a[l-1]),abs(b[i]-a[l]));
}
}
cout<<ans;
return 0;
}
洛谷p2440 木材加工
知识点
函数判断+求满足条件的最大值
#include<iostream>
#include<cmath>
#include<cstdio>
#include<algorithm>
#include<string>
#define shu 105000000
using namespace std;
long long n,k,a[shu];
bool f(int b)
{
long long tot=0;
for(int i=1;i<=n;i++)
{
tot+=a[i]/b;
}
return tot>=k;
}
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
long long l=0,r=100000000,ans;
while(l<=r)
{
long long mid=l+(r-l)/2;
if(mid==0)
{
cout<<"0"<<endl;
return 0;
}
if(f(mid))
{
ans=mid,l=mid+1;
}
else r=mid-1;
}
cout<<ans;
return 0;
}
洛谷p1824 进击的奶牛
知识点
1.函数判断的技巧,建立变量记录上一头牛的坐标,两坐标相减可以得到间距
2.二分答案,找符合条件中的最大值模板
#include<cstdio>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
long long n,c,a[100050];
bool f(int b){
long long last=-10000000,sum=0;
for(int i=1;i<=n;i++){
if(a[i]-last>=b){
sum++;
last=a[i];
}
}
return sum>=c;
}
int main(){
cin>>n>>c;
for(int i=1;i<=n;i++){
cin>>a[i];
}
sort(a+1,a+1+n);
long long l=1,r=a[n];
long long ans=0;
while(l<=r){
int mid=l+(r-l)/2;
if(f(mid)){
ans=mid;
l=mid+1;
}else{
r=mid-1;
}
}
cout<<ans;
return 0;
}
三.搜索
搜索是什么
搜索算法也是一种暴力枚举策略。但是算法特性决定了效率比直接的枚举所以答案要高,使用搜索可以解决一些小规模的情况,分为dfs深搜(栈的思想)和bfs广搜(队列思想)
思路模板
dfs一般形式:
void dfs(int k){
if(所以空填完了){
记录答案
return ;
}
for(枚举这个空能填的选项){
if(条件都通过){
记录下这个空(保存现场)
dfs(k+1);
取消这个空(回溯恢复现场)
}
}
}
dfs一般形势:
while(!q.enpty())
{
int first=q.front();
q.pop();
for(枚举所有可扩展状态) //找到first所以可达状态v
if(是合法的) //v需要满足条件,比如未访问过、未在队内
q.push(v); //入队(同时可能需要维护某些必要信息)
}
dfs不能搜最短、最小
bfs搜最短、最小
关于要不要恢复现场:枚举所有可能的状态,如果不恢复现场会不会污染其他状态
个体:比如点、连通块可以标记,就没必要恢复现场
整体:比如全排列、地图,枚举的是路径,有必要恢复现场
如果出题人说了这么一句废话:“按字典序输出”就是要我们从小到大枚举.
洛谷p1706 全排列问题 dfs
知识点
搜索模板
#include<cstdio>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int n,a[50],vis[50];
void dfs(int b){
if(b>n){//所以空填完了,然后输出
for(int i=1;i<=n;i++){
printf("%5d",a[i]);
}
cout<<endl;
return ;
}
for(int i=1;i<=n;i++){
if(vis[i]==0){
vis[i]=1;//用了就不能再用,因为数字不能相同
a[b]=i;//记录答案
dfs(b+1);//这里会完成或失败
vis[i]=0;//完成或失败后回溯
}
}
}
int main(){
cin>>n;
dfs(1);
return 0;
}
洛谷p1219 八皇后 dfs
知识点
八皇后,需要判断四个点:行,列,左斜,右斜
行的判断,dfs x往下走++就不可能在同一行,不用特判
列的判断,for循环列i++,我们可以建立数组记录,用了就变为1,没用就是0
左上斜边 可以发现行减列数字相同 zs[x-i+n] 加n防止越界
右上斜边 可以发现行和列加起来数字相同 ys[x+n]
#include<cstdio>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int n,ans;
int a[1050];//记录答案
int column[1050];//记录列的访问
int zs[1050];//记录左上到右下的那条
int ys[1050];//记录右上到左下的那条
void pr(){
for(int i=1;i<=n;i++){
cout<<a[i]<<" ";
}
cout<<endl;
}
void dfs(int x){
if(x>n){
if(ans<3){
pr();
}
ans++;
return ;
}
for(int i=1;i<=n;i++){
if(!column[i] && !zs[x-i+n] && !ys[x+i]){
column[i]=1;
zs[x-i+n]=1;
ys[x+i]=1;
a[x]=i;
dfs(x+1);
column[i]=0;
zs[x-i+n]=0;
ys[x+i]=0;
}
}
}
int main(){
cin>>n;
dfs(1);
cout<<ans;
return 0;
}
洛谷p1605 迷宫 dfs
知识点
1.起点需要记录用过
2.障碍物,路线都需要记录
#include<cstdio>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int n,m,t,diex,diey,ans,startx,starty,endx,endy;
int vis[20][20];//障碍
int a[20][20];//路线,走过了要记录
int aa[]={-1,1,0,0};
int bb[]={0,0,-1,1};
void dfs(int x,int y){
if(x==endx && y==endy){
ans++;
return ;
}
for(int i=0;i<4;i++){
int dx=aa[i]+x;
int dy=bb[i]+y;
if(dx>=1 && dx<=n && dy>=1 && dy<=m && !vis[dx][dy] && !a[dx][dy] ){
a[dx][dy]=1;
dfs(dx,dy);
a[dx][dy]=0;
}
}
}
int main(){
cin>>n>>m>>t;
cin>>startx>>starty>>endx>>endy;
for(int i=1;i<=t;i++){
cin>>diex>>diey;
vis[diex][diey]=1;
}
vis[startx][starty]=1;
dfs(startx,starty);
cout<<ans;
return 0;
}
洛谷p1101 单词方阵 dfs
知识点
此题先找到第一个字母,往它的八个方向判断,如果全部符合,vis记录输出本身,否则输出*
#include<bits/stdc++.h>
using namespace std;
int n,vis[105][105];
char a[105][105];
string s="yizhong";
int xx[]={1,1,1,0,0,-1,-1,-1};
int yy[]={1,0,-1,1,-1,1,0,-1};
void dfs(int x,int y){
for(int i=0;i<8;i++){
int flag=1;
for(int j=0;j<7;j++){
int dx=j*xx[i]+x;
int dy=j*yy[i]+y;
if(dx<1||dx>n||dy<1||dy>n||s[j]!=a[dx][dy]){
flag=0;
break;
}
}
if(flag){
for(int j=0;j<7;j++){
int dx=j*xx[i]+x;
int dy=j*yy[i]+y;
vis[dx][dy]=1;
}
}
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>a[i][j];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(a[i][j]=='y'){
dfs(i,j);
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(!vis[i][j]){
cout<<"*";
}
else {
cout<<a[i][j];
}
}
cout<<endl;
}
return 0;
}
洛谷p1162 填图颜色 bfs
知识点
运用bfs,为了方便搜索,假设外面还有一圈0,从0,0开始遍历,遍历过的用vis数组记录,这样没有被记录过的0就是要改变的数
#include<bits/stdc++.h>
#include<queue>
using namespace std;
int n,a[50][50],vis[50][50];
int aa[]={1,0,0,-1};
int bb[]={0,-1,1,0};
int main()
{
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>a[i][j];
}
}
queue<int>x;
queue<int>y;
x.push(0);
y.push(0);
while(!x.empty()){
x.pop();
y.pop();
for(int i=0;i<4;i++){
int dx=aa[i]+x.front();
int dy=bb[i]+y.front();
if(dx>=0&&dx<=n+1&&dy>=0&&dy<=n+1&&a[dx][dy]==0&&!vis[dx][dy]){//外面还有一圈0
vis[dx][dy]=1;
x.push(dx);
y.push(dy);
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(vis[i][j]==0&&a[i][j]==0){
cout<<"2 ";
}
else cout<<a[i][j]<<" ";
}
cout<<endl;
}
return 0;
}
DFS代码
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
int n;
int a[50][50];
int vis[50][50];
int aa[]={-1,1,0,0};
int bb[]={0,0,-1,1};
void dfs(int x,int y){
for(int i=0;i<4;i++){
int dx=aa[i]+x;
int dy=bb[i]+y;
if(dx>=0&&dx<=n+1&&dy>=0&&dy<=n+1&&!vis[dx][dy]&&a[dx][dy]==0){
vis[dx][dy]=1;
dfs(dx,dy);
}
}
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>a[i][j];
}
}
dfs(0,0);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(!vis[i][j] && a[i][j]==0){
cout<<"2"<<" ";
}else{
cout<<a[i][j]<<" ";
}
}
cout<<endl;
}
return 0;
}
洛谷p1443 马的遍历 bfs
知识点
队列与结构体的运用,广搜,初始化数组全部是-1
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#include<cstdio>
using namespace std;
int n,m,mx,my;
int a[400][400];
int aa[]={2,2,1,1,-1,-1,-2,-2};
int bb[]={1,-1,2,-2,2,-2,1,-1};
struct horse{
int x,y,s;
};
void bfs(int x,int y){
queue<horse>Q;
Q.push((horse){x,y,0});
while(!Q.empty()){
for(int i=0;i<8;i++){
int dx=aa[i]+Q.front().x;
int dy=bb[i]+Q.front().y;
if(dx>=1&&dx<=n&&dy>=1&&dy<=m&&a[dx][dy]==-1){
a[dx][dy]=Q.front().s+1;
Q.push((horse){dx,dy,Q.front().s+1});
}
}
Q.pop();
}
}
int main()
{
cin>>n>>m>>mx>>my;
memset(a,-1,sizeof(a));
a[mx][my]=0;
bfs(mx,my);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
printf("%-5d",a[i][j]);
}
cout<<endl;
}
return 0;
}
洛谷p1135 奇怪的电梯 bfs
知识点
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#include<cstdio>
using namespace std;
int n,a,b;
int ans[500],vis[500];
int main()
{
cin>>n>>a>>b;
for(int i=1;i<=n;i++){
cin>>ans[i];
}
queue<int>x;
queue<int>s;
x.push(a);
s.push(0);
while(!x.empty()){
if(x.front()==b){
cout<<s.front();
return 0;
}
int dx=x.front()+ans[x.front()];
int dy=x.front()-ans[x.front()];
if(dx<=n&&!vis[dx]){
vis[dx]=1;
x.push(dx);
s.push(s.front()+1);
}
if(dy>=1&&!vis[dy]){
vis[dy]=1;
x.push(dy);
s.push(s.front()+1);
}
x.pop();
s.pop();
}
cout<<"-1";
return 0;
}