洛谷 线性Dp官方题单 个人题解(持续更新)

这篇博客探讨了编程中常用的三种算法技术:递归、记忆化搜索和动态规划。通过实例展示了它们在解决任务调度、经验日、过河卒、最大食物链计数等问题上的应用。博客内容涵盖了如何使用这些算法来优化解决方案,提高效率,并给出了具体的代码实现。
摘要由CSDN通过智能技术生成

尼克的任务 题目链接

暴力做法 用递归的时候慎重开全局变量 …不然会debug几个小时

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
struct aaa{
    int st,len;
}a[N];
int f[N];
bool st[N];
int sum,n,ans;
vector<int>v[N];
void dfs(int t,int fre){
    if(t==sum+1){
        ans=max(ans,fre);
        return;
    }
    int cnt=0;
    for(int i=0;i<v[t].size();i++){//枚举这个时间点
        dfs(t+a[v[t][i]].len,fre);//去枚举这个任务结束时的时间
        cnt++;//相当于flag的作用
    }
    if(cnt==0) dfs(t+1,fre+1);//如果当前时间点没有任务,那么空闲时间+1,然后去枚举下一个时间点
}
int main(){
    cin >> sum >> n;//总时间为sum,总任务数为n
    for(int i=1;i<=n;i++){
        cin >> a[i].st >> a[i].len;
        v[a[i].st].push_back(i);//某个时间点所包含的所有任务
    }
    dfs(1,0);//从时间点为1,空闲时间为0开始搜索
    cout << ans;
    return 0;
}

记忆化搜索做法

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
vector<int>v[N];
int n;
int m;
int f[N];
bool st[N];
bool flag;
int dfs(int t,int fr){
    if(st[t]){
        return f[t];
    }
    if(t>n){
        return 0;
    }
    if(v[t].size()==0){
        f[t]=1+dfs(t+1,fr+1);
    }
    else{
        for(int i=0;i<v[t].size();i++){   //看在这个时间点能否给尼克安排工作
            f[t]=max(f[t],dfs(t+v[t][i],fr)); // 去到下一个工作结束时间
        }
    }
    st[t]=true;
    return f[t];
}
int main(){
    //memset(f,-0x3f3f3f,sizeof f);
    cin >> n >> m;
    while(m--){
        int st,last;
        cin >> st >> last;
        v[st].push_back({last});
    }
    int ans=0;
    cout << dfs(1,0);
    return 0;
}

纯dp

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
struct aaa{
    int st,len;
}a[N];
int f[N];//f[i]表示从i-n的休息时间数
int sum,n;
vector<int>v[N];
int main(){
    cin >> sum >> n;
    for(int i=1;i<=n;i++){
        cin >> a[i].st >> a[i].len;
        v[a[i].st].push_back(i);
    }
    for(int i=sum;i>=1;i--){
        if(v[i].size()>0){//如果说这个任务点有任务的话
            for(int j=0;j<v[i].size();j++){
                f[i]=max(f[i],f[i+a[v[i][j]].len]);
            }
        }
        else{//若果没有就加一了
            f[i]=f[i+1]+1;
        }
    }
    cout <<f[1];
    return 0;
}

五倍经验日

题目链接

记忆化搜索
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
long long int f[N][N];
bool st[N][N];
long long int a[N];
long long int lose[N],win[N];
long long int use[N];
int n,sum; 
long long int dp(int i,int j){
	if(i>n) return 0;
	if(st[i][j]) return f[i][j];
	st[i][j]=true;
	if(j>=use[i]){
		f[i][j]=max(dp(i+1,j)+lose[i],dp(i+1,j-use[i])+win[i]);
	}
	else {
		f[i][j]=dp(i+1,j)+lose[i];
	}
	return f[i][j];
}
int main(){
	cin >> n >> sum;
	for(int i=1;i<=n;i++) cin >> lose[i] >> win[i] >> use[i];
	cout << dp(1,sum)*5;
	return 0;
}

