2023杭电多校第二场1010 Klee likes making friends

传送门:Vjudge

前题提要:看完 s t d std std之后感觉官方给的 s t d std std简直是依托答辩, d p dp dp方程和 m i n n minn minn的具体定义不同,但是变量一样,并且没有注释,容易导致歧义(具体来说minn本意是前缀最小值,题解却说是后缀).使用滚动数组并且故作玄虚的倒着枚举(实际上完全不需要),倒着枚举在我看来纯属zz,徒徒增加了思维负担罢了.仔细揣摩它的代码甚至还能发现一处错误(可能不是错误,但是很像是错误),真不知道怎么AC的. 故准备撰写一篇更加亲民的题解.

这道题的dp想法其实是需要两次滚动的思想的.(虽然卡滚动我也觉得很zz,但是平时练习的话我还是忍了)
首先看完题面,我们肯定会发现最后的花费和最后两个朋友的位置有关(显然对于最后m个位置,最后两个朋友的位置不同会导致我们的答案不同).所以我们的dp不难应该想到和最后两个朋友的位置有关.
我们不难想到应该定义 d p [ i ] [ p o s 1 ] [ p o s 2 ] dp[i][pos1][pos2] dp[i][pos1][pos2]为前 i i i个位置最后两个朋友分别选在 p o s 1 , p o s 2 pos1,pos2 pos1,pos2位置的最小花费.首先这个dp方程思想肯定是没有问题的.但是我们会发现这个朴素的dp方程需要 2 e 5 ∗ 2 e 5 ∗ 2 e 5 2e5*2e5*2e5 2e52e52e5的位置(注意这个是没办法用map离散化的,因为我们的位置是可以在任意的m个位置的,所以离散化并不能优化),所以我们得考虑优化dp方程.
在优化之前先考虑这个dp方程该如何进行递推.
因为连续m个位置需要2个朋友,那么我们现在一个在 p o s 1 pos1 pos1,一个在 p o s 2 pos2 pos2(这里不妨设pos1<pos2),那么对于我们在 p o s 1 pos1 pos1前面的那一个朋友来说,就必须在 [ p o s 2 − m , p o s 1 − 1 ] [pos2-m,pos1-1] [pos2m,pos11]的区间里找一个了(因为对于区间 [ p o s 2 − m , p o s 2 − 1 ] [pos2-m,pos2-1] [pos2m,pos21]来说我们这个需要两个朋友,一个在pos1位置,那么 x x x就需要在上述区间).然后我们就可以进行递推了.
d p [ i ] [ p o s 1 ] [ p o s 2 ] = m a x ( d p [ j ∈ [ p o s 1 , i − 1 ]   ] [ x ] [ p o s 1 ] ) , x ∈ [ p o s 2 − m , p o s 1 − 1 ] dp[i][pos1][pos2]=max(dp[j\in[pos1,i-1]\ ][x][pos1]),x\in[pos2-m,pos1-1] dp[i][pos1][pos2]=max(dp[j[pos1,i1] ][x][pos1]),x[pos2m,pos11]此时我们就发现了,我们的第一维可以跟着我们的后两维随便取的,更加具体的来说,我们的第一维是没有任何用处的,所以我们可以直接优化掉.此时我们有:
d p [ p o s 1 ] [ p o s 2 ] = m a x ( d p [ x ] [ p o s 1 ] ) , x ∈ [ p o s 2 − m , p o s 1 − 1 ] dp[pos1][pos2]=max(dp[x][pos1]),x\in[pos2-m,pos1-1] dp[pos1][pos2]=max(dp[x][pos1]),x[pos2m,pos11]但是此时我们发现虽然此时我们的dp方程进行了一维的滚动,但是依旧需要 2 e 5 ∗ 2 e 5 2e5*2e5 2e52e5,依旧是无法接受,所以需要继续进行优化才行.
此时我们会发现对于一个位置来说,我们此时的位置最多只能由在它之前的m个位置递推而来,也就是说在这个位置m之前的位置对于后面的所有位置来说都是没有用的,所以这里又可以进行滚动.考虑直接进行%m来进行滚动.(但是需要注意的是,我们此时并不能对pos1和pos2同时进行%m滚动,因为我们同时进行取模滚动的话会导致相对大小随机发生变化,导致影响线性性质,很难维护).所以此时我们对pos2进行滚动,然后对于pos1来说,我们没必要维护pos1的具体位置,可以使用pos1和pos2的相对位置来代替这个,这样我们就同时对于两个都进行优化了.此时我们的 d p dp dp方程就变成了:
d p [ p o s % m ] [ d i s ] = m a x ( d p [ ( p o s − d i s ) % m ] [ d i s ∈ [ 1 , p o s − d i s − ( p o s − m ) ] ] ) dp[pos\%m][dis]=max(dp[(pos-dis)\%m][dis\in[1,pos-dis-(pos-m)]]) dp[pos%m][dis]=max(dp[(posdis)%m][dis[1,posdis(posm)]])因为此时的我们的 p o s 1 pos1 pos1变成了 p o s 2 − d i s pos2-dis pos2dis,那么此时的选取区间就变成了 [ p o s 2 − m , p o s 2 − d i s − 1 ] [pos2-m,pos2-dis-1] [pos2m,pos2dis1].
现在我们的问题就是如何快速求出最大的那一个 d p dp dp值了,对于区间最小值,第一想法显然是线段树,但是此时需要二维线段树,一是内存不够,而来码量也较大,所以考虑使用前缀最大值来进行维护.
具体来说就是在跑出 d p [ p o s ] [ d i s ] dp[pos][dis] dp[pos][dis]的时候,同时用一个数组 m i n n [ p o s ] [ d i s ] minn[pos][dis] minn[pos][dis]来不断的从前往后进行取 m i n min min,这样就能求出了前缀最小值了,又因为我们每次需要是 d p [ p o s ] [ 1 dp[pos][1 dp[pos][1~ x ] x] x]的最小值,很符合我们的前缀最小值,此时我们直接进行调用即可.

