今日总结2024/3/23

今日学习了简单dp问题和数学递推计算问题,明确了简单dp的递推过程是由上一个状态+当前状态改变的一个定量而来

DP起始就是带记忆化的暴力枚举,大概是暴力DFS->记忆化->DP的过程

DP的维度应该是能表示出集合且越小越好

Acwing  1015.摘花生

Hello Kitty想摘点花生送给她喜欢的米老鼠。

她来到一片有网格状道路的矩形花生地(如下图),从西北角进去,东南角出来。

地里每个道路的交叉点上都有种着一株花生苗,上面有若干颗花生,经过一株花生苗就能摘走该它上面所有的花生。

Hello Kitty只能向东或向南走,不能向西或向北走。

问Hello Kitty最多能够摘到多少颗花生。

1.gif

输入格式

第一行是一个整数T,代表一共有多少组数据。

接下来是T组数据。

每组数据的第一行是两个整数,分别代表花生苗的行数R和列数 C。

每组数据的接下来R行数据,从北向南依次描述每行花生苗的情况。每行数据有C个整数,按从西向东的顺序描述了该行每株花生苗上的花生数目M。

输出格式

对每组输入数据,输出一行,内容为Hello Kitty能摘到得最多的花生颗数。

数据范围

1≤T≤100,
1≤R,C≤100,
0≤M≤1000

本题只要明确他是从上到下,从左到右即可

状态表示dp[i][j]为摘到第i行第j列花生总数的集合,属性为最大值,可以从左边和上面中选个最大的加上不变量i j处花生的数量即可

#include <iostream>
#include <cstring>
#define endl '\n'
using namespace std;
const int N=1e2+3;
int g[N][N],dp[N][N];
int r,c;

void solve(){
    memset(dp,0,sizeof dp);
    //dp[i][j]是从西北角摘到i行j列花生数量的最大值
    for(int i=1;i<=r;i++)
    for(int j=1;j<=c;j++){
        dp[i][j]=max(dp[i][j-1],dp[i-1][j])+g[i][j];//从左边和上面选一个最大的
    }
    cout<<dp[r][c]<<endl;
}

int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int cnt;
    cin>>cnt;
    while(cnt--){
    cin>>r>>c;
    for(int i=1;i<=r;i++)
    for(int j=1;j<=c;j++)
    cin>>g[i][j];
    solve();
    }    
    return 0;
}
洛谷P3637 最长上升子序列

状态表示为集合就是以a[i]结尾的子序列,属性为最大值

dp[i]就是以a[i]结尾的上升子序列的长度的最大值

状态计算就是dp[1-(i-1)]中a[j]<a[i]的以a[j]结尾的子序列最大长度+1

本质上是用双指针实现的滚动数组,用dp[i]中存的以a[i]结尾的序列最大长度取a[1-i-1]之前的小于a[i]的dp[j]+1来更新dp[i],取个最大值即可),最开始都是以自己开头以自己结尾,长度为1

就是由a[i]之前的数结尾,且小于a[i]的最大上升序列长度+1递推过来的

输入

6
1 2 4 1 3 4

输出

4(也就是1 2 3 4)
#include <iostream>
using namespace std;
const int N=5e3+9;
int dp[N];//dp[i]为以a[i]结尾上升子序列的最大长度
int a[N],n;

int main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i],dp[i]=1;//初始化为1,每个以a[i]结尾刚开始长度都为1
	for(int i=1;i<=n;i++)//枚举dp[i]
	for(int j=1;j<i;j++){//dp[i]应当是在i之前的dp[j]以a[j]结尾且a[i]>a[j]的状态递推过来的
		if(a[i]>a[j])
		dp[i]=max(dp[i],dp[j]+1);
	}
	int ans=0;
	for(int i=1;i<=n;i++)
	ans=max(ans,dp[i]);//找出以a[1-n]结尾的最大长度
	cout<<ans;
	return 0;
}
Acwing 1205.买不到的数目

小明开了一家糖果店。

他别出心裁:把水果糖包成4颗一包和7颗一包的两种。

糖果不能拆包卖。

小朋友来买糖的时候,他就用这两种包装来组合。

当然有些糖果数目是无法组合出来的,比如要买 10 颗糖。

你可以用计算机测试一下,在这种包装情况下,最大不能买到的数量是17。

大于17的任何数字都可以用4和7组合出来。

本题的要求就是在已知两个包装的数量时,求最大不能组合出的数字。

输入格式

两个正整数 n,m,表示每种包装中糖的颗数。

输出格式

一个正整数,表示最大不能买到的糖数。

数据范围

2≤n,m≤1000,
保证数据一定有解。

输入样例:
4 7
输出样例:
17

首先,没思路,那就暴力,三重循环,第一重枚举凑出的数i,第二重枚举n的个数,第三重枚举m的个数,因此1-2s操作次数要1e7-1e8之间且最坏就是1e9

所以将每重循环控制在1e3最多

#include <iostream>
using namespace std;
int ans;
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<1e2;i++){
		bool flag=true;
		for(int j=0;j<1e2;j++)
		for(int k=0;k<1e2;k++)
		if(n*j+m*k==i) flag=false;
		if(flag)
		ans=max(ans,i); 
	}
	cout<<ans;
	return 0;
}

这样三个测试集能过一个