dp

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1010;
int n,x;
int win[N],lose[N],use[N];
int f[N];//f[i][j] 表示前i个人且做多用j瓶药时所能获得的最大经验值
//状态转移:要么选择用药过,要么不选择用药过
//选择用药 则 f[i][j]=f[i-1][j-use]+win
//不选择用药  f[i][j]=f[i-1][j]+lose
signed main(){
    cin >> n >> x;
    for(int i=1;i<=n;i++){
        cin >> lose[i] >> win[i] >> use[i];
    }
    for(int i=1;i<=n;i++){
        for(int j=x;j>=0;j--){
            if(use[i]<=j){
                f[j]=max(f[j-use[i]]+win[i],f[j]+lose[i]);
            }
            else {
                f[j]=f[j]+lose[i];
            }
        }
    }
    cout << f[x]*5;
    return 0;
}

过河卒

题目链接

记忆化搜索
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
long long int a[N][N];
long long int f[N][N];
bool st[N][N];
long long int endx,endy,mx,may;
long long int dfs(int x,int y){
	if(x>endx||y>endy) return 0;
	if(st[x][y]) return f[x][y];
	if(x==endx&&y==endy){
		return 1;      //方案数是逐渐被叠加的,这种记忆化搜索方式当然是合理的
	}
	if(a[x+1][y]!=-1){
		f[x][y]=dfs(x+1,y);
	}
	if(a[x][y+1]!=-1){
		f[x][y]+=dfs(x,y+1);
	}
	st[x][y]=true;
	return f[x][y];
}
int main(){
	cin >> endx >> endy >> mx >> may;
	endx=endx+2;
	endy=endy+2;
	a[mx+2][may+2]=-1;
	a[mx-2+2][may-1+2]=-1;
	a[mx-2+2][may+1+2]=-1;
	a[mx-1+2][may-2+2]=-1;
	a[mx-1+2][may+2+2]=-1;
	a[mx+1+2][may-2+2]=-1;
	a[mx+1+2][may+2+2]=-1;
	a[mx+2+2][may-1+2]=-1;
	a[mx+2+2][may+1+2]=-1;
	cout << dfs(2,2);
	return 0;
}


dp

#include<bits/stdc++.h>
#define int long long
using namespace std;
int vis[25][25];
int dp[25][25]; //表示从(2,2)走到(i,j)的总方案数
signed main(){
    dp[2][2]=1;//初始化dp数组,从 (2,2)走到(2,2)的方案数是1;
    int n,m,x,y;
    cin >> n >> m >> x >> y;
    n+=2,m+=2,x+=2,y+=2;//为了防止越界,给所有坐标加上一个偏移量
    vis[x][y]=1;
	vis[x-2][y-1]=1;
	vis[x-2][y+1]=1;
	vis[x+2][y-1]=1;
	vis[x+2][y+1]=1;
	vis[x-1][y+2]=1;
	vis[x-1][y-2]=1;
	vis[x+1][y+2]=1;
	vis[x+1][y-2]=1;
	for(int i=2;i<=n;i++){
	    for(int j=2;j<=m;j++){
	        if(i==2&&j==2){
	            continue;
	        }
	        if(!vis[i][j]){
	            dp[i][j]=dp[i-1][j]+dp[i][j-1]; //类似于数字三角形
	        }
	    }
	}
	cout << dp[n][m];
    return 0;
}

挖地雷