至此本题结束


下面是具体的代码部分(此题卡map,pos数组不能用map映射):

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
inline void print(__int128 x){
	if(x<0) {putchar('-');x=-x;}
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
#define maxn 1000000
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int dp[2010][2010];int minn[2010][2010];int a[maxn];int pos[maxn];
//dp[i][j]的定义最后一个朋友的位置为i,倒二个朋友所处位置距离最后一个朋友i的距离为j的最小花费
//minn[i][j]的定义对于位置i来说,所有dp[i][j]的前缀最小值(j>=1&&j<=m-1)
//简单来说也就是以i为最后一个朋友,倒二个朋友所处位置距离i为j的dp[i][j]的前缀最小值(j从小到大)
int main() {
	int T=read();
	while (T--) {
		int n=read();int m=read();
		for(int i=0;i<=m;i++) {
			for(int j=0;j<=m;j++) {
				dp[i][j]=minn[i][j]=int_INF;
			}
		}
		for (int i=1;i<=n;i++) {
			a[i]=read();
			pos[i]=i%m;
		}
		int ans=int_INF;
		for (int i=2;i<=m;i++) {
			for(int j=1;j<=i-1;j++) {//距离
				dp[pos[i]][j]=a[i]+a[i-j];
				minn[pos[i]][j]=min(minn[pos[i]][j-1],dp[pos[i]][j]);
			}
		}
		for(int i=m+1;i<=n;i++) {
			for(int j=1;j<=m-1;j++) {
				//注意此时的滚动数组和01背包那种是不同的,此时的滚动区间刚好没有交叉
				//所以完全没必要倒着枚举,不知道std为什么故作玄虚
				int kk=i-j;
				dp[pos[i]][j]=a[i]+minn[pos[kk]][kk-(i-m)];
				//此时倒一位置为i,倒二位置为kk,所以倒三位置只能是[i-m,kk-1],所以距离为kk-(i-m)
				minn[pos[i]][j]=min(minn[pos[i]][j-1],dp[pos[i]][j]);
			}
		}		
		for(int i=n-m+2;i<=n;i++) {
			//最后的答案区间一定是[n-m+2,n],因为我们必须要有两个朋友才行
			//所以最后的朋友的位置一定要为倒二留一个位置,不知道为什么std是n-m+1,竟然还过了,真是奇妙
			int st=n-m+1;
			for(int j=1;j<=i-st;j++) {
				ans=min(ans,dp[pos[i]][j]);
			}
		}
		cout<<ans<<"\n";
	}
}
  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 16
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值