根据这个代码打表找规律可以找出计算公式ans=(n-1)*(m-1)-1,直接cout这个就行

反正我看不出来,死记就行,由裴蜀定理

两个正整数且互质的两个数不能凑出的最大数就是(n-1)*(m-1)-1

Acwing 1211.蚂蚁感冒

长 100100 厘米的细长直杆子上有 n 只蚂蚁。

它们的头有的朝左,有的朝右。

每只蚂蚁都只能沿着杆子向前爬,速度是 11 厘米/秒。

当两只蚂蚁碰面时,它们会同时掉头往相反的方向爬行。

这些蚂蚁中,有 11 只蚂蚁感冒了。

并且在和其它蚂蚁碰面时,会把感冒传染给碰到的蚂蚁。

请你计算,当所有蚂蚁都爬离杆子时,有多少只蚂蚁患上了感冒。

输入格式

第一行输入一个整数 n, 表示蚂蚁的总数。

接着的一行是 n个用空格分开的整数 Xi, Xi 的绝对值表示蚂蚁离开杆子左边端点的距离。

正值表示头朝右,负值表示头朝左,数据中不会出现 0 值,也不会出现两只蚂蚁占用同一位置。

其中,第一个数据代表的蚂蚁感冒了。

输出格式

输出1个整数,表示最后感冒蚂蚁的数目。

数据范围

1<n<50,
0<|Xi|<100

输入样例1:
3
5 -2 8//开始病蚂蚁向右走
输出样例1:
1
输入样例2:
5
-10 8 -20 12 25//开始病蚂蚁向左走
输出样例2:
3

这边明确所有蚂蚁其实都是等效走了区间长度-pos(起始位置)即可,就是感冒的蚂蚁碰到一个没感冒的传染,那么两只蚂蚁的身份可以互换,也就是两只蚂蚁穿过去接着走,只不过被感染了

那么从起始蚂蚁(假设向右)开始往右看向左走的,如果有向左走的那么再加上起始蚂蚁左边向右走的数量即可,左边等效,初始化用pair把蚂蚁的位置,方向存起来即可。再对位置进行排序,然后处理,其实不写排序也行,两边写个循环判断大于小于就行

#include <iostream>
#include <algorithm>
#include <utility>
using namespace std;
typedef pair<int,int> PII;//x存位置,y存方向(1向右,0向左)
#define x first
#define y second
const int N=51;
PII ant[N];
int n;

int find(int st){//二分查找找到排序后开始生病蚂蚁的位置
    int l=1,r=n;
    while(l<r){
        int mid=(l+r+1)>>1;
        if(st>=ant[mid].x) l=mid;
        else r=mid-1;
    }
    return l;
}

int main(){
    cin>>n;
    int st;
    for(int i=1;i<=n;i++){
        int tmp;cin>>tmp;
        ant[i].x=abs(tmp);
        ant[i].y=(tmp>0?1:0);
        if(i==1) st=ant[i].x;
    }
    sort(ant,ant+n+1);//按照位置排序
    int fir=find(st),ans=1;//找到排序后的刚开始的蚂蚁
    if(ant[fir].y==1){//找向右走生病蚂蚁右边向左走和左边向右走的蚂蚁
        for(int i=fir;i<=n;i++)
        if(ant[i].y==0) ans++;
        if(ans>1)//这边如果右边没有向左走的蚂蚁就不看左边
        for(int i=fir-1;i>=1;i--)
        if(ant[i].y==1) ans++;
    }else{
        for(int i=fir-1;i>=1;i--){
        if(ant[i].y==1) ans++;//看左边有没有向右走的向右走的
        }
        if(ans>1)//如果左边没有向右的就不看右边
        for(int i=fir+1;i<=n;i++)
        if(ant[i].y==0) ans++;
    }
    cout<<ans;
    return 0;
}
Acwing 1216.饮料换购

乐羊羊饮料厂正在举办一次促销优惠活动。乐羊羊C型饮料,凭3个瓶盖可以再换一瓶C型饮料,并且可以一直循环下去(但不允许暂借或赊账)。

请你计算一下,如果小明不浪费瓶盖,尽量地参加活动,那么,对于他初始买入的 n瓶饮料,最后他一共能喝到多少瓶饮料。

输入格式

输入一个整数 n,表示初始买入的饮料数量。

输出格式

输出一个整数,表示一共能够喝到的饮料数量。

数据范围

0<n<10000

此题只需要判断每次将瓶盖数/3的余数,并将余数加入下一次兑换中,但是余数在下一次计算对话的总瓶子数中要减去不然会重复计算,因此我们用两个数来存,一个存每次待兑换的瓶盖,一个sum变量存我能喝到的全部瓶子的个数

#include <iostream>
using namespace std;
int n,sum,k;//k为每次剩下的瓶盖

int check(int m){
    if(m%3==0) k=0;
    else if(m%3==2) k=2;
    else k=1;
    return m/3;
}

void dfs(int u){
    sum+=u-k;
    if(u<3){//小于3个瓶盖换不了了
        cout<<sum;
        return;
    }
    k=0;
    u=check(u);
    dfs(u+k);
}

int main(){
    cin>>n;
    dfs(n);
    return 0;
}

u+k是下一次待兑换的个数,第一次k=0,u=n则刚开始喝到的就是u-k就是n瓶

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值