2023ACM暑期集训 DAY 3

目前进度——动态规划1:线性dp、背包问题,区间

好题

1012 [NOIP1999]拦截导弹

前言

本题不重要,重要的是关于伪单调栈求最长单调子序列的理解

伪单调栈求最长单调子序列的一些理解重点

  1. 最终伪单调栈中的序列不一定是最长单调子序列。
  2. 以最长上升子序列为例,解释操作的意图。若目前元素 \(a_i\) 大于栈首元素,则可直接接在栈首,成为新的栈首;否则,需要在栈中二分第一个大于等于 \(a_i\) 的数,使之被替换成 \(a_i\)。接下来介绍否则后操作的意图。易发现,\(a_i\) 除通过大于栈首而成为栈首之外,还可通过让栈首第一个大于等于其而替换掉栈首,因为根据贪心,这么做可以在后面接更多的数。于是产生新的疑惑:为何不能只判断栈首是否是第一个大于等于 \(a_i\) 的数,如果是替换掉即可,为什么还要替换其他位置?因为在遍历到 \(a_i\) 之前,可能有多个栈元素因无法成为栈首而作为了栈中的其他元素,这些元素虽然无法成为栈首但仍有可能比 \(a_i\) 更有资格成为栈首,所以 \(a_i\) 要成为栈首就要先满足比他们都有资格然后才可以去挑战栈首。故替换其他位置的意图,就是要存储这些虽然不是栈首但有一定资格成为栈首的元素
  3. 我们易发现在最长上升子序列中,寻找的是第一个小于等于 \(a_i\) 的栈元素;在最长不下降子序列中,寻找的是第一个小于 \(a_i\) 的元素。为何会有此差别?思考易得,若为小于等于则栈中无值大小相等的元素,满足上升;小于则栈中可有值相等的元素,满足不下降。

1014 小A买彩票

标签

概率DP

思路

  1. 等权重的递推概率 DP,唯一的注意点是盈利的最小值是负值,需要变换零点,使得盈利的等价最小值为 \(0\)\(1\)

代码

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxw=110,maxn=32;
long long dp[maxn][maxw],dw[4]={-1,-2,0,1},n;
long long gcd(long long a,long long b)
{
    if(a<b) swap(a,b);
    if(b==0) return a;
    else return gcd(b,a%b);
}
int main()
{
    scanf("%lld",&n);
    memset(dp,-1,sizeof(dp));
    dp[0][61]=1;
    for(int i=0;i<n;i++)
    {
        for(int j=1;j<=n+61;j++)
        {
            if(dp[i][j]==-1) continue;
            for(int k=0;k<4;k++)
            {
                if(dp[i+1][j+dw[k]]==-1) 
                    dp[i+1][j+dw[k]]=0;
                dp[i+1][j+dw[k]]+=dp[i][j];
            }
        }
    }
    long long sum=0,sumi=0;
    for(int i=1;i<=n+61;i++)
        if(dp[n][i]!=-1)
        {
            sum+=dp[n][i];
            if(i>=61) sumi+=dp[n][i];
        }
    long long g=gcd(sum,sumi);
    if(sumi==0) printf("0/1");
    else printf("%lld/%lld",sumi/g,sum/g);
    return 0;
}

1015 购物

标签

有限制的01背包

思路

  1. 如标签所言,为有限制的背包问题,具体表现在任意两个拿去物品的位置大于等于 \(k\)。故若设状态 \(dp_{i,j}\) 为拿取前 \(i\) 个物品中的 \(j\) 个所能达到的最大欢迎度,则需特殊注意 \(j=0\)\(j=1\) 的情形,因为在这种情形下不受到位置关系的制约,进而无需以下标差大于等于 \(k\) 作为转移条件。
  2. 时间复杂度为 \(\mathcal O(nm)\)

