算法题目回顾

1.枚举

lanqiao  OJ 512 反倍数

枚举所有的数字 用一个函数去判断某个数字是否是特别的数,将满足条件的数字个数求和

lanqiao  OJ 3227 找到最多的数字

枚举整个整个矩阵的所有数字,用map来存储所有的数字出现的次数,最后遍历map找出出现的次数>n*m/2即可

知识点:

1.map<int,int> mp; mp[x]++;

2.遍历map   for(const auto&[x,y]:mp)

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
map<int,int>mp;
int main(){
  int n,m;
  cin>>n>>m;
  int x;
  for(int i=0;i<n;i++){
    for(int j=0;j<m;j++){
      cin>>x;
      mp[x]++;
    }
  }
  for(const auto&[x,y]:mp){
    if(2*y>m*n) cout<<x;
  }
  return 0;
}

小蓝的漆房

1.枚举整个走廊需要被涂上的颜色

2.对于每种颜色,计算涂上它所需要的最少天数,我们可以从左到右遍历每个房子,如果该房子的颜色不是当前正在涂的颜色,那么我们就从该房子开始,向右涂k个房子,直到将区间都涂上目标颜色

3.取最小值

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;

int a[N],b[N];
int main(){
  int t;
  cin>>t;
  while(t--){
    int n,k,res=0x3f3f3f3f;
    cin>>n>>k;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=60;i++){
      int cnt=0;
      for(int j=1;j<=n;j++) b[j]=a[j];
      for(int j=1;j<=n;j++){
        if(b[j]!=i){//不是同一个颜色
          for(int h=j;h<=j+k-1;h++) b[h]=i;
          j=j+k-1;
          cnt++;
        }
      }
      res=min(res,cnt);
    }
    cout<<res<<endl;
  }
  return 0;
}

2.模拟

lanqiao OJ 549 扫雷

遍历每个位置,特判当前点的位置有地雷的情况,扫描九宫格更新地雷数量即可

#include <iostream>
using namespace std;
const int N=150;
int mp[N][N],ans[N][N];
int main()
{
  int n,m;
  cin>>n>>m;
  for(int i=1;i<=n;i++){
    for(int j=1;j<=m;j++){
      cin>>mp[i][j];
    }
  }
  for(int i=1;i<=n;i++){
    for(int j=1;j<=m;j++){
      if(mp[i][j]==1){
        ans[i][j]=9;
        continue;
      }
      //扫描四个方位
      for(int _i=max(1,i-1);_i<=min(n,i+1);++_i){
        for(int _j=max(1,j-1);_j<=min(m,j+1);++_j){
          if(mp[_i][_j]) ans[i][j]++;
        }
      }
    }
  }
  for(int i=1;i<=n;i++){
    for(int j=1;j<=m;j++){
      cout<<ans[i][j]<<" ";
    }
    cout<<endl;
  }
  return 0;
}

lanqiao OJ 551 浇灌

用两个二维数组分别表示当前和后一分钟的灌溉情况  当前浇灌数组a,四个方位标记,数组b进行标记,再将数组b赋给数组a,此时数组a表示当前的灌溉情况。计算灌溉数目时候,应该在外面进行计算,在扫描四个方位时候进行灌溉,会重复记录。

#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n,m,t,k;
int a[N][N],b[N][N];
int main(){
  cin>>n>>m;
  cin>>t;
  int x,y;
  for(int i=0;i<t;i++){
    cin>>x>>y;
    b[x][y]=1;
  }
  cin>>k;
  int cnt=0;
  while(k--){
    for(int i=1;i<=n;i++){
      for(int j=1;j<=m;j++){
        if(b[i][j]==1){//两个数组交替标记 表示此分钟和下一分钟 不然会搞混
          a[i-1][j]=1;//cnt标记不能在这标记 会有重合的点 必须要在外面统一跑一遍
          a[i+1][j]=1;//注意i-1开头必须在1开始
          a[i][j+1]=1;
          a[i][j-1]=1;
        }
      }
    }
    for(int i=1;i<=n;i++){
      for(int j=1;j<=m;j++){
        b[i][j]=a[i][j];
      }
    }
  }
  for(int i=1;i<=n;i++){
    for(int j=1;j<=m;j++){
      if(b[i][j]==1) cnt++;
    }
  }
  cout<<cnt;
  return 0;
}

小蓝和小桥的挑战

至少执行操作数,积不可为0,先把所有为0的数全改为1,再算和是否为0,若和为0,再将整体加1

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;

int a[N],t,n;
int main(){
  cin>>t;
  while(t--){
    cin>>n;
    int sum=0,z=0;
    for(int i=0;i<n;i++){
      cin>>a[i];
      sum+=a[i];//看看和是否为0
      if(!a[i]) z++;//0的个数有多少个 积改0的个数
    }
    sum+=z;
    if(sum==0) cout<<z+1<<endl;//和为0 只需要改一个
    else cout<<z<<endl;
  }
  return 0;
}

DNA序列的修正

采用数字和方式代替碱基配对

map键值对

//利用数字来进行求和查看是不是匹配
#include<bits/stdc++.h>
using namespace std;
map<char,int>mp{//利用键值对
  {'A',0},
  {'C',1},
  {'G',2},
  {'T',3}
};
int main(){
  int n;
  cin>>n;
  string a,b;
  cin>>a>>b;
  int cnt=0;
  for(int i=0;i<n;i++){
    if(mp[a[i]]+mp[b[i]]!=3){
      for(int j=i+1;j<n;j++){
        if(mp[a[i]]+mp[b[j]]==3&&mp[a[j]]+mp[b[i]]==3){
          swap(b[i],b[j]);
          break;
        }
      }
      cnt++;
    }
  }
  cout<<cnt<<endl;
  return 0;
}

3.递归

lanqiao OJ 760

#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int a[N];

int dfs(int dep){
  int res=1;
  for(int i=a[dep-1]/2;i>=1;i--){
    a[dep]=i;
    res+=dfs(dep+1);
  }
  return res;
}
// 6 3 1 1走到头返回res res=1 res+=1 res=2 再return res res+=res=3 
int main(){
  int n;
  cin>>n;
  a[1]=n;
  cout<<dfs(2)<<endl;
  return 0;
}

计算函数值

#include<bits/stdc++.h>
using namespace std;
int f(int x){
    if(x==0)return 1;
    if(x%2)return f(x-1)+1;
    return f(x/2);
}

int main(){
    int n;
    cin>>n;
    cout<<f(n);
    return 0;
}

4.进制转化

1.任意进制转换为十进制

ll x=0;
for(int i=1;i<=n;i++){
  x=x*k+a[i];
}
cout<<x<<endl;

2.将十进制转换为任意进制

ll x;
cin>>x;
while(x){ a[++cnt]=x%k; x/k;}
reverse(a+1,a+1+cnt);

lanqiao OJ 1230 

先将N进制数字转换为10进制,再将10进制转换为M进制

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int N = 10000;
int a[N];
char ch[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};

void solve()
{
  int n,m;cin >> n >> m;
  string s;cin >> s;
  //n进制转10进制
  ll x=0;
  for(int i=0;i<s.length();i++)
  {
    if('0'<= s[i] && s[i]<= '9')a[i+1] = s[i]-'0';
    else a[i+1] = s[i] - 'A' + 10; 
  }
  for(int i=1;i<=s.length();i++)
  {
    x = x*n + a[i];
  }
  //10进制转换m进制;
  string ans;
  while(x)
  {
    ans += ch[x % m];
    x/=m;
  }
  reverse(ans.begin(),ans.end());
  cout << ans <<'\n';
}

