ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

dp专题

2022-07-27 19:05:28  阅读:175  来源: 互联网

标签:专题 int max HP len include dp


P1280 尼克的任务

链接:https://www.luogu.com.cn/problem/P1280

本题的关键在于顺推不好做 想着要倒推 想着要统计开始的时间点 排序是为了消除后效性!!!!是个非常好的模型

#include<iostream>  
#include<algorithm>  
using namespace std;  
long int n,k,sum[10001],num=1,f[10001];  
struct ren//结构体,一起排序 ,从大到小   
{  
    long int ks,js;  
};  
ren z[10001];  
int cmp(ren a,ren b)  
{  
    return a.ks>b.ks;  
}  
int main()  
{  
    long int i,j;   
    cin>>n>>k;  
    for(i=1;i<=k;i++)  
    {  
    cin>>z[i].ks>>z[i].js;    
    sum[z[i].ks]++;  
    }  
    sort(z+1,z+k+1,cmp);  
    for(i=n;i>=1;i--)//倒着搜   
    {  
        if(sum[i]==0)  
        f[i]=f[i+1]+1;  
        else for(j=1;j<=sum[i];j++)  
        {  
            if(f[i+z[num].js]>f[i])  
            f[i]=f[i+z[num].js];  
            num++;//当前已扫过的任务数   
        }  
    }  
    cout<<f[1]<<endl;  
}  

这个题目和费用提前算不太一样 两者本质上都是消除后效性

这个题不同的就是不知道后面到底哪些要选 而费用提前算是后面的一定都是会选的

所以这个题巧妙地从后往前转移 这样转移前面的一定是后面最优策略 转移方程非常像算期望值

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#define maxn 100005
double f[maxn];
double n,k,c,w;
int a[maxn],b[maxn];
int main()
{
    int i;
    scanf("%lf%lf%lf%lf",&n,&k,&c,&w);
    for(i=1;i<=n;i++) scanf("%d%d",&a[i],&b[i]);
    for(i=n;i>=1;i--)
    {
        if(a[i]==1) f[i]=max(f[i+1],f[i+1]*(1-k/100)+b[i]);
        if(a[i]==2) f[i]=max(f[i+1],f[i+1]*(1+c/100)-b[i]);
    }
    printf("%.2lf",f[1]*w);
}

这个dp以前没见过 确实不会 怎么才能保证两两互不相交呢 在我印象里面没有这样操作过的dp

考虑换个想法 设dp[i,j,k]表示 前i个 两者差值为j 用了k次加倍 因为下标不能为负 所以整体向右偏移1300

初始化 dp[0,1300,0]=0 答案 max{dp[n,1300,i]} i属于[0,k]

转移方程:dp[i,j,k]=max(dp[i-1,j+v[i],k]+w[i],dp[i-1,j-v[i],k]+w[i],dp[i-1,j+2v[i],k-1]+w[i],dp[i-1,j-2v[i],k-1]+w[i])

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int N=103;
typedef long long ll;
ll f[N][3003][N],w[N],v[N];
int main()
{
	memset(f,-0x3f,sizeof f);
	f[0][1300][0]=0;
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		scanf("%lld%lld",&w[i],&v[i]);
	for(int i=1;i<=n;i++)
	for(int j=0;j<=m;j++)
	for(int k=0;k<=2600;k++)
	{
		f[i][k][j]=f[i-1][k][j];
		if(k>=2*v[i]&&j>=1)
			f[i][k][j]=max(f[i][k][j],f[i-1][k-2*v[i]][j-1]+w[i]);
		if(k>=v[i])
			f[i][k][j]=max(f[i][k][j],f[i-1][k-v[i]][j]+w[i]);
		if(k+2*v[i]<=2600&&j>=1)
			f[i][k][j]=max(f[i][k][j],f[i-1][k+2*v[i]][j-1]+w[i]);
		if(k+v[i]<=2600)
			f[i][k][j]=max(f[i][k][j],f[i-1][k+v[i]][j]+w[i]);
	}
	ll ans=-0x3f3f3f3f;
	for(int i=0;i<=m;i++)
		ans=max(ans,f[n][1300][i]);
	cout<<ans;
	return 0;
}

https://www.luogu.org/problem/P2577

分析:

首先本题不是贪心排序就是dp

再数据范围<=200,就只有可能是dp

考虑本题,容易想到应该尽量让那些打饭快却吃饭慢的排在前面

又因为所有人打饭的总时间是一定的,

即无论怎么安排所有人打完饭都会耗费这么多时间

所以我们只用考虑吃饭慢的排在前面则一定是最优的

但此时有两个窗口,怎样安排就要dp了

  • f[i,j]记录前i个人排队,第一队用时为j的情况下最大用时。

  • 不记录第2队状态的原因是可以由第一队状态推出来。

  • 为了便于计算,建议使用前缀和维护一下,可以较为简易的计算出第2队的情况。

  • 那么我们就得到了这样个方程:

    加入第一队的情况下: f[i,j]=min(f[i,j],max(f[i-1,j-a[i].x],j+a[i].y));

    当前最小时间为上一个人用的时间和这一个人用的时间的最大值

    加入第二队同理

    f[i,j]=max(f[i-1,j],a[i].y+b[i]-j)其中b[i]为前i个人排队所用的总时间

    然而200*40000好像有点大,降维呗!

    类似背包一样降维就行

    code by std:

