Logo lxn 的博客

博客

20240124-动态规划期末考试

...
lxn
2025-12-01 12:57:46

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-01-21 17:01:15

T417071 写作业

  • 题目分析:

    • 50% 的数据:dp或者dfs记忆化均可

    $f[n]=min(f[n-1],f[n/2]+1$ ,$n$为偶数.

    $f[n]=f[n-1]+1$ ,$n$为奇数.

    • 100% 的数据,考察点:数学方法,二进制运算。 参考代码:
#include <bits\/stdc++.h>
using namespace std;
typedef long long ll;
ll n,cnt;
int main(){
	cin>>n;
	while(n>1){
		if(n&1)n--,cnt++;
		else n\/=2,cnt++;
	}
	cout<<cnt<<endl;
}

T417086 促销

  • 题目分析 贪心+01背包变形。购买商品的方式可以通过优惠,也可以单独购买。题目要求最少的购买件数,那么我们买便宜的件数会更多。因此先排序。 设购买前i件的最少花费为$f[i]$. $$ f[i]=min(f[i-1]+a[i],f[i-k]+2 \times a[i])

$$

参考代码

\/*
物品可以有用优惠购买,也可以不用优惠。 
*\/ 
#include <bits\/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+9;
ll a[N], f[N],n,p,k,ans=0;
int main() {
    cin >> n>>p>>k;
   	for(int i=1;i<=n;i++)cin>>a[i],f[i]=2e9;
   	sort(a+1,a+1+n);
   	for(int i=1;i<=n;i++){
   		if(f[i-1]>p) break;
   		f[i]=min(f[i],f[i-1]+a[i]);
   		if(i>=k)f[i]=min(f[i],f[i-k]+2*a[i]);
   		if(f[i]<=p)ans=i;
	}
	cout<<ans<<endl;
}

T418732 种花还是种草

  • 题目大意 给出有 $N $个顶点的带边权完全图,选任意条顶点不重合的线段,求最大边权和。 $N<=16$,暴搜或者状压DP都可以。

    状态DP,设$f(i)$为选了二进制i中包含点的最大距离,从$i$状态中枚举其中的两个点$j,k$个点为新连接的两个点,则,则转移方程为 $$ f(i)=max(f(i-2^j-2^k)+a_{i,j})
    $$

时间复杂度$o(n^22^n)$

参考代码

#include <bits\/stdc++.h>
using namespace std;
int a[1010][1010];
long long f[1010101];
int main() {
	int n;
	cin >> n;
	for (int i = 0; i < n-1; ++i) for (int j = i + 1; j < n; ++j) cin >> a[i][j];
	for (int i = 0; i <= (1 << n); ++i) {
		for (int j = 0; j < n; ++j) {
			if (i & (1 << j)) {
				for (int k = j + 1; k < n; ++k) {
					if (i & (1 << k)) f[i] = max(f[i ^ (1 << j) ^ (1 << k)] + a[j][k], f[i]);
				}
			}
		}
	}
	long long an = 0;
	for (int i = 0; i <= (1 << n); ++i) {
		an = max(an, f[i]);
	}
	cout << an << '\n';
	return 0;
}

T275408 收菜

每个袋子只能装1种物品,用刚好能装的下的最下背包来装这个物品。

60%的数据

$n^2$处理把物品放入哪个袋子。

100%的数据

要找更快的匹配方法。

  • 方法一 处理价值大的物品,给物品找袋子

贪心先从价值大的开始装。找到能装上且浪费空间最少的袋子,用上他。可以用一个袋子的多重集,二分查找,用掉一个删掉一个。

  • 方法二 找出袋子能装入的最大价值物品,给袋子找物品。 看袋子能装入那些,从可以装入的物品中选出价值最大的。

把袋子和物品都按照体积排序。

把袋子大小能容纳的物品放入价值优先的优先队列。从能装入的物品中选择价值最大的。

T417069 小容玩Arcaea

  • 题目分析 考虑 DP。

$f(i,j)$为掷了$i$次硬币,计数器示数为 连续$j$次为正面的最大收益。 如果 $j=0$,则说明上一次掷到的是反面,所以上一次计数器的示数可以是$0$到 $i$之间的任意整数。因为上一次为反面,所以没有奖励。 所以此时 $f(i,j)=max (f(i-1,k)),0\leq k<j$的最大值。

如果 $j≥1$,则说明上一次掷到的是正面,所以上一次计数器的示数一定是$j-1$。因为上一次是正面,所以会有奖励,所以此时

$f(i,j)=max(f(i-1,j-1)+a_i+b_i)$

答案为$max( f(n,k))$ $(0≤k≤n) $的总和。

参考代码

\/\/ABC261D
#include<iostream>
#define int long long
using namespace std;

const int N = 5010;

int n, m, ans;
int a[N], b[N];
int f[N][N];

signed main()
{
	cin >> n >> m;
	for(int i=1; i<=n; i++)
	{
		cin >> a[i];
	}
	for(int i=1; i<=m; i++)
	{
		int x, y;
		cin >> x >> y;
		b[x] = y;
	}
	for(int i=1; i<=n; i++)
	{
		for(int j=0; j<=i-1; j++)
		{
			f[i][0] = max(f[i][0], f[i-1][j]);
		}
		for(int j=1; j<=i; j++)
		{
			f[i][j] = max(f[i][j], f[i-1][j-1] + a[i] + b[j]);
		}
	}
	for(int i=1; i<=n; i++)
	{
		ans = max(ans, f[n][i]);
	}
	
	cout << ans;
	return 0;
}

T417054 菜园

  • 题目分析

一个有 $n$个格子的长纸条,第 $ i$个格子颜色为 $a_i$,价值为$ b_i$,现在要任选 $ k$种颜色,使得所有颜色是较大编号的格子都在较小编号的后面,求最大价值。

按照题目的意思,想在要求同样的格子颜色是连续的,那么需要选的两种颜色要分布的左右区间不能相交。这样就转换为一段一段区间的选。考虑设计以颜色为主体的状态,在加上所选的颜色种类数,也就是序列的长度,设计两维状态。设 $f(i,k)$为选了颜色 $i$ 为结尾,选了 $k$ 种颜色的最小价值 。

那么需要统计每种颜色左侧和右侧 出现的位置。 记 $l_i$表示颜色$ i$ 第一次出现的位置, $ r_i$表示颜色 $i $最后一次出现的位置。那么只有满足$ r_j<l_i$时才能选$i,j$两种颜色。

$f(i,k)=min(f(j,k-1)+b[i])$,$j<i,r_j<l_i$. 第$i$种颜色对应的格子都在第j种格子的右边,$j<i$保证了颜色编号的上升。

  • 时间复杂度$o(n^2k)$,加了区间限制条件后跑不满。

参考代码:


#include<bits\/stdc++.h>
using namespace std;
typedef long long ll;
const int N=509; 
ll n,m,ans=-1,a[N],b[N];
ll l[N],r[N],f[N][N];
int main(){
	memset(f,-1,sizeof(f));
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	for(int i=1;i<=n;i++) scanf("%lld",&b[i]);
	for(int i=1;i<=n;i++){
		if(!l[a[i]]) l[a[i]]=i;
		r[a[i]]=max(r[a[i]],(ll)i);
	}
	f[0][0]=0;
	for(int i=1;i<=n;i++){
		for(int j=0;j<i;j++){
		    if(l[i]<=r[j])continue;
			for(int k=1;k<=m;k++){
				if(f[j][k-1]!=-1) f[i][k]=max(f[i][k],f[j][k-1]+b[i]);
			}
		}
	}
	for(int i=1;i<=n;i++) ans=max(ans,f[i][m]);
	printf("%lld\n",ans);
	return 0;
}

T275421 逛菜园

  • 题目大意

给你一个矩阵,从最后一行开始走,到了每一行第一个到的点时只能向左或者向上走,否则就可以向左向右向上走,最终走到第一行结束,求经过所有格子的最小值。

坐标类型的动态规划。考虑设 $f(i,j)$表示从到 $i$ 行,第 $j $可以获得的最小代价。

那么如何转移呢?

显然,进入第 $ i$ 行时的列数一定是 $≥j$ 的,这样才能到达第 $j $列。

考虑从刚进到第 $ i$ 行的点 $ k$ 跑到 $ j $的路径上的点一定是要算代价的,而同时可以再往左跑一段后跑回来。

这样此题复杂度就成 $O(n^3 )$ 的了,会超时。

如何优化呢?可以发现从右下走到左上的过程中,可以向左多跑一段,跑到哪里为止呢?越小越好,大于$0$就停止了,转化为预处理一个以 $j$ 为终点的最小子段和$sum(i,j)$。

两种情况:

  • 从$i+1$过来,到$j$列的左侧逛一圈。
  • 从$j+1$过来,如果$j+1$已经往左逛一圈了,到$j$这里就没必要再逛了,实际就是$j+1$的子段和是否连接了j,条件就是$sum[i][j]>0$。

$$ f(i,j)= min(f(i+1,j)+sum(i,j),f(i,j+1)+max(sum(i,j),0)) $$

于是此题复杂度 $ O(n^2 )$。

写作业: B3636 文字工作(数据增加了,和原题不一样)

促销:忘记来源了,自己造的数据

收菜:P6538 [COCI2013-2014#1] LOPOV

小容玩Arcaea:ABC261D

种花还是种草:AT_abc318_d [ABC318D] Gener

单调不下降的菜园: P9688 Colo.

逛菜园:P6649 「SWTR-5」Grid

描述: 虽然叫动态规划的期末考试,也不一定都是动态规划,还是根据题目时间情况分析。

难度:普及-到普及+

赛时答疑与赛后题解: 题解 奖励:

评论

暂无评论

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。