int main()
{
  ios::sync_with_stdio(0),cout.tie(0),cin.tie(0);
  int t;
  cin >> t;
  for(int i=1;i<=t;i++)solve();
  return 0;
}

5.前缀和

lanqiao OJ  3382 区间次方和

由于k较小 所以可以处理五个数组分别表示不同的次方,例如a3[]中的元素都是数组a中的元素的3次方。再对五个数组进行预处理出前缀和。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll p=1e9+7;
const int N=1e5+10;
ll a[6][N],prefix[6][N];
int main(){
  ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
  int n,m;
  cin>>n>>m;
  for(int i=1;i<=n;i++) cin>>a[1][i];
  for(int i=2;i<=5;i++){
    for(int j=1;j<=n;j++){
      a[i][j]=a[i-1][j]*a[1][j]%p;
    }
  }
  for(int i=1;i<=5;i++){
    for(int j=1;j<=n;j++){
      prefix[i][j]=(prefix[i][j-1]+a[i][j])%p;
    }
  }
  while(m--){
    int l,r,k;
    cin>>l>>r>>k;
    cout<<(prefix[k][r]-prefix[k][l-1]+p)%p<<endl;
  }
  return 0;
}

lanqiao OJ 3419 小郑的平衡串(字符看成数字进行前缀和)

将L看做1,Q看做-1,只有当某个区间和为0时,字符串是平衡的

可以预处理前缀和,然后枚举所有区间,得到所有平衡区间的最大长度进行输出

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int main(){
  string str;
  getline(cin,str+1);
  int a[N];
  for(int i=1;i<=str.length()+1;i++){
    if(str[i]=='L'){
      a[i]=1;
    }else{
      a[i]=-1;
    }
  }
  int pre[N];
  pre[0]=0;
  for(int i=1;i<=str.length();i++){
    pre[i]=pre[i-1]+a[i];
  }
  //是一段距离 并非从头开始计算 
  int mx=0;
  for(int i=1;i<=str.length();i++){
    for(int j=i;j<=str,length();j++){
      if((pre[j]-pre[i-1])==0){
        mx=max(j-i+1,mx);
      }
    }
  }
  cout<<mx;
  return 0;
}

大石头的搬运工(两头前缀和)

无论我们怎么移动石头,最后的总费用只依赖于每个石头最后的位置,而与移动的顺序无关

我们运用前缀和的思想,考虑到石头移动的费用与重量和距离有关,我们可以先将石头按位置排序,然后计算每个石头移动到任意位置的费用

先对初始位置进行排序,从头到尾进行前缀和(重量和位置),从尾到头进行前缀和(重量和位置) ,然后两个前缀和的交点进行取最小

#include<bits/stdc++.h>

#define x first 
#define y second
using namespace std;

typedef long long ll;
typedef pair<int,int> PII;
const int N=1e5+10;

int n;
PII q[N];
ll pre[N],nex[N];

int main(){
  cin>>n;
  for(int i=1;i<=n;i++){
    cin>>q[i].y>>q[i].x;//重量 初始位置
  }
  sort(q+1,q+n+1);//对初始位置进行排序
  ll s=0;
  //对重量和位置进行前缀和
  for(int i=1;i<=n;i++){
    pre[i]=pre[i-1];
    pre[i]+=s*(q[i].x-q[i-1].x);
    s+=q[i].y;//重量
  }
  s=0;
  for(int i=n;i>=1;i--){
    nex[i]=nex[i+1];
    nex[i]+=s*(q[i+1].x-q[i].x);
    s+=q[i].y;//重量
  }
  ll res=1e18;//开大
  for(int i=1;i<=n;i++){
    res=min(res,pre[i]+nex[i]);
  }
  cout<<res<<endl;
  return 0;
}

最大数组和(避免贪心)

首先将宝石价值大小进行从小到大排序,然后进行前缀和数组,利用双指针 枚举所有情况 避免贪心 因为可能一大两小的总体价值比两大价值要小

#include<bits/stdc++.h>
using namespace std;
int t,n,k;
const int N=200005;
typedef long long ll;
ll a[N],sum[N];

int main(){
  cin>>t;
  while(t--){
    cin>>n>>k;
    for(int i=1;i<=n;i++){
      cin>>a[i];
    }
    sort(a+1,a+n+1);
    memset(sum,0,sizeof sum);
    for(int i=1;i<=n;i++){//前缀和下标从1开始 
      sum[i]=sum[i-1]+a[i];
    }
    ll ans=0;
    int pos=0;
    while(k>=0){//利用双指针 枚举所有情况 避免贪心
      ans=max(ans,sum[n-k]-sum[pos]);
      pos+=2;
      k--;
    }
    cout<<ans<<endl;
  }
  return 0;
}

6.差分

lanqiao OJ 3291 区间更新

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N],diff[N];
int main(){
  int n,m;
  while(cin>>n>>m){
    for(int i=1;i<=n;i++){
      cin>>a[i];
    }
    a[0]=0;
    for(int i=1;i<=n;i++){
      diff[i]=a[i]-a[i-1];
    }
    while(m--){
      int l,r,v;
      cin>>l>>r>>v;
      diff[l]+=v;
      diff[r+1]-=v;
    }
    //前缀和还原
    for(int i=1;i<=n;i++) a[i]=a[i-1]+diff[i];
    for(int i=1;i<=n;i++){
      cout<<a[i]<<" ";
    }
    cout<<endl;
  }
  return 0;
}

两种差分方法 

lanqiao OJ 2176 小明的彩灯

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;//1e9 开longlong 
const int N=1e6;
int main(){
  ll a[N],diff[N];
  int n,q;
  cin>>n>>q;
  a[0]=0;
  for(int i=1;i<=n;i++){
    cin>>a[i];
  }
  for(int i=1;i<=n;i++){//差分从1开始
      diff[i]=a[i]-a[i-1];
  }
  while(q--){
    int l,r,x;
    cin>>l>>r>>x;
    diff[l]+=x;
    diff[r+1]-=x;//差分从r+1开始
  }
  for(int i=1;i<=n;i++){//前缀和进行还原
    a[i]=a[i-1]+diff[i];
  }
  for(int i=1;i<=n;i++){
    if(a[i]<0){
      a[i]=0;
    }
    cout<<a[i]<<" ";
  }
  return 0;
}

泡澡

需要计算每一分钟需要消耗的热水量  统计得到每一分钟需要的热水数量 
sl[i]+=p; sr[i]-=p 最后对数组S进行求前缀和 得到某一时刻X的热水使用量为Sx 检查是否超过M

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,w;
const int N=200010;
int main(){
  cin>>n>>w;
  vector<ll> s(N);
  for(int i=0;i<n;i++){
    int l,r,c;
    cin>>l>>r>>c;
    s[l]+=c;
    s[r]-=c;
  }
  //前缀和进行还原
  for(int i=1;i<N;i++) s[i]+=s[i-1];
  for(int i=0;i<N;i++){
    if(s[i]>w){
      cout<<"No"<<endl;
      return 0;
    }
  }
  cout<<"Yes"<<endl;
  return 0;
}

肖恩的投球游戏

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100010;

int n,q;
ll a[N],b[N];
int main(){
  ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
  cin>>n>>q;
  for(int i=1;i<=n;i++) cin>>a[i];
  for(int i=1;i<=q;i++){
    int l,r,c;
    cin>>l>>r>>c;
    b[l]+=c,b[r+1]-=c;
  }
  for(int i=1;i<=n;i++) b[i]+=b[i-1];
  for(int i=1;i<=n;i++) cout<<a[i]+b[i]<<" ";
  return 0;
}