代码

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e4+100;
const int INF=2147483647;
int n,m,k,dp[maxn][maxn],w[maxn];
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++)
        scanf("%d",&w[i]);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            dp[i][j]=-INF;
    dp[1][0]=0,dp[1][1]=w[1];
    for(int i=2;i<=n;i++)
    {
        dp[i][0]=0;
        for(int j=1;j<=m;j++)
        {
            dp[i][j]=dp[i-1][j];
            if(j==1) dp[i][j]=max(dp[i][j],w[i]);
            if(i-k>=1&&dp[i-k][j-1]!=-INF)
                dp[i][j]=max(dp[i][j],dp[i-k][j-1]+w[i]);
        }
    }
    printf("%d",dp[n][m]);
    return 0;
}

1017 [NOIP2001]装箱问题

思路

  1. bool型的 DP 数组可以存储某种状态的存在情况。

代码

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=35,maxv=2e4+10;
int n,v,w[maxn];
bool dp[maxn][maxv];
int main()
{
    scanf("%d%d",&v,&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&w[i]);
    dp[1][0]=true,dp[1][w[1]]=true;
    for(int i=1;i<n;i++)
    {
        for(int j=0;j<=v;j++)
        {
            if(!dp[i][j]) continue;
            dp[i+1][j]=true;
            if(j+w[i+1]<=v) dp[i+1][j+w[i+1]]=true;
        }
    }
    int ans=0;
    for(int i=0;i<=v;i++)
        if(dp[n][i]!=false) ans=max(ans,i);
    printf("%d",v-ans);
    return 0;
}

1022 Music Problem

思路

  1. 通过取模的方式优化空间。
  2. 注意:本题因为整点时间会因为不播放任意一首歌便存在,故不能简单的只用 bool 型 DP 数组存储存在状态,必须存储各种状态存在的次数

代码

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int mod=3600;
const int maxn=1e5+10;
int T,n,a[maxn];
int dp[2][mod];
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            a[i]%=mod;
        }
        memset(dp,0,sizeof(dp));
        dp[0][0]=1;
        for(int i=1;i<=n;i++)
        {
            if(dp[0][0]>1) break;
            for(int j=0;j<mod;j++)
                dp[1][j]+=dp[0][(j-a[i]+mod)%mod];
            for(int j=0;j<mod;j++)
                dp[0][j]+=dp[1][j],dp[1][j]=0;
        }      
        if(dp[0][0]>1) printf("YES\n");
        else printf("NO\n");
    }
}

1023 美味菜肴

标签

物品价值随时间损失的 01 背包问题

思路

  1. 考虑普通 01 背包可不论物品存储次序的原因。易得,因为对于两个物品 \(i\)\(j\),无论先将 \(i\) 放进背包,还是先将 \(j\) 放进背包,最终得到的价值均为 \(v_i+v_j\)——即最终价值与物品拿取次序无关。但是,若如本题物品价值随时间以不同的速率减少,进而导致最终价值与物品拿取次序有关,则需先规定物品拿取次序,则进行 01 背包
  2. 考虑如何规定物品次序。解不等式 \(a_1-b_1t_1+a_2-b_2(t_1+c_1)\le a_2-b_2t_1+a_1-b_1(c_2+t_1)\) 得,\(\frac{b_1}{c_1}\le \frac{b_2}{c_2}\)。故可知当 \(\frac{b_1}{c_1}\le \frac{b_2}{c_2}\) 时,先取 2 好物体会使最终价值更大。以此类推可得,物品拿取次序按照 \(\frac{b}{c}\) 由小到大的顺序进行。