#include<bits/stdc++.h>
using namespace std;
struct lsg{int x,y;}a[1000];
int n,f[400001],sum,ans,b[1000];
bool pd(lsg x,lsg y){return x.y>y.y;}
int main(){
    ios::sync_with_stdio(false);
    cin>>n;
    for (int i=1;i<=n;i++)cin>>a[i].x>>a[i].y;
    sort(a+1,a+1+n,pd);memset(f,10,sizeof(f));f[0]=0;
    for (int i=1;i<=n;i++)b[i]=a[i].x+b[i-1];
    for (int i=1;i<=n;i++){
            for (int j=sum;j>=0;j--){
                f[j+a[i].x]=min(f[j+a[i].x],max(f[j],a[i].y+j+a[i].x));//将i加入第一个队列
                    f[j]=max(f[j],a[i].y+b[i]-j);//将i加入第二个队列
                }
            sum+=a[i].x;
        }
    ans=1e9;
    for (int i=1;i<=sum;i++)ans=min(ans,f[i]);
    cout<<ans<<endl;
}

https://www.luogu.org/problem/P2467

这是一道好题

题目描述

求1-n排列组成的波动数列的个数

因为要比较大小关系 所以dp里面最后数是多少是要记录的 又因为是每个数都不重复的排列

所以定义dp[i,j]表示用前 i 个数 最后一个数为 j 的方案数

dp[i,j]相当于dp[i-1,k]中原排列大于等于j的数都加1,再把j插到末尾后的新合法排列的方案数

答案有“M"型与"W"型,显然方案数是一样的,这里只考虑"W"型的,最后把答案*2就行了


用一个前缀和维护一下就好

这时你可能会有疑问,为什么偶数是枚举[1,j-1],而奇数是枚举[j,i-1],

因为只考虑“W”形态的,所以奇数一定是山峰的,而偶数一定山谷

偶数就不用再每个加一了 直接加进去就好 奇数就要每个大于等于 j 的加一

所以奇数枚举的一定要比前一个位置上的数大,偶数枚举的一定要比前一个位置上的数小

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define N 4211
#define M(a) ((a)<=mod?(a):(a-mod))
inline int read(){
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-')f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        x=(x<<3)+(x<<1)+c-'0';
        c=getchar();
    }
    return x*f;
}
int n,mod;
int dp[N][N],ans=0;
int b[N];
int lowbit(int x){
    return x&(-x);
}
void Add(int x,int d){
    while(x<=n){
        b[x]=M(b[x]+d);
        x+=lowbit(x);
    }
}
int Ask(int x){
    int ans=0;
    while(x){
        ans=M(ans+b[x]);
        x-=lowbit(x);
    }
    return ans;
}
int main(){
    n=read(),mod=read();
    for(int i=1;i<=n;i++){
        dp[1][i]=1;
        Add(i,1);
    }
    for(int i=2;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(i&1){
                if(i>j){
                    dp[i][j]=M(Ask(i-1)-Ask(j-1)+mod);
                }
            }
            else{
                dp[i][j]=Ask(j-1);
            }
        }
        memset(b,0,sizeof(b));
        for(int j=1;j<=n;j++){
            Add(j,dp[i][j]);
        }
    }
    for(int i=1;i<=n;i++){
        ans=M(ans+dp[n][i]);
    } 
    cout<<2*ans%mod<<endl;
    return 0;
}

https://www.luogu.org/problem/P1005

分析:

发现啊,每一行怎么取数是互不干扰的,,只用分别处理每一行就好

数据范围也在算法复杂度以内

很好联想到区间dp

dp[i,j]表示处理到该行,区间[i,j]的最优解

考虑怎么转移