肖恩的投球游戏加强版(二维版)先构造差分数组 再进行加减 再进行前缀和还原

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1010;
ll a[N][N],s[N][N];
int n,m,q;

void insert(int x1,int y1,int x2,int y2,int c){
  s[x1][y1]+=c;
  s[x1][y2+1]-=c;
  s[x2+1][y1]-=c;
  s[x2+1][y2+1]+=c;
}
int main(){
  ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
  cin>>n>>m>>q;
  for(int i=1;i<=n;i++){
    for(int j=1;j<=m;j++){
      cin>>a[i][j];
      insert(i,j,i,j,a[i][j]);
    }
  }
  while(q--){
    int x1,y1,x2,y2,c;
    cin>>x1>>y1>>x2>>y2>>c;
    insert(x1,y1,x2,y2,c);
  }
  //求原数组
  for(int i=1;i<=n;i++){
    for(int j=1;j<=m;j++){
      a[i][j]=a[i-1][j]+a[i][j-1]-a[i-1][j-1]+s[i][j];
      cout<<a[i][j]<<" ";
    }
    cout<<endl;
  }
  return 0;
}

 7.贪心

lanqiao OJ 3412 简单排序模型

差距最小 先排序 对相邻两数差距进行求最小值

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long ll;
int main(){
  int n;
  cin>>n;
  int w[N];
  for(int i=1;i<=n;i++){
    cin>>w[i];
  }
  sort(w+1,w+1+n);
  int mn=w[2]-w[1];
  for(int i=1;i<n;i++){
    mn=min(mn,w[i+1]-w[i]);//min函数 中不可用long long
  }
  cout<<mn;
  return 0;
}

lanqiao OJ545  总操作数一定情况下最小代价模型

每次选择合并代价小的部落合并

不仅可以使得当前代价最小 还可以使得后序合并的代价也尽可能小 部落大小通过优先队列来维护

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
priority_queue<ll,vector<ll>,greater<ll>> pq;//按升序排列 头部最小
int main(){
  int n;
  cin>>n;
  for(int i=1;i<=n;i++){
    ll x;
    cin>>x;
    pq.push(x);
  }
  ll ans=0;
  while(pq.size()>1){
    ll x=pq.top();pq.pop();
    ll y=pq.top();pq.pop();

    ans+=x+y;
    pq.push(x+y);//压进去自动排序
  }
  cout<<ans<<endl;
  return 0;
}

lanqiao OJ 532  最少数目的贪心模型

一个最贵的一个最便宜的占一组

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+9;
int main()
{
  int w,n;
  cin>>w>>n;
  int a[N];
  for(int i=1;i<=n;i++) cin>>a[i];
  sort(a+1,a+1+n);
  int l=1,r=n,ans=0;//利用双指针
  while(l<=r){
    ans++;
    if(l==r){
      break;
    }
    if(a[l]+a[r]<=w){
      l++;
      r--;
    }else{
      r--;
    }
  }
  cout<<ans<<endl;
  return 0;
}

最大卡牌价值

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long ll;
bool cmp(int a,int b){
  return a>b;
}
int n,k;
ll a[N],b[N],c[N],ans=0;
int main(){
  cin>>n>>k;
  for(int i=1;i<=n;i++){
    cin>>a[i];
    ans+=a[i];
  }
  for(int i=1;i<=n;i++){
    cin>>b[i];
    c[i]=b[i]-a[i];
  }
  sort(c+1,c+1+n,cmp);//降序
  k=min(n,k);//细节 题目中没有告诉谁大谁小
  while(k){
    if(c[k]>=0) ans+=c[k];//必须大于0才能翻 还有k卡条件
    k--;
  }
  cout<<ans<<endl;
  return 0;
}

珠宝的最大交替和

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
//多个1e9数相加 会爆int 
#define all(s) s.begin(),s.end()

int n;
int main()
{
    ios_base :: sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cin >> n;
    std::vector<int> a, b;
    for (int i = 0; i < n; ++i) {
        int x;
        cin >> x;
        x = abs(x);
        if (i % 2) b.push_back(x);
        else a.push_back(x);
    }
    if (n == 1) {
        cout << abs(a[0]) << '\n';
        return 0;
    }
    int mi = *min_element(all(a));
    int mx = *max_element(all(b));
    LL ans = accumulate(all(a), 0LL) - accumulate(all(b), 0LL);
    if (mx >= mi)ans += 2 * mx - 2 * mi;//细节 主要是要减的最大数 大于 加的最小数 才是有效的
    
    cout << ans << '\n';
    return 0;
}

小蓝的礼物

//细节
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long ll;
int n,k,a[N];
ll s[N];
int main(){
  cin>>n>>k;
  for(int i=1;i<=n;i++){
    cin>>a[i];
  }
  sort(a+1,a+n+1);
  for(int i=1;i<=n;i++){
    s[i]=s[i-1]+a[i];
  }
  for(int i=1;i<=n;i++){
    if((s[i-1]+(a[i]+1)/2)>k){
      cout<<i-1<<endl;
      return 0;
    }
  }
  cout<<n<<endl;
  return 0;
}

四个瓷瓶的神秘游戏

鸡哥的购物挑战

冒险者公会

明日方舟大作战!

8.双指针

对撞指针

lanqiao OJ 1371

#include<bits/stdc++.h>
using namespace std;
int main(){
  string str;
  cin>>str;
  for(int i=0,j=str.length()-1;i<str.length()/2;i++,j--){
    if(str[i]!=str[j]) {
      cout<<"N";
      return 0;
    }
  }
  cout<<"Y"<<endl;
  return 0;
}

快慢指针

lanqiao OJ 1372  寻找最美区间

#include<bits/stdc++.h>
using namespace std;
int n,k,a[100005];
int main(){
  cin>>n>>k;
  for(int i=1;i<=n;i++){
    cin>>a[i];
  }
  int sum=0,ans=1e9;
  for(int i=1,j=0;i<=n;i++){
    while(sum<k&&j<=n){
      j++;
      sum+=a[j];
    }
    if(sum>=k){
      ans=min(ans,j-i+1);
      sum-=a[i];//连续区间 删除最一开始的元素
    }
  }
  if(ans==1e9){
    cout<<0;
  }else{
    cout<<ans;
  }
  return 0;
}

聪明的小羊肖恩(类似于前缀和+双指针)

对撞指针

问题转化为对于一个给定的数z 如何求出a[i]+a[j]<=Z的下标数量

先对数组a进行排序 定义两个指针 l和r l=1 r=n

a[l]+a[r]>Z r--

a[l]+a[r]<Z l++

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
typedef long long ll;
int n,L,R;
int a[N];

ll calc(int v){
  int l=1,r=n;
  ll ans=0;
  while(l<r){
    while(l<r&&a[l]+a[r]>v){
      r--;
    }//先进来 r-- 1-r(最大的r)距离内a[1]+a[r]<=v
    ans+=r-l;//加上l-r距离内的下标对 1-r内部的所有有序对 不仅仅局限于从1开始
    l++;
  }
  return ans;
}
int main(){
  cin>>n>>L>>R;
  for(int i=1;i<=n;i++) cin>>a[i];
  sort(a+1,a+n+1);
  cout<<calc(R)-calc(L-1)<<endl;//有点类似于前缀和的思想
  return 0;
}

神奇的数组(双指针)

快慢指针