记忆化搜索
#include<bits/stdc++.h>
using namespace std;
const int N=25;
int n;
bool st[N];
vector<int>a[N];//每个地窖与其他地窖的连接情况
int pre[N];
int num[N]; //每个地窖的地雷数
int f[N];//f[i] 表示从 f[i] 开始挖可以挖到的最多地雷数量
int dfs(int x){
    if(st[x]){
        return f[x];
    }
    for(int j=0;j<a[x].size();j++){
        int sb=dfs(a[x][j])+num[a[x][j]];
        if(f[x]<sb){
            f[x]=sb;
            pre[x]=a[x][j];
        }
    }
    return f[x];
}
int main(){
    cin >> n;
    for(int i=1;i<=n;i++){
        cin >> num[i];
     //   f[i]=num[i];
    }
    int c=0;
    for(int i=n-1;i>=1;i--){
        c++;
        int d=c;
        for(int j=1;j<=i;j++){
            int flag;
            cin >> flag;
            if(flag){
                a[n-i].push_back({d+1});
            }
            d++;
        }
    }
    for(int i=1;i<=n;i++){
        dfs(i);
        st[i]=true;
    }
    // for(int i=1;i<=n-1;i++){
    //     for(int j=0;j<a[i].size();j++){
    //         cout << a[i][j]<<" ";
    //     }
    //     cout << endl;
    // }
    int ans=0;
    int res=0;
    for(int i=1;i<=n;i++){
        if(ans<(f[i]+num[i])){
            ans=f[i]+num[i];
            res=i;
        }
    }
    cout << res<<" ";
    while(pre[res]!=0){
        cout << pre[res]<<" ";
        res=pre[res];
    }
    cout << endl;
    cout << ans;
    return 0;
}

最大食物链计数

记忆化搜索
#include<bits/stdc++.h>
using namespace std;
const int N=5010;
const int mod=80112002;
int n,m;
int f[N]; // f[i] 表示从i开始到弱者有几条食物链
bool st[N];
bool q[N],r[N];
vector<int>v[N];
int sum;
int dfs(int x){
	int flag=1;
    if(!q[x]){
        return 1;
    }
    if(st[x]){
        return f[x];
    }
    for(int i=0;i<v[x].size();i++){
        f[x]=(f[x]+dfs(v[x][i]))%mod;
        flag=0;
    }
    if(flag){
    	return 0;
	}
    st[x]=true;
    return f[x];
}
int main(){
    cin >> n >> m;
    for(int i=1;i<=m;i++){
        int a,b;
        cin >> a >> b;
        v[b].push_back({a});
        q[b]=true;//强者名单,不在强者名单里的就是弱者qaq 
		r[a]=true;//弱者名单,不在弱者名单里的就是强者 
    }
    for(int i=n;i>=1;i--){
    	if(!r[i]){
    		sum=(sum+dfs(i))%mod;
		}
	}
	cout << sum;
    return 0;
}

大师

在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;
int n;
const int N=1010,V=40005;
const int vik=20000;
const long long mod=998244353;
int dp[N][V];
int a[N];
void Input(void) 
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) 
	{
		scanf("%d",&a[i]);
	}
	return ;
}
void solve(void) 
{
	long long tot=0;
	for(int i=1;i<=n;i++) 
	{
		tot++;//这里的=1是把i单独作为一个等差数列加的1
		for(int j=1;j<i;j++) 
		{
			dp[i][a[i]-a[j]+vik]=(dp[i][a[i]-a[j]+vik]+dp[j][a[i]-a[j]+vik]+1)%mod;//这里的+1是把i和j单独作为一个等差数列+1
			tot=(tot+dp[j][a[i]-a[j]+vik]+1)%mod; //因为公差可能会重复出现 所以这里,tot要直接加上每一次的转移值,比如进行第二重循环时,dp[5][7]可能会出现两次甚至更多,
			//为了避免重复加,所以要只加每一次的偏移量即可,实在不理解,可以手动模拟一下 不然要ipad干啥?
		}
	}
	cout<<tot;
}
int main(void) 
{
	Input();
	solve() ;
    return 0;
}

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1010;
const int v=20010;//防止数组越界 加一个偏移量
const int mod= 998244353;
int a[N];
int n;
int f[N][v*2];// f[i][j] 表示以第i个数为结尾且公差为k的所有子序列的个数
signed main(){
    int n;
    cin >> n;
    for(int i=1;i<=n;i++){
        cin >> a[i];
    }
    int res=0;
    for(int i=1;i<=n;i++){
        res++;
        for(int j=1;j<i;j++){
            f[i][a[i]-a[j]+v]=(f[i][a[i]-a[j]+v]+f[j][a[i]-a[j]+v]+1)%mod;
            res=(res+f[j][a[i]-a[j]+v]+1)%mod;
        }
    }
    cout << res;
    return 0;
}

