2023ACM暑期集训 DAY 3
目前进度——动态规划1:线性dp、背包问题,区间
好题
1012 [NOIP1999]拦截导弹
前言
本题不重要,重要的是关于伪单调栈求最长单调子序列的理解。
伪单调栈求最长单调子序列的一些理解重点
- 最终伪单调栈中的序列不一定是最长单调子序列。
- 以最长上升子序列为例,解释操作的意图。若目前元素 \(a_i\) 大于栈首元素,则可直接接在栈首,成为新的栈首;否则,需要在栈中二分找第一个大于等于 \(a_i\) 的数,使之被替换成 \(a_i\)。接下来介绍否则后操作的意图。易发现,\(a_i\) 除通过大于栈首而成为栈首之外,还可通过让栈首第一个大于等于其而替换掉栈首,因为根据贪心,这么做可以在后面接更多的数。于是产生新的疑惑:为何不能只判断栈首是否是第一个大于等于 \(a_i\) 的数,如果是替换掉即可,为什么还要替换其他位置?因为在遍历到 \(a_i\) 之前,可能有多个栈元素因无法成为栈首而作为了栈中的其他元素,这些元素虽然无法成为栈首但仍有可能比 \(a_i\) 更有资格成为栈首,所以 \(a_i\) 要成为栈首就要先满足比他们都有资格然后才可以去挑战栈首。故替换其他位置的意图,就是要存储这些虽然不是栈首但有一定资格成为栈首的元素。
- 我们易发现在最长上升子序列中,寻找的是第一个小于等于 \(a_i\) 的栈元素;在最长不下降子序列中,寻找的是第一个小于 \(a_i\) 的元素。为何会有此差别?思考易得,若为小于等于则栈中无值大小相等的元素,满足上升;小于则栈中可有值相等的元素,满足不下降。
1014 小A买彩票
标签
概率DP
思路
- 等权重的递推概率 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背包
思路
- 如标签所言,为有限制的背包问题,具体表现在任意两个拿去物品的位置大于等于 \(k\)。故若设状态 \(dp_{i,j}\) 为拿取前 \(i\) 个物品中的 \(j\) 个所能达到的最大欢迎度,则需特殊注意 \(j=0\) 与 \(j=1\) 的情形,因为在这种情形下不受到位置关系的制约,进而无需以下标差大于等于 \(k\) 作为转移条件。
- 时间复杂度为 \(\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]装箱问题
思路
- 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
思路
- 通过取模的方式优化空间。
- 注意:本题因为整点时间会因为不播放任意一首歌便存在,故不能简单的只用 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 背包问题
思路
- 考虑普通 01 背包可不论物品存储次序的原因。易得,因为对于两个物品 \(i\) 与 \(j\),无论先将 \(i\) 放进背包,还是先将 \(j\) 放进背包,最终得到的价值均为 \(v_i+v_j\)——即最终价值与物品拿取次序无关。但是,若如本题物品价值随时间以不同的速率减少,进而导致最终价值与物品拿取次序有关,则需先规定物品拿取次序,则进行 01 背包。
- 考虑如何规定物品次序。解不等式 \(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 队伍配置
思路
- 滚动数组优化空间。
代码
点击查看代码
#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
- 因为每个主件至多有 \(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;
}