//异或运算 两个二进制数相加时如果没有产生进位 那么两个数异或的值等于相加的值
//不同数做异或等于1 相同数做异或等于0
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
typedef long long ll;
int main(){
  int n;
  cin>>n;
  vector<int> a(n);
  for(int i=0;i<n;i++) cin>>a[i];
  int l=0,r=0,res=0;
  ll ans=0;
  while(l<n){
    while(r<n&&((res^a[r])==(res+a[r]))) res^=a[r],r++;//1-r 2-r+1 r不断扩张
    ans+=r-l;
    res^=a[l];//恢复前面 取消a[l]的这个异或
    l++;
  }
  cout<<ans<<endl;
  return 0;
}

9.二分 

整数二分

//找到升序数组a中的x第一次出现的位置
int l=0,r=1e9;
while(l+1!=r)
{
  int mid=(l+r)/2;
  if(a[mid]>=x) r=mid; 
  else l=mid;
}
cout<<r<<endl;

lanqiao OJ 1389

在从小到大的排序数组中 

lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标

upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

在从大到小的排序数组中,重载lower_bound()和upper_bound()

lower_bound( begin,end,num,greater<type>() ):从数组的begin位置到end-1位置二分查找第一个小于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

upper_bound( begin,end,num,greater<type>() ):从数组的begin位置到end-1位置二分查找第一个小于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

#include <bits/stdc++.h>
using namespace std;
int main()
{
  int data[200];
  for(int i=0;i<200;i++){
    data[i]=4*i+6;
  }
  int n;
  cin>>n;
  int m=lower_bound(data,data+200,n)-data;
  cout<<m<<endl;
  /*int l=0,r=200;
  while(l+1!=r){
    int mid=(l+r)/2;
    if(data[mid]>=n) r=mid;
    else l=mid;
  }
  cout<<r<<endl;
  return 0;
  */
}

 浮点二分

double l=0,r=1e9,eps=1e-6;
while(r-l>=eps){
  double mid=(l+r)/2;
  if(f(mid)>=0) r=mid;
  else l=mid;
}
cout<<r<<endl;

二分答案

(二分题目好多都是 给出不同数目的某物=物体 要求分割相同数目的最大份数)

一般情况下,我们需要将答案进行二分,然后枚举出某个可能解后判断是否可以更优或者是否合法,从而不断逼近最优解

bool check(int mid)
{
  bool res=true;
  return res;
}
int main(){
  int l=0,r=1e9;
  while(l+1!=r){
    int mid=(l+r)/2;
    if(check(mid)) l=mid;
    else r=mid;
  }
  cout<<l<<endl;
}

lanqiao OJ 364

lanqiao OJ 3683

lanqiao OJ 3404

若题目中有n个物品最大XX为多少

则进行check(): 解出来的数>=n

可凑成的最大花束数

假设答案是x,那么我们需要的花朵是x*k朵 提供的有效花朵min(ai,k)
统计出每个人可提供的有效花朵的和是否达到x*k 达到则说明可以凑出x束花束
注意二分的上界不可开小 判断res>x*k x*k可能会爆long long  则修改为 res/x>=k

//对于第i位追求者所送的花 思考可以提供多少有效花朵 min(a[i],x)
//可以统计出每个人可以提供的有效花朵的和是否达到了 x*k
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;

int n,k;
int main(){
  cin>>n>>k;
  vector<LL> a(n);
  for(int i=0;i<n;i++) cin>>a[i];
  auto check=[&](LL x){
    LL res=0;
    for(int i=0;i<n;i++){
      res+=min(a[i],x);
    }
    return res/x>=k;
  }
  LL l=0;
  LL r=2e14;
  while(l<r){
    LL mid=(l+r+1)>>1;
    if(check(mid)) l=mid;
    else r=mid-1;
  }
  cout<<r<<endl;
  return 0;
}

最大通过数

两部分能源宝石进行前缀和 先取出一部分过左边关卡 再取出剩下一部分过剩下的关卡 将两部分关卡数相加

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int N=2e5+10;
int n,m,k;
ll a[N],b[N];
int main(){
  cin>>n>>m>>k;
  for(int i=1;i<=n;i++){
    cin>>a[i];
    a[i]+=a[i-1];
  }
  for(int i=1;i<=m;i++){
    cin>>b[i];
    b[i]+=b[i-1];
  }
  int ans=0;
  for(int i=0;i<=n;i++){
    if(a[i]>k) break;
    ll res=k-a[i];
    int x=upper_bound(b,b+m+1,res)-b-1;
    ans=max(ans,i+x);
  }
  cout<<ans<<endl;
  return 0;
}

妮妮的月饼工厂

//题目要求我们用N块原料 制作出k个高度完全相同的月饼 且这k个月饼尽量高
//选定一个高度h 然后将每块原材料切割成高度为h的月饼 切成cnt=[原材料/h]的月饼 
//cnt>=k

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
  int n,k;
  cin>>n>>k;
  vector<int> v(n);
  for(int i=1;i<=n;i++) cin>>v[i];
  int l=1,r=1e9,res=-1;
  while(l<=r){
    int mid=(l+r)>>1;
    int cnt=0;
    for(int i=1;i<=n;i++){
      cnt+=v[i]/mid;
    }
    if(cnt>=k){//已经切出来了 高度应该再高一些
      l=mid+1,res=mid;
    }else{
      r=mid-1;//高度应该低一些
    }
  }
  cout<<res<<endl;
  return 0;
} 

基德的神秘冒险

体育健将

10.线性DP

DP广义上有暴力解题的思想

注意:初始化 转移状态方程

1.确定状态,一般“到第i个为止,xx为j(xx为k)的方案数/最小代价/最大价值”

2.确定状态转移方程 从已知状态到新状态的方法

3.确定最终状态并确定

lanqiao OJ 1536数字三角形(路径方向转移)

dp[i][j]表示从第i行第j列的元素往下走的所有路径当中的最大的和

状态转移方程 dp[i][j]=a[i][j]+max(dp[i+1][j],dp[i+1][j+1]);

#include<bits/stdc++.h>
using namespace std;
const int N=105;
int n;
int a[N][N],dp[N][N];
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=n;i>=1;i--){
    for(int j=1;j<=i;j++){
      dp[i][j]=a[i][j]+max(dp[i+1][j],dp[i+1][j+1]);
    }
  }
  cout<<dp[1][1]<<endl;
  return 0;
}

lianqiao OJ 3367 破损的楼梯(部分转移点不合法)

破损的楼梯设状态dp[i]表示走到第i阶台阶方案数

状态转移方程 dp[i]=d[i-1]+dp[i-2] 如果破损 则dp[i]=0

从前到后 最后输出dp[n][n]

对结果进行取模 dp[i]=(dp[i-1]+dp[i-2])%p;每个方案数都进行取模 

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
const int p=1e9+7;
ll dp[N];
bool broken[N];
int main(){
  ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
  int n,m;
  cin>>n>>m;
  memset(broken,false,sizeof broken);
  for(int i=0;i<m;i++){
    int x;
    cin>>x;
    broken[x]=true;
  }
  dp[0]=1;//从0走到2也算一个方案 初始化
  if(broken[1]==false) dp[1]=1;//第一个台阶不破 就可以从第一个台阶走到第二个台阶
  for(int i=2;i<=n;i++){//注意识别一下i的初始下标
    if(broken[i]==true) continue;//直接跳过不可以dp[i]=0 是因为根本不可能有方案在这个台阶开始起步 
    dp[i]=(dp[i-1]+dp[i-2])%p;
  }
  cout<<dp[n]<<endl;
  return 0;
}

lianqiao OJ 3423 安全序列(重点)(前缀和优化)

直接求和会超时 利用前缀和来优化时间复杂度

