记录一些有点难(指我不会写的)的dp

Posted KaaaterinaX

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记录一些有点难(指我不会写的)的dp相关的知识,希望对你有一定的参考价值。

HDU3236 Gift Hunting
好神奇,一开始以为是标记两个背包的状态,结果发现根本没法处理某个物品放在了哪个背包里。。

还没写完,稍微想一下思路大概就是先处理必须选择的物品,然后标记选完所有必选物品能达到的所有状态,再根据这些状态对非必选的物品进行状态转移。
给出状态分析思路: 代码t掉了,找个爹来帮我改改😅?

const int maxn=350;
const int maxm=505;

int p1[maxn],h1[maxn];
int p0[maxn],h0[maxn];
//记录所有必须买的物品买完了之后能到达的状态,然后可买可不买的物品从这些状态开始转移
int now[maxm][maxm][2];//滚动数组dp[v1][v2][0/1]=最大价值,记录当前用了v1/v2中的体积,以及是否使用免费的卷
int last[maxm][maxm][2];
bool vis[maxn][maxn][2];//记录把必须买的物品全买了之后达到的所有状态

———————————————————————————————————
CF688E The Values You Can Make
相当于两个维度的01背包(个人抽象理解),状态分析很巧妙。

const int maxn=507;
bool dp[maxn][maxn];//dp[i][j]=0/1,表示当前硬币总价值为i,这些硬币能否凑成j
int a[maxn];
int main()
    int n,K;
    cin>>n>>K;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    
    dp[0][0]=1;
    for(int k=1;k<=n;k++)
        for(int i=K;i>=0;i--)
            for(int j=K;j>=0;j--)
                if(dp[i][j]==1)
                    if(i+a[k]<=K)
                        dp[i+a[k]][j]=1;
                        dp[i+a[k]][j+a[k]]=1;
                    
                
            
        
    
    int ans=0;
    for(int i=0;i<=K;i++)
        if(dp[K][i]==1)
            ans++;
        
    
    cout<<ans<<endl;
    for(int i=0;i<=K;i++)
        if(dp[K][i]==1)
            cout<<i<<' ';
        
    
    cout<<endl;

———————————————————————————————————

CF82D Two out of Three

如何选择状态,才能让操作不具有后效性?

通过观察得出,在第 i i i次收银的时候,可以选择的人只有 k , i ∗ 2 , i ∗ 2 + 1 , k k,i * 2,i * 2+1,k ki2i2+1k为上一次选择剩下的那个人。
那么每次选择只需要记录剩下了谁,因为只有这个会对后续选择产生影响。

这样一来状态转移方程就很好设了!

PS:这个题记忆化搜索部分我不会写。。把第一个答案写出来我已经尽力了TT。
(我晚点回来补上qaq)


//好了。。爷写了能算出第一个答案的代码了,接下来要进行记忆化搜索,记录状态是如何转移的。。。


const int maxn=1e3+100;//允许往后越界?

int a[maxn];//每个人的权值

int dp[maxn][maxn];//dp[i][j]=选了i次,剩下j号人的最小花费

int pr[maxn][maxn][3];//记忆化数组

int main()
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    
    memset(dp,INF,sizeof(dp));
    
    dp[1][3]=max(a[1],a[2]);
    dp[1][2]=max(a[1],a[3]);
    dp[1][1]=max(a[2],a[3]);
    
    for(int i=2;i<=(n>>1)+1;i++)
        //第i次选择
        //对每个可选择剩下的元素进行枚举
        for(int j=1;j<=i*2+1;j++)
            if(j==i*2+1)
                //这种情况可以由上一次所有的状态转移而来
                int minn=INF;
                for(int k=1;k<=(i-1)*2+1;k++)
                    minn=min(minn,dp[i-1][k]+max(a[k],a[i*2]));
                
                dp[i][j]=minn;
            
            else if(j==i*2)
                int minn=INF;
                for(int k=1;k<=(i-1)*2+1;k++)
                    minn=min(minn,dp[i-1][k]+max(a[k],a[i*2+1]));
                
                dp[i][j]=minn;
            
            else
                dp[i][j]=dp[i-1][j]+max(a[i*2],a[i*2+1]);
            
        
    
//    for(int i=1;i<=n/2+1;i++)
//        cout<<"i="<<i<<endl;
//        for(int j=1;j<=i*2+1;j++)
//            cout<<"j="<<j<<endl;
//            cout<<"dp="<<dp[i][j]<<endl;
//        
//        cout<<endl;
//    
    cout<<dp[(n>>1)+1][n+1]<<endl;

———————————————————————————————————
Max Sum Plus Plus
牛马题目,看了好久好久好久好久好久好久。。hatui

n的范围非常大,所以必须压缩时间以及空间。用到了dp优化的常用手段:记录最大值避免重复遍历、滚动数组优化。

const int maxn=1e6+7;

ll a[maxn];