只有从[i-1,j]和[i,j+1]转移过来(因为转移逐渐将区间缩小

因为题目中说要取完,但是空区间是DP不出来的,然后就得手动模拟每个长度为1的区间

具体高精啊,区间dp啊,见代码了

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>

using namespace std;

const int MAXN = 85, Mod = 10000; //高精四位压缩大法好 
int n, m;
int ar[MAXN];

struct HP {
    int p[505], len;
    HP() {
        memset(p, 0, sizeof p);
        len = 0;
    } //这是构造函数,用于直接创建一个高精度变量 
    void print() {
        printf("%d", p[len]);  
        for (int i = len - 1; i > 0; i--) {  
            if (p[i] == 0) {
                printf("0000"); 
                continue;
            }
            for (int k = 10; k * p[i] < Mod; k *= 10) 
                printf("0");
            printf("%d", p[i]);
        }
    } //四位压缩的输出 
} f[MAXN][MAXN], base[MAXN], ans;

HP operator + (const HP &a, const HP &b) {
    HP c; c.len = max(a.len, b.len); int x = 0;
    for (int i = 1; i <= c.len; i++) {
        c.p[i] = a.p[i] + b.p[i] + x;
        x = c.p[i] / Mod;
        c.p[i] %= Mod;
    }
    if (x > 0)
        c.p[++c.len] = x;
    return c;
} //高精+高精 

HP operator * (const HP &a, const int &b) {
    HP c; c.len = a.len; int x = 0;
    for (int i = 1; i <= c.len; i++) {
        c.p[i] = a.p[i] * b + x;
        x = c.p[i] / Mod;
        c.p[i] %= Mod;
    }
    while (x > 0)
        c.p[++c.len] = x % Mod, x /= Mod;
    return c;
} //高精*单精 

HP max(const HP &a, const HP &b) {
    if (a.len > b.len)
        return a;
    else if (a.len < b.len)
        return b;
    for (int i = a.len; i > 0; i--)
        if (a.p[i] > b.p[i])
            return a;
        else if (a.p[i] < b.p[i])
            return b;
    return a;
} //比较取最大值 

void BaseTwo() {
    base[0].p[1] = 1, base[0].len = 1;
    for (int i = 1; i <= m + 2; i++){
        base[i] = base[i - 1] * 2;
    }
} //预处理出2的幂 

int main(void) {
    scanf("%d%d", &n, &m);
    BaseTwo();
    while (n--) {
        memset(f, 0, sizeof f);
        for (int i = 1; i <= m; i++)
            scanf("%d", &ar[i]);
        for (int i = 1; i <= m; i++)
            for (int j = m; j >= i; j--) { //因为终值是小区间,DP自然就从大区间开始 
                f[i][j] = max(f[i][j], f[i - 1][j] + base[m - j + i - 1] * ar[i - 1]); 
                f[i][j] = max(f[i][j], f[i][j + 1] + base[m - j + i - 1] * ar[j + 1]);
            } //用结构体重载运算符写起来比较自然 
        HP Max;
        for (int i = 1; i <= m; i++)
            Max = max(Max, f[i][i] + base[m] * ar[i]);
        ans = ans + Max; //记录到总答案中 
    }
    ans.print(); //输出 
    return 0;
}

https://www.luogu.org/problem/P2511

分析

第一问,求最长的最短,很显然一个二分就行

那第二问计数

f[i,j]代表前i个数分成j块的方案数,

f[i,j]=Σ f[k,j-1] (k>=left[i]&&k<i) ,而因为空间问题,是不可以开1000*50000个数组的,

考虑用到前缀和 因为[ , j ] 都是由 [ , j-1]转移过来的 直接降维 每次更新完f数组 最后更新前缀数组s

这是一道非常好的动规优化

时间复杂度(N*M)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
using namespace std;

int read()
{
    int x=0,f=1;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return x*f;
}

const int mod=10007;
const int maxn=50010;
int n,m,mx,ans;
int a[maxn],sum[maxn];
int dp[maxn],S[maxn];
int rem[maxn];

int check(int x)
{
    int tot=0,len=0;
    for(int i=1;i<=n;++i)
    {
        if(len+a[i]>x) tot++,len=a[i];
        else len+=a[i];
        if(tot>m) return 0;
    }
    return tot<=m;
}

int DP(int x)
{
    int k=0;
    for(int i=1;i<=n;++i)
    for(;k<i;++k)
    if(sum[i]-sum[k]<=x){ rem[i]=k; break;}
    
    int res=(sum[n]<=x);//因为后面是从2开始枚举的 特判是否有只能为1段的情况

    for(int i=1;i<=n;++i)
    {
    	if(sum[i]<=x) dp[i]=1;//初始化很重要 此时的dp[i] 表示前i个数分为1段的方案 如果没法分为1段的dp值就为0
    	S[i]=(S[i-1]+dp[i])%mod;
    }
    
    for(int i=2;i<=m+1;++i)
    {
        for(int j=1;j<=n;++j)
        {
            dp[j]=S[j-1];
            //非常常见的一种方法 用于转移的时候数组下标可能为负 取0
            if(rem[j]-1>=0) dp[j]=((dp[j]-S[rem[j]-1])%mod+mod)%mod;//注意减法出现负数
        }
        for(int j=1;j<=n;++j)
        S[j]=(S[j-1]+dp[j])%mod;
        
        res=(res+dp[n])%mod;
    }
    return res;
}

int main()
{
    n=read();m=read();
    for(int i=1;i<=n;++i) 
    a[i]=read(),sum[i]=sum[i-1]+a[i],mx=max(mx,a[i]);
    
    int L=mx,R=sum[n],mid;
    while(L<R)
    {
        mid=L+R>>1;
        if(check(mid)) ans=mid,R=mid;
        else L=mid+1;
    }
    printf("%d %d",ans,DP(ans));
    return 0;
}

标签:专题,int,max,HP,len,include,dp
来源: https://www.cnblogs.com/wzxbeliever/p/16525928.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有