设状态dp[i]表示以位置i结尾的方案数  dp[i]是从j=1到j=i-k-1的dp[j]求和

0表示不放 1表示放
dp[i]就在i这个地方标记为1 
0000 1000 0100 0010 0001 1001
dp[0]=1 pre[0]=1; 0000 初始化方案数 
dp[1]=1 pre[1]=2; 1000  新标记
dp[2]=1 pre[2]=3; 0100  新标记
dp[3]=1 pre[3]=4; 0010  新标记
dp[4]=2 pre[4]=6; 0001  1001新标记 并且出现可以放的间隔数为2的新标记
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1e6+10;
const int p=1e9+7;

ll dp[N],pre[N];

int main(){
  int n,k;
  cin>>n>>k;
  dp[0]=pre[0]=1;
  for(int i=1;i<=n;i++){
    if(i-k-1<1) dp[i]=1;
    else dp[i]=pre[i-k-1];//pre[i-k-1]代表的是从j=1开始到j=i-k-1的dp[i]的和
    pre[i]=(pre[i-1]+dp[i])%p;
  }
  cout<<pre[n]<<endl;
  return 0;
}

拍照

考虑原序列的正向最长子序列和反向最长下降子序列

//考虑原序列的正向最长子序列和反向最长下降子序列
#include<bits/stdc++.h>
using namespace std;
const int N=105;
int n,m,a[N],dp1[N],dp2[N];
int main(){
  cin>>n;
  for(int i=1;i<=n;i++){
    cin>>a[i];
    dp1[i]=dp2[i]=1;//初始化  dp[i]代表从i这个下标开始站位排序的人数 可以从任何一个人开始排序
  }
  for(int i=2;i<=n;i++){
    for(int j=1;j<i;j++){//升序
      if(a[i]>=a[j]) dp1[i]=max(dp1[i],dp1[j]+1);//dp1[i]是到i这个下标(包括这个下标)的正向最长上升子序列
    }
  }
  for(int i=n-1;i>=1;i++){
    for(int j=n;j>i;j--){
      if(a[i]>=a[j]) dp2[i]=max(dp2[i],dp2[j]+1);//dp2[i]是到i这个下标(包括这个下标)的反向最长上升子序列
    }
  }
  for(int i=1;i<=n;i++){
    m=max(m,dp1[i]+dp2[i]-1);
  }
  cout<<n-m<<endl;
  return 0;
}

可构造的序列总数(倍数转移)

f[i][j]是只考虑前i个数且第i个数为j的合法方案数

先利用两个循环 找出这个数的因子 再将每个因子进行转移 所有的方案数进行相加

f[i][j]+=f[i-1][z] (j mod z==0)

vector<vector> f(n+1,vector(k+1));

 这种方式声明了一个二维的vector,其中外层的vector有n+1个元素,每个元素都是一个内层vector,内层的vector有k+1个元素。这种方式会为每个内层的vector分配内存空间,并且可以通过两个索引来访问元素,例如f[i][j]表示访问第i个外层vector中的第j个元素。

vector<vector<int>> f(n+1,vector<ll>(k+1));与vector<vector<int>> e(k+1);的区别

  1. vector<vector<int>> f(n+1,vector<ll>(k+1));:这个声明创建了一个二维矩阵,其中第一维的大小为n+1,第二维的大小为k+1。该二维矩阵中的每个元素都是一个整数类型的向量,每个向量的大小为k+1。这种方式可以用来存储一个n x k 的矩阵。

  2. vector<vector<int>> e(k+1);:这个声明创建了一个二维矩阵,其中第一维的大小为k+1,但没有为第二维的向量指定大小。这意味着每个元素e[i]将是一个空的整数类型向量。这种方式适用于在稍后根据需要为每个元素分配不同大小的向量。

//从1-k之间找出长度为n的具有倍数关系的区间数
//f[i][j]是只考虑前i个数且第i个数为j的合法方案数
//初始化f[1][i]=1; 序列区间中全为i也是一种情况 初始值
//因为倍数关系 所以转移时 j应该是从它的因子转移来
//f[i][j]+=f[i-1][z] (j mod z==0)
#include<bits/stdc++.h>
using namespace std;
const int N=2020;
typedef long long ll;
const ll p=1e9+7;
int n,k;
int main(){
  cin>>k>>n;
  vector<vector<ll>> f(n+1,vector<ll>(k+1));//声明二维数组
  vector<vector<int>> e(k+1);
  for(int i=1;i<=k;i++){ //i=2 j=2 4 6 i是j的因子
    for(int j=i;j<=k;j+=i){
      e[j].push_back(i);
    }
  }
  for(int i=1;i<=k;i++) f[1][i]=1;
  for(int i=2;i<=n;i++){
    for(int j=1;j<=k;j++){
      for(auto v:e[j]){
        f[i][j]=(f[i][j]+f[i-1][v])%p;
      }
    }
  }
  ll ans=0;
  for(int i=1;i<=k;i++) ans=(ans+f[n][i])%p;//最后总数也要mod
  cout<<ans<<endl;
  return 0;
}

最快洗车时间(子集和问题)

定义f[i][j]表示只考虑前i个数能否选出和为j的子集

将洗车所有时间 进行划分遍历 看此时间点是否可以将车辆划分为两大部分

将车辆分配为两组 如何分配时间最短

//将车辆分配为两组 如何分配时间最短
//转化为子集和 即判断是否在原数组中选择一些数 使得他们和为x x取值为[1,S]
//定义f[i][j]表示只考虑前i个数能否选出和为j的子集
//f[i][j]=f[i-1][j]Vf[i-1][j-a[i]]
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=110;
int n;
int a[N];
bool f[N][N];
int main(){
  cin>>n;
  for(int i=1;i<=n;i++) cin>>a[i];
  int m=accumulate(a+1,a+n+1,0);
  f[0][0]=true;//初始化 表示已经洗掉
  for(int i=1;i<=n;i++){
    for(int j=0;j<=m;j++){
      f[i][j]=f[i-1][j];
      if(a[i]<=j) f[i][j]|=f[i-1][j-a[i]];
    }
  }
  int ans=m;
  for(int i=0;i<=m;i++){
    if(f[n][i]){
      ans=min(ans,max(i,m-i));
    }
  }
  cout<<ans<<endl;
  return 0;
}

11.二维DP

lanqiao OJ 389 摆花  注意从哪一步推出来的

dp[i][j]表示已经摆了i种花 一共摆了j盆花

枚举这次可以摆放的花数目 在上一步减去即可

//一共摆m盆花 共有n种花 每种花最多可以摆a[i]盆 不同种类的话需要按标号从小到大进行排序 共有多少种不同的摆花方案
//dp[i][j]表示已经摆了i种花 一共摆了j盆花
//dp[i][j]=dp[i-1][j-k](k从0到a[i])求和
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=110;
const ll p=1e6+7;
ll a[N];
ll dp[N][N];
int main(){
  int n,m;
  cin>>n>>m;
  for(int i=1;i<=n;i++) cin>>a[i];
  dp[0][0]=1;//方案数 一般起始都为1
  for(int i=1;i<=n;i++){
    for(int j=0;j<=m;i++){
      for(int k=0;k<=a[i]&&k<=j;k++){
        dp[i][j]=(dp[i][j]+dp[i-1][j-k])%p;
      }
    }
  }
  cout<<dp[n][m]<<endl;
  return 0;
}

lanqiao OJ 3711 选数异或

//dp[i][j]是到第i个数为止(但不一定选第i个),异或和为j的子序列个数
//对于第i层 转移方式 是选或者不选 两种
//dp[i][j]=dp[i-1][j]+dp[i-1][j^a[i]];
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=1e5+9;
const ll p=998244353;
int a[N],dp[N][70];