代码

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=55,maxt=1e6+5;
const ll INF=1e18;
int n,m;
ll b[maxn];
ll dp[maxt],T;
struct node
{
    int a;
    ll bi;
    ll c;
} p[maxn];
bool cmp(node x,node y)
{
    return y.c*x.bi>x.c*y.bi;
}
int main()
{
    scanf("%d%d%lld",&n,&m,&T);
    for(int i=1;i<=n;i++)
        scanf("%lld",&b[i]);
    for(int i=1;i<=m;i++)
    {
        int id;
        scanf("%d%d%lld",&id,&p[i].a,&p[i].c);
        p[i].bi=b[id];
    }
    sort(p+1,p+m+1,cmp);
    memset(dp,-0x7f,sizeof(dp));
    dp[0]=0;
    for(int i=1;i<=m;i++)
    {
        for(int j=T;j>=p[i].c;j--)
            dp[j]=max(dp[j],dp[j-p[i].c]+p[i].a-1ll*j*p[i].bi);
    }
    ll ans=-1e18;
    for(int i=1;i<=T;i++)
        ans=max(ans,dp[i]);
    printf("%lld",ans);
    return 0;
}

1025 队伍配置

思路

  1. 滚动数组优化空间。

代码

点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
using namespace std;
const int maxn=305,maxc=140;
const int INF=2147483647;
int dp[maxc][10][10],n,m,d,ans;
int a[maxn<<1],b[maxn<<1];
int main ()
{
	scanf("%d%d%d",&n,&m,&d);
	for(int i=1;i<=n+m;i++)
		scanf("%d%d",&a[i],&b[i]);
	for(int i=1;i<=n;i++)
	{
		for(int j=d;j>=b[i];j--)
		{
			for(int k=1;k<=min(5,i);k++)
            {
				dp[j][k][0]=max(dp[j][k][0],dp[j-b[i]][k-1][0]+a[i]);
				ans=max(ans,dp[j][k][0]);
            }
		}
	}
	for(int i=n+1;i<=n+m;i++)
	{
		for(int j=d;j>=b[i];j--)//目前状态 const 
		{
			for(int k=1;k<=5;k++)//队伍中人数 
			{
				for(int l=1;l<=min(k,i-n);l++)//装备礼服的人数
				{
					dp[j][k][l]=max(dp[j][k][l],dp[j-b[i]][k][l-1]+a[i]);
					ans=max(ans,dp[j][k][l]);
				} 
			}
		}
	}
	printf("%d",ans);
	return 0;
}

1024 [NOIP2006]金明的预算方案

标签 1

有依赖的背包

前言 1

有依赖的背包,即某些物品的拿取必须依赖其他某些物品的已拿取状态。

思路 1

  1. 因为每个主件至多有 \(2\) 个附件,故可以枚举主件,然后暴力枚举拿取附件的情况进行转移。

代码 1

点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
using namespace std;
const int maxn=65,maxv=33000;
int dp[maxv],w[maxn],v[maxn],n,m,q[maxn];
int sumw[maxn],sumv[maxn];
vector<int> son[maxn];
int main ()
{
	scanf("%d%d",&m,&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d%d",&v[i],&w[i],&q[i]);
		if(q[i]!=0) son[q[i]].push_back(i);
		sumw[q[i]]+=w[i]*v[i],sumv[q[i]]+=v[i];
	}
	for(int i=1;i<=n;i++)
	{
		if(q[i]!=0) continue;
		for(int j=m;j>=v[i];j--)
		{
			dp[j]=max(dp[j],dp[j-v[i]]+v[i]*w[i]);
			if(son[i].size()>=1)
			{
				for(int k=0;k<son[i].size();k++)
				{
					int vi=son[i][k];
					if(j-v[i]-v[vi]>=0) 
						dp[j]=max(dp[j],dp[j-v[i]-v[vi]]+v[i]*w[i]+v[vi]*w[vi]);
				}
			}
			if(son[i].size()>=2)
			{
				if(j-v[i]-sumv[i]>=0)
					dp[j]=max(dp[j],dp[j-v[i]-sumv[i]]+v[i]*w[i]+sumw[i]);
			}
			//printf("%d,%d,%d\n",i,j,dp[j]);
		}
	}
	int ans=0;
	for(int j=0;j<=m;j++)
		ans=max(ans,dp[j]);
	printf("%d",ans);
	return 0;
}
posted @ 2023-07-17 12:01  shyiaw  阅读(47)  评论(0)    收藏  举报