ll dp[maxn];//记录当前组选到i时的最大值
ll Max[maxn];//记录上一组的最大值,有效位置仅到j-1

int main()
    int m,n;
    while(scanf("%d%d",&m,&n)!=EOF)
        for(int i=1;i<=n;i++)
            scanf("%lld",&a[i]);
            dp[i]=0;
            Max[i]=0;
        
        
        ll x=0;
        //对于每一个数,可以选择留在当前区间(如果它的前一个数选了的话),或到下一个区间
        for(int i=1;i<=m;i++)
            //选到第m组
            x=-LINF;//找这个组当前的最大值
            for(int j=i;j<=n;j++)
                dp[j]=max(dp[j-1]+a[j],Max[j-1]+a[j]);//可以选择接上一个或者重新开一组
                //边界问题:如果当前j=i,那么dp[j-1]和Max[j-1]记录的都是上一组选到j-1的最大值
                Max[j-1]=x;//选这一组的时候不会再用到这个值了,所以更新为第i组选到j-1的最大值,而不再是i-1组了
                x=max(x,dp[j]);
            
        
        printf("%lld\\n",x);
    

———————————————————————————————————
凸多边形的划分(acwing1069非免费题库)

是个很巧妙的区间dp。如果看出结果呈区间分布,思路就豁然开朗了。

首先这个数据的范围很大,long long都存不下,所以要用快读/快写来输入/输出int_128。

举个例子,对于一个八边形,如图:

整个图形的划分可以由三段连续的区间合并而来,各区间的划分互不干扰

从边界开始分析,可以得出状态转移方程。

__int128 a[105];

__int128 dp[105][105];

//环形区间dp?
//单起点枚举?
int main()
    int n;
    read(n);
    for(int i=1;i<=n;i++)
        read(a[i]);
        //a[i+n]=a[i];
    
    //最少都是三个节点一个区间
    //两个节点一个区间的可以直接默认为0
    for(int len=3;len<=n;len++)
        //枚举起点
        for(int i=1;i<=n-len+1;i++)
            int j=i+len-1;
            //枚举从哪转移
            dp[i][j]=1e30;
            for(int k=i+1;k<j;k++)
                dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+a[i]*a[j]*a[k]);
            
        
    
    write(dp[1][n]);
    cout<<endl;

———————————————————————————————————
D. Coloring Brackets

前俩天这题过了,确实很有意思的一个题目。

有点水。。过几天重构一下。】】】】

const int maxn=710;
const int R=1;
const int B=2;
const int mod=1000000007;

string x;

ll dp[maxn][maxn][4][4];

int a[maxn];//记录匹配的括号

void match()
    //括号匹配
    stack<int> s;
    for(int i=0;i<x.size();i++)
        if(x[i]=='(')
            s.push(i);
        
        else
            a[s.top()]=i;
            a[i]=s.top();
            s.pop();
        
    


//这是一个神奇的区间dp,运用的是记忆化搜索的方法状态转移()

ll DP(int l,int r,int lc,int rc)
    //lc和rc是传入外层的颜色
    ll res=0;
    if(l>=r) return 1;
    if(dp[l][r][lc][rc]!=-1) return dp[l][r][lc][rc];
    if(a[l]==r)
        //如果这个l r是匹配的
        if(lc!=B) res+=DP(l+1,r-1,B,0)%mod;
        if(lc!=R) res+=DP(l+1,r-1,R,0)%mod;
        if(rc!=B) res+=DP(l+1,r-1,0,B)%mod;
        if(rc!=R) res+=DP(l+1,r-1,0,R)%mod;
    else
        //如果不是匹配的,那么就继续分成更小的区间
        int m=a[l];
        if(lc!=R) res+=DP(l+1,m-1,R,0)*DP(m+1,r,0,rc)%mod;
        if(lc!=B) res+=DP(l+1,m-1,B,0)*DP(m+1,r,0,rc)%mod;
        res += (DP(l+1, m-1, 0, R) * DP(m+1, r, R, rc)) % mod;
        res += (DP(l+1, m-1, 0, B) * DP(m+1, r, B, rc)) % mod;
    
    dp[l][r][lc][rc]=res%mod;
    return res%mod;
    

int main()
    cin>>x;
    //跑一遍括号匹配,只需要记录每个字符匹配的字符在什么位置
    match();
    
    int n=(int)x.size();
    
    memset(dp,-1,sizeof dp);
    
    cout<<DP(0,n-1,0,0)<<endl;
    

以上是关于记录一些有点难(指我不会写的)的dp的主要内容,如果未能解决你的问题,请参考以下文章

经典矩阵快速幂加速dp递推——namomo #1 c

01背包

FCS NOI2018福建省冬摸鱼笔记 day4

Re0:DP学习之路 01背包如何打印路径?

背包但物品数量均匀

Max Sum Plus Plus HDU - 1024 基础dp 二维变一维的过程,有点难想