int main(){
  int n,x;
  cin>>n>>x;
  for(int i=1;i<=n;i++) cin>>a[i];
  dp[0][0]=1;
  for(int i=1;i<=n;i++){
    for(int j=0;j<64;j++){
      dp[i][j]=(dp[i-1][j]+dp[i-1][j^a[i]])%p;
    }
  }
  cout<<dp[n][x]<<endl;
  return 0;
}

lanqiao OJ 505 数字三角形(图形路径DP)

//dp[i][j][k]表示从该点(i,j)出发一共进行k次右移
#include<bits/stdc++.h>
using namespace std;
const int N=110;
typedef long long ll;
int main(){
  int n;
  cin>>n;
  int a[N][N];
  ll dp[N][N][N];
  for(int i=1;i<=n;i++){
    for(int j=1;j<=i;j++){
      cin>>a[i][j];
    }
  }
  for(int i=n;i>=1;i--){
    for(int j=1;j<=i;j++){//枚举此行下的每一个点
      for(int k=0;k<=n-i;k++){
        if(k>=1) dp[i][j][k]=a[i][j]+max(dp[i+1][j][k],dp[i+1][j+1][k-1]);//两种选择进行右移或者是左移
        else dp[i][j][k]=a[i][j]+dp[i+1][j][k];//k=0不进行右移
      }
    }
  }
  if(n&1) cout<<dp[1][1][(n-1)/2];
  else cout<<max(dp[1][1][(n-1)/2],dp[1][1][n-1-(n-1)/2]);
  return 0;
}

地图 (图形路径DP)

//不仅去记录位置 更要记录变换方向的次数
//定义状态 f[x][y][d][step]为起点为(x,y)当前方向为d并且改变方向step次
//d为向下时 f[x][y][d][step]=f[x+1][y][!d][step+1]+f[x][y+1][d][step]
//d为向上时 f[x][y][d][step]=f[x+1][y][d][step]+f[x][y+1][!d][step+1]

#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n,m,k;
char s[N][N];
int f[N][N][2][6];
int dx[]={0,1},dy[]={1,0};

int dfs(int x,int y,int d,int step){
  if(x>n||y>n) return 0;
  if(s[x][y]=='#') return 0;
  if(step>k) return 0;
  if(x==n&&y==m) return 1;//方案数加1
  if(f[x][y][d][step]) return f[x][y][d][step]; // 记忆化
  int res=0;
  for(int i=0;i<2;i++){
    res+=dfs(x+dx[i],y+dy[i],i,step+(i!=d));
  }
  return f[x][y][d][step]=res;
}
int main(){
  ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
  cin>>n>>m>>k;
  for(int i=1;i<=n;i++){
    cin>>s[i]+1;
  }
  int ans=0;
  if(s[1][2]!='#') ans+=dfs(1,2,0,0);
  if(s[2][1]!='#') ans+=dfs(2,1,1,0);
  cout<<ans<<endl;
  return 0;
}

电影放映计划(典型背包问题  给定空间的最大价值问题)

//典型背包问题  给定空间的最大价值问题
//dp[i]表示第i分钟时候获得最大利润
//对于一部电影 我们需要决定是否放映它 dp[i]=max(dp[i-T[j]]+p[j],dp[i]);
//只要i>=T[j]就代表可以播放 还是遵循暴力解题的思想 dp[i-T[j]]代表减去这个T[j]时间之前可以获取的利益
#include<bits/stdc++.h>
using namespace std;
int M,N;
int main(){
  cin>>M>>N;
  vector<int> T(N),P(N);
  for(int i=0;i<N;i++){
    cin>>T[i]>>P[i];
  }
  int K;
  cin>>K;
  M+=K;
  for(int i=0;i<N;i++){
    T[i]+=K;
  }
  vector<int> dp(M+1,0);//初始值全为0
  for(int i=1;i<=M;i++){
    dp[i]=dp[i-1];
    for(int j=0;j<N;j++){
      if(i>=T[j]) dp[i]=max(dp[i],dp[i-T[j]]+P[j]);
    }
  }
  cout<<dp[M]<<endl;
  return 0;
}

12.LIS和LCS

LIS:最长上升子序列

dp[i]表示1-i的最长上升子序列的长度 状态转移方程为 if( a[j]<a[i] )dp[i]=max(dp[j]+1)

最后输出dp[n]

lanqiao OJ 2049 蓝桥勇士

#include<bits/stdc++.h>
using namespace std;
const int N=1100;
typedef long long ll;
ll a[N];
int dp[N];
int main(){
  int n;
  cin>>n;
  for(int i=1;i<=n;i++){
    cin>>a[i];
  }
  for(int i=1;i<=n;i++){
    dp[i]=1;
    for(int j=1;j<i;j++){
      if(a[j]<a[i]) dp[i]=max(dp[i],dp[j]+1);
    }
  }
  int ans=0;
  for(int i=1;i<=n;i++){
    ans=max(ans,dp[i]);
  }
  cout<<ans<<endl;
  return 0;
}

LCS:最长公共子序列

dp[i][j]表示A[1-i]序列和B[i-i]序列中(不规定结尾)的最长公共子序列的长度

if a[i]=b[j]:dp[i][j]=dp[i-1][j-1]+1;

else dp[i][j]=max(dp[i-1][j],dp[i][j-1]); 

lanqiao OJ 1189

#include <iostream>
using namespace std;
const int N=1e3+9;
int n,m,a[N],b[N],dp[N][N];
int main()
{
  int n,m;
  cin>>n>>m;
  for(int i=1;i<=n;i++) cin>>a[i];
  for(int i=1;i<=m;i++) cin>>b[i];
  for(int i=1;i<=n;i++){
    for(int j=1;j<=m;j++){
      if(a[i]==b[j]) dp[i][j]=dp[i-1][j-1]+1;
      else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
    }
  }
  cout<<dp[n][m]<<endl;
  return 0;
}

13.01背包

只要两种状态拿与不拿

dp[i][j]表示到第i个物品为止,拿的物品总体积为j的情况的最大价值

dp[i][j]=max(dp[i-1][j],dp[i-1][j-w]+v);

lanqiao OJ 1174 小明的背包1

二维数组

#include<bits/stdc++.h>
using namespace std;
const int N=110;
const int M=1010;
typedef long long ll;
ll dp[N][M];

int main(){
  int n,V;
  cin>>n>>V;
  for(int i=1;i<=n;i++){
    ll w,v;
    cin>>w>>v;
    for(int j=0;j<=V;j++){
      if(j>=w) dp[i][j]=max(dp[i-1][j],dp[i-1][j-w]+v);
      else dp[i][j]=dp[i-1][j];
    }
  }
  cout<<dp[n][V]<<endl;
  return 0;
}

一维数组

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int M=1010;
ll dp[M];
int main(){
  int n,V;
  cin>>n>>V;
  for(int i=1;i<=n;i++){
    ll w,v;
    cin>>w>>v;
    for(int j=V;j>=w;j--){
      dp[j]=max(dp[j],dp[j-w]+v);
    }
  }
  cout<<dp[V]<<endl;
  return 0;
}

lanqiao OJ  2223 背包与魔法

dp[i][j]表示物品总重量为i,且使用了j次魔法的情况下的最大价值

对于每个物品有3种选择,可以不选,选但不用魔法,选且用魔法