参考:来源1

来源2

导弹拦截

//第一问求最长不下降子序列 ,第二问 用到一个定理
//一个序列可以拆成最少n个最长不下降子序列的个数=这个序列LIS的长度
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int f[N],p[N];
int a[N],b[N];
int main(){
    int i=0;
    while(cin >> a[i]){
        i++;
    }
    for(int j=i-1,k=0;j>=0;j--,k++){
        b[k]=a[j];
    }
    int len=0;
    for(int j=0;j<i;j++){
        int l=0,r=len;
        while(l<r){
            int mid = l + r + 1 >> 1;
            if(f[mid]<=b[j]){
                l=mid;
            }
            else {
                r=mid-1;
            }
        }
        len=max(len,r+1);
        f[r+1]=b[j];
    }
    cout << len << endl;
    len=0;
    int n=i;
    for(i=0;i<n;i++){
        int l=0,r=len;
        while(l < r ){
            int mid= l+r+1 >> 1;
            if(p[mid]<a[i]){ //我们要找的是小于它的最大值
                l=mid;
            }
            else {
                r=mid-1;
            }
        }
        len=max(len,r+1);
        p[r+1]=a[i];
    }
    cout << len <<endl;
    return 0;
}

最长公共子序列洛谷版本

//利用全排列性质 nlogn做法
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N],b[N];
int mp[N];
int f[N];
int n;
int main(){
    cin >> n;
    for(int i=0;i<n;i++){
        cin >> a[i];
        mp[a[i]]=i;
    }
    for(int i=0;i<n;i++){
        cin >> b[i];
    }
    for(int i=0;i<n;i++){
        b[i]=mp[b[i]];
    }
    int len;
    for(int i=0;i<n;i++){
        int l=0,r=len;
        while(l<r){
            int mid = l+r+1 >>1;
            if(f[mid]<b[i]){
                l=mid;
            }
            else {
                r=mid-1;
            }
        }
        len=max(len,r+1);
        f[r+1]=b[i];
    }
    cout << len;
    return 0;
}

在这里插入图片描述
在这里插入图片描述参考来源

鸣人的影分身

经典整数划分问题,状态转移不太好想,根据每个方案是否含0来进行划分
#include<bits/stdc++.h>
using namespace std;
int main(){
    int t;
    cin >> t;
    while(t--){
        int n,m;
        cin >> m >> n;
        int f[21][21];
        memset(f,0,sizeof f);
        f[0][0]=1;
        for(int i=0;i<=m;i++){
            for(int j=1;j<=n;j++){
                f[i][j]=f[i][j-1];
                if(i>=j){
                    f[i][j]+=f[i-j][j];
                }
            }
        }
        cout << f[m][n] << endl;
    }
    return 0;
}

方格取数

//数字三角形->摘花生 拓展
#include<bits/stdc++.h>
using namespace std;
const int N=15;
int f[N*2][N][N];  //第一维代表行列之和  f[k][i][j] 表示当前的行列之和为k 第一条路径走到第i行
//第二条路径走到第j行 ,两条路径的行列之和是可以公用的
int g[N][N];
int n,a,b,c;
int main(){
    cin >> n;
    while(cin >> a >> b >> c,a||b||c) g[a][b]=c;
    for(int k=2;k<=2*n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                int c1=k-i,c2=k-j;
                int t=g[i][c1];
                if(i!=j){
                    t+=g[j][c2];
                }
                if(c1>=1&&c1<=n&&c2>=1&&c2<=n){//防止越界 ,比如当枚举到二人同时走3步时即是k=3时,j可能已经枚举到6此时就不满足状态表示所赋予的含义
                    f[k][i][j]=max(max(f[k-1][i-1][j],f[k-1][i][j-1]),max(f[k-1][i][j],f[k-1][i-1][j-1]))+t;
                }
            }
        }
    }
    cout << f[2*n][n][n];
    return 0;
}

可以参考:优质题解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值