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