//设状态dp[i][j]表示物品总重量为i 且 使用了j次魔法的情况下的最大价值
//对于每个物品有三种选择 可以不选 选但不使用魔法 选且用魔法
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e4+9;
ll dp[N][2];
int main(){
  int n,m,k;
  cin>>n>>m>>k;
  for(int i=1;i<=n;i++){
    ll w,v;
    cin>>w>>v;
    for(int j=m;j>=0;j--){
      if(j>=w){
        dp[j][0]=max(dp[j][0],dp[j-w][0]+v);//没有选择 或者选择但没有使用魔法
        dp[j][1]=max(dp[j][1],dp[j-w][1]+v);//之前已经使用过魔法 但这次没有选择 或者之前使用过魔法 但这次选择了
      }
      if(j>=w+k){
        dp[j][1]=max(dp[j][1],dp[j-w-k][0]+2*v);//之前已经使用过魔法 但这次没有选择 或者 这次使用了魔法而且选择了
      }
    }
  }
  cout<<max(dp[m][0],dp[m][1])<<endl;
  return 0;
}

倒水

我们可以把最初拥有水的体积m看作背包容量,给客人倒水的量和获得的满意度看成一个物品,倒水的量是物品体积,客人的满意度是物品价值。相当于每个客人是一个物品组,每个物品组有3件物品,分别是(0,e[i]),(a[i],b[i]) (c[i],d[i]),而且每个组必须选择1个物品,不能不选,最后求得是满意度之和的最大值,相当于是选择的所有物品的价值之和最大。那么就是分组背包问题了,因为每组物品只有3个,所以做3次 01 背包的转移就好了。

//我们可以把最初拥有水的体积m看作背包容量,给客人倒水的量和获得的满意度看成一个物品,倒水的量是物品体积,客人的满意度是物品价值。相当于每个客人是一个物品组,每个物品组有3件物品,分别是
//(0,e[i]),(a[i],b[i]) (c[i],d[i]),而且每个组必须选择1个物品,不能不选,最后求得是满意度之和的最大值,相当于是选择的所有物品的价值之和最大。那么就是分组背包问题了,因为每组物品只有3个,所以做3次 01 背包的转移就好了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
  ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
  int n,m;
  cin>>n>>m;
  vector<int> a(n+1),b(n+1),c(n+1),d(n+1),e(n+1);
  for(int i=1;i<=n;i++){
    cin>>a[i]>>b[i]>>c[i]>>d[i]>>e[i];
  }
  vector<vector<ll>> f(n+1,vector<ll>(m+1));
  for(int i=1;i<=n;i++){
    for(int j=0;j<=m;j++){
      f[i][j]=f[i-1][j]+e[i];//不给客人倒水
      if(j>=a[i]) f[i][j]=max(f[i][j],f[i-1][j-a[i]]+b[i]);
      if(j>=c[i]) f[i][j]=max(f[i][j],f[i-1][j-c[i]]+d[i]);
    }
  }
  cout<<f[n][m]<<endl;
  return 0;
}

盗墓分赃2

//去某些数使其总和为all/2 如果正好等于all/2则yes 否则no
//背包容量为宝藏重量总和的一半 物品价值为宝藏重量a[i] 物品体积为宝藏重量a[i]
//dp[i][j]表示前i件宝藏中体积不超过j的最大价值
//转移方程 dp[i][j]=max(dp[i][j],dp[i-1][j-a[i]]+a[i]);
//dp[n][sum]==sum 则yes 否则no
#include<bits/stdc++.h>
using namespace std;

int dp[10010];
int a[10010];
int sum;

int main(){
  int n;
  cin>>n;
  for(int i=1;i<=n;i++){
    cin>>a[i];
    sum+=a[i];
  }
  if(sum%2||n%2){
    cout<<"no"<<endl;
    return 0;
  }
  sum/=2;
  for(int i=1;i<=n;i++){
    for(int j=sum;j>=a[i];j--){
      dp[j]=max(dp[j],dp[j-a[i]]+a[i]);
    }
  }
  if(dp[sum]==sum){
    printf("yes\n");
  }else{
    printf("no\n");
  }
  return 0;
}

蓝桥课程抢购

购物策略

小兰的神秘礼物

14.完全背包

15.多重背包

16.Flood Fill

池塘计数

查找w块的个数 先判断开头是否是‘W’并且未被标记过 才能进入bfs搜索

#include<iostream>
#include<cstring>
#include<algorithm>

#define x first 
#define y second

using namespace std;

typedef pair<int,int>PII;
const int N=1010;
const int M=N*N;

int n,m;
char g[N][N];
PII q[M];
bool st[N][N];

void bfs(int sx,int sy){
    int hh=0,tt=0;
    q[0]={sx,sy};
    st[sx][sy]=true;//打上标记
    while(hh<=tt){
        PII t=q[hh++];//先是0 再是1
        for(int i=t.x-1;i<=t.x+1;i++){
            for(int j=t.y-1;j<=t.y+1;j++){
                if(i==t.x&&j==t.y)  continue;
                if(i<0||i>=n||j<0||j>=m) continue;
                if(g[i][j]=='.'||st[i][j]) continue;
                q[++tt]={i,j};//先是1 
                st[i][j]=true;
            }
        }
    }
}
int main(){
    cin>>n>>m;
    for(int i=0;i<n;i++) cin>>g[i];
    int cnt=0;
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            if(g[i][j]=='W'&&!st[i][j]){
                bfs(i,j);
                cnt++;
            }
        }
    }
    cout<<cnt<<endl;
    return 0;
}

 城堡问题

#include<bits/stdc++.h>

#define x first 
#define y second

using namespace std;

typedef pair<int,int> PII;

const int N=55,M=N*N;

int n,m;
int g[N][N];
PII q[M];
bool st[N][N];

int bfs(int sx,int sy){
    int dx[4]={0,-1,0,1};
    int dy[4]={-1,0,1,0};
    q[0]={sx,sy};
    st[sx][sy]=true;
    int hh=0,tt=0;
    int area=0;
    while(hh<=tt){
        PII t=q[hh++];
        area++;
        for(int i=0;i<4;i++){
            int a=t.x+dx[i],b=t.y+dy[i];
            if(a<0||a>=n||b<0||b>=m) continue;
            if(st[a][b]) continue;
            if(g[t.x][t.y]>>i&1) continue;
            q[++tt]={a,b};
            st[a][b]=true;
        }
    }
    return area;
}
int main(){
    cin>>n>>m;
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            cin>>g[i][j];
        }
    }
    int cnt=0,area=0;
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            if(!st[i][j]){
                area=max(area,bfs(i,j));
                cnt++;
            }
        }
    }
    cout<<cnt<<endl;
    cout<<area<<endl;
    return 0;
}

山峰和山谷

寻找最高处和最低处的联通块

#include <cstring>
#include <iostream>
#include <algorithm>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 1010, M = N * N;

int n;
int h[N][N];
PII q[M];
bool st[N][N];

void bfs(int sx, int sy, bool& has_higher, bool& has_lower)//如果是判断两种不同数量 可以利用bool型传回main函数
{
    int hh = 0, tt = 0;
    q[0] = {sx, sy};
    st[sx][sy] = true;

    while (hh <= tt)
    {
        PII t = q[hh ++ ];

        for (int i = t.x - 1; i <= t.x + 1; i ++ )
            for (int j = t.y - 1; j <= t.y + 1; j ++ )
            {
                if (i == t.x && j == t.y) continue;
                if (i < 0 || i >= n || j < 0 || j >= n) continue;
                if (h[i][j] != h[t.x][t.y]) // 山脉的边界
                {
                    if (h[i][j] > h[t.x][t.y]) has_higher  = true;
                    else has_lower = true;
                }
                else if (!st[i][j])
                {
                    q[ ++ tt] = {i, j};
                    st[i][j] = true;
                }
            }
    }
}

