石门集训 8.16 题解
T4
是个动规题,没反应过来啊,虽然大概率也不会。写了个 dfs。
题意
有 的矩形,价值为 。
现在你要从 ,通过移动到 的方式,走到 。
再通过移动到 的方式,回去 。定义价值为路径上 的和,重复走过的路径只计算一次。
问最大的价值是多少?
input
3
1 2 3
4 5 6
7 8 9
output
42
错解
首先,猜个结论:先从 跑 dp。然后按路径将 设为 。在从 跑 dp。
但是这样做是错误的。
3
5 5 1
0 5 -2
5 -4 -2
dp 会跑出来 的结果。但是正解却是 。
dp:
正解:
动态规划
此时我们就举出了反例。还是老实用朴素的 dp 吧。本题可以理解为有两个人从 走到 。那就可以将状态设为 。分别是第一个人的坐标,第二个人的坐标。
我们使两个人一起走,如果两人坐标相同,就只算一遍。这个时候的状态转移方程即让两个人各走一步:
但是时空复杂度:。
优化
考虑优化,dp 优化的本质就是削减无用状态与重复转移。我们注意到,只有 的状态是有意义的,因为如果不同,说明不符合两个人一起走的定义。
假如两个人同时走,那么他们的坐标和就会相同。设坐标和为 。那么枚举 。 就可以算出来了。此时可设 为坐标和为 ,第一个人在 ,第二个人在 。
接下来思考状态转移方程,其实就是由朴素版推出来的:
复杂度就变成 。
初始值
在开始的时候,我们要将所有状态设为不可达。但是 的状态是确定的。。
#include<bits/stdc++.h>
using namespace std;
const int N = 3e2+1;
int a[N][N],dp[N<<1][N][N],n;
int main() {
memset(dp,-0x3f,sizeof dp);
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
cin>>a[i][j];
}
}
dp[2][1][1]=a[1][1];
for(int i=3;i<=n<<1;i++) {
for(int j=1;j<=n;j++) {if(i-j<1||i-j>n) continue;//枚举 x;y为 i-x+1,不能越界
for(int k=1;k<=n;k++) {if(i-k<1||i-k>n) continue;//同上
int t=0;
if(j==k) t=a[j][i-j];//相同只算一个
else t=a[j][i-j]+a[k][i-k];
dp[i][j][k]=max(max(dp[i-1][j-1][k],dp[i-1][j][k-1]),max(dp[i-1][j][k],dp[i-1][j-1][k-1]))+t;
}
}
}
cout<<dp[n<<1][n][n];
return 0;
}
T5-Bottles
题意
有 瓶水,第 瓶水的水量为 ,容量为 。将 单位水从一个瓶子转移到另一个瓶子所消耗时间为 秒,且可以进行无限次转移。求储存所有水所需最小瓶子数 以及该情况下所用最小时间 。
一步一步的做题,找思路。
第一问
对于第一问,非常容易。设 。只需要排序,然后找到最大的满足 的 ,就是第一问的答案,记为 。
第二问
对于最优解问题,考虑 dp。
定义状态
有以下条件需要被满足:
-
对 个水瓶进行考虑选择与否。
-
选择的水瓶数量不能超过 。
-
要有足够的容量装水。
其实也可以看做背包问题,价格为 ,价值为 。
我们就可以以此定义 为考虑了第 个水瓶,选择了 个,容量共有 的最大 之和。
为什么是最大值?因为 一定。如果不用转移的水更多,要转移的水更少。
状态转移方程
根据背包问题的解法,对第 个瓶子分类讨论。有选与不选,即:
#include<bits/stdc++.h>
#define b(__) b[mp[__]]
using namespace std;
const int N=1e2+1;
int n,a[N],ans=1e9,b[N],mp[N],sum,limits=1,S;
int dp[N][N*N];//第 i 个盘子,选 j 个 容量为 k
int main() {
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i],sum+=a[i],mp[i]=i;
for(int i=1;i<=n;i++) cin>>b[i],S+=b[i];
sort(mp+1,mp+n+1,[](int x,int y) {return b[x]>b[y];});
for(int i=1,t=0;i<=n;i++) {
t+=b(i);
if(t>=sum) {
limits=i;
break;
}
}
memset(dp,-0x3f,sizeof dp);
dp[0][0]=0;//赋初始值
for(int i=1;i<=n;i++)
for(int j=min(limits,i);j>=1;j--)
for(int k=S;k>=b[i];k--)//此处 a,b 排序与否均可
dp[j][k]=max(dp[j][k],dp[j-1][k-b[i]]+a[i]);
for(int i=sum;i<=S;i++) ans=min(sum-dp[limits][i],ans);
//此时要有合法的状态!若 i<sum,说明装不下
cout<<limits<<" "<<ans<<"\n";
return 0;
}
不写了,看 hht 的题解。
总结,简单题挂小分,难题根本不会,dp 仍需努力。dfs 水平有所提升

浙公网安备 33010602011771号