int main()
{
    scanf("%d", &n);

    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
            scanf("%d", &h[i][j]);

    int peak = 0, valley = 0;
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
            if (!st[i][j])
            {
                bool has_higher = false, has_lower = false;
                bfs(i, j, has_higher, has_lower);
                if (!has_higher) peak ++ ;
                if (!has_lower) valley ++ ;
            }

    printf("%d %d\n", peak, valley);

    return 0;
}

17.最短路模型

迷宫问题

找出最短路线 并且逆序输出最短路的坐标

//逆序输出已经走过的点
#include <cstring>
#include <iostream>
#include <algorithm>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 1010, M = N * N;

int n;
int g[N][N];
PII q[M];
PII pre[N][N];

void bfs(int sx, int sy)
{
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

    int hh = 0, tt = 0;
    q[0] = {sx, sy};

    memset(pre, -1, sizeof pre);
    pre[sx][sy] = {0, 0};
    while (hh <= tt)
    {
        PII t = q[hh ++ ];

        for (int i = 0; i < 4; i ++ )
        {
            int a = t.x + dx[i], b = t.y + dy[i];
            if (a < 0 || a >= n || b < 0 || b >= n) continue;
            if (g[a][b]) continue;
             if (pre[a][b].x != -1) continue;

            q[ ++ tt] = {a, b};
            pre[a][b] = t;
        }
    }
}

int main()
{
    scanf("%d", &n);

    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
            scanf("%d", &g[i][j]);

    bfs(n - 1, n - 1);

    PII end(0, 0);

    while (true)
    {
        printf("%d %d\n", end.x, end.y);
        if (end.x == n - 1 && end.y == n - 1) break;
        end = pre[end.x][end.y];
    }

    return 0;
}

武士风度的牛

#include <cstring>
#include <iostream>
#include <algorithm>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 155, M = N * N;

int n, m;
char g[N][N];
PII q[M];
int dist[N][N];

int bfs()
{
    int dx[] = {-2, -1, 1, 2, 2, 1, -1, -2};
    int dy[] = {1, 2, 2, 1, -1, -2, -2, -1};

    int sx, sy;
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < m; j ++ )
            if (g[i][j] == 'K')
                sx = i, sy = j;

    int hh = 0, tt = 0;
    q[0] = {sx, sy};

    memset(dist, -1, sizeof dist);
    dist[sx][sy] = 0;

    while (hh <= tt)
    {
        auto t = q[hh ++ ];

        for (int i = 0; i < 8; i ++ )
        {
            int a = t.x + dx[i], b = t.y + dy[i];
            if (a < 0 || a >= n || b < 0 || b >= m) continue;
            if (g[a][b] == '*') continue;
            if (dist[a][b] != -1) continue;//检验是否被遍历过 如果不为-1 就代表已经放入队列中
            if (g[a][b] == 'H') return dist[t.x][t.y] + 1;

            dist[a][b] = dist[t.x][t.y] + 1;
            q[ ++ tt] = {a, b};
        }
    }

    return -1;
}

int main()
{
    cin >> m >> n;

    for (int i = 0; i < n; i ++ ) cin >> g[i];

    cout << bfs() << endl;

    return 0;
}

18.连通性 

迷宫

#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n;
char g[N][N];
int xa,xb,ya,yb;
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
bool st[N][N];

bool dfs(int x,int y){
    if(x==xb&&y==yb) return true;
    st[x][y]=true;
    for(int i=0;i<4;i++){
        int a=x+dx[i],b=y+dy[i];
        if(a<0||a>=n||b<0||b>=n) continue;
        if(st[a][b]) continue;
        if(g[a][b]=='#') continue;
        if(dfs(a,b)) return true;
    }
    return false;
}
int main(){
    int t;
    cin>>t;
    while(t--){
        cin>>n;
        for(int i=0;i<n;i++) cin>>g[i];
        cin>>xa>>ya>>xb>>yb;
        memset(st,0,sizeof st);
        if(g[xa][ya]=='#'||g[xb][yb]=='#'){
            cout<<"NO"<<endl;
        }else{
            if(dfs(xa,ya)) cout<<"YES"<<endl;
            else cout<<"NO"<<endl;
        }
    }
    return 0;
}

19.KMP

用于匹配模式串在文本串S出现的所有位置

精髓在于next数组,next数组表示此时模式串下失配时应该移动到的位置 也表示最长的相同真前后缀的长度

//计算next数组
char s[N],p[N];
int nex[N];
int n=strlen(s+1),m=strlen(p+1);//字符串的下标都是从1开始
nex[0]=nex[1]=0;//初始化
for(int i=2,j=0;i<=m;i++){
  //不断匹配p[i]和p[j+1]
  while(j&&p[i]!=p[j+1]) j=nex[j];
  if(p[i]==p[j+1]) j++;
  nex[i]=j;
}
//通过next数组进行匹配
for(int i=1,j=0;i<=n;i++){
  while(j&&s[i]!=p[j+1]) j=nex[j];
  if(s[i]==p[i+1]) j++;
  if(j==m)//成功匹配一次
}
#include<bits/stdc++.h>  
using namespace std;
const int maxn=1e6+10;
// P:  a b a b a c j
//nex: 0 0 1 2 3 0
// S:  a b a b a b a c i
string s;
int nex[maxn];
int main(){
  cin>>s;
  s=""+s;
  int n=s.length();
  nex[0]=nex[1]=0;
  for(int i=2,j=0;i<=n;i++){
    while(j!=0&&s[i]!=s[j+1]){
      j=nex[j];//j返回相同字符串的起始位置  eg: S="abababac" P="ababac" nex[j]=3 从3开始匹配
    }
    if(s[i]==s[j+1]) j++;
    nex[i]=j;
  }
  int cir=n-nex[n-1];
  if(n%cir==0){
    cout<<n/cir;
  }else{
    cout<<"1"<<endl;
  }
  return 0;
}

 

求字符串最短循环节的个数

//求字符串最短循环节
#include<bits/stdc++.h>
#define maxn 1100000 
char s[maxn];
int nex[maxn];
//最长前后缀长度
void getnext(int len){
    int j=0;nex[0]=0;
    for(int i=1;i<len;i++){
        while(j!=0&&s[i]!=s[j]){
            j=nex[j-1];
        }
        if(s[i]==s[j])j++;
        nex[i]=j;
    }
}

int main() {
    scanf("%s", s);
    int len=strlen(s);
    getnext(len);
    int cir=len-nex[len - 1];
    if (len%cir==0) {
        printf("%d", len / cir);
    } else {
        printf("1");
    }
    return 0;
}

求模式串中最大的前缀字符串

//求出现在字符串中的最大的前缀字符串
//kmp的next数组存的是这个位置前面的字符串中的前缀和后缀相等的最大长度
#include<bits/stdc++.h>
using namespace std;
int main(){
  string s;
  int n;
  cin>>n;
  cin>>s;
  vector<int> nex(n+1);
  s=" "+s;
  nex[0]=nex[1]=0;
  for(int i=2,j=0;i<=n;i++){
    while(j!=0&&s[i]!=s[j+1]){
      j=nex[j];
    }
    if(s[i]==s[j+1]) j++;
    nex[i]=j;
  }
  int ans=0;
  for(int i=1;i<=n;i++){
    ans=max(ans,nex[i]);
  }
  cout<<ans<<endl;
  return 0;
}

  • 29
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值