CF1799 D. Hot Start Up (easy&hard version) [dp+不停的优化+线段树]

传送门:CF

[前题提要]:D1思维难度不高;D2感觉十分变态,感觉就是为了出题而出题,但是竟然只有*2100,看来还是我太菜了…


E a s y    v e r s i o n : Easy\;version: Easyversion:

不难想到应该使用 d p dp dp来解决这道题.仔细模拟一下,就会得到一个朴素的定义:考虑定义 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]为加入了前 i i i个任务,第1个CPU最后完成的任务是第 j j j个任务,第2个CPU最后完成的任务是第 k k k个任务的最小花费.发现第一维显然可以使用滚动数组滚掉,考虑重定义为 d p [ 0 / 1 ] [ j ] [ k ] dp[0/1][j][k] dp[0/1][j][k].但是此时的复杂度依旧是 n 2 k n^2k n2k.

仔细观察一下,会发现其实是存在这样一个性质的,我们会发现对于加入的第 i i i个任务,我们必然存在一个CPU最后一个任务是 i i i.所以此时我们就可以不用一起枚举两个CPU的状态,我们可以假定任意一个CPU放第 i i i个任务,这样我们就可以将复杂度降为 n 2 n^2 n2或者 n k nk nk了.

具体来说,我们有以下递推方程:
d p [ n o w ] [ i ] [ j ] = d p [ p r e ] [ i − 1 ] [ j ] + h o t    o r    c o l d dp[now][i][j]=dp[pre][i-1][j]+hot\;or\;cold dp[now][i][j]=dp[pre][i1][j]+hotorcold 将第 i i i个任务放在最后状态为 i − 1 i-1 i1的CPU1上
d p [ n o w ] [ i − 1 ] [ i ] = d p [ p r e ] [ i − 1 ] [ j ] + h o t    o r    c o l d , j ∈ [ 0 , i − 2 ] dp[now][i-1][i]=dp[pre][i-1][j]+hot\;or\;cold,j\in[0,i-2] dp[now][i1][i]=dp[pre][i1][j]+hotorcold,j[0,i2] 将第 i i i个任务放在最后状态为 j j j的CPU2上
d p [ n o w ] [ j ] [ i ] = d p [ p r e ] [ j ] [ i − 1 ] + h o t    o r    c o l d dp[now][j][i]=dp[pre][j][i-1]+hot\;or\;cold dp[now][j][i]=dp[pre][j][i1]+hotorcold 将第 i i i个任务放在最后状态为 i − 1 i-1 i1的CPU2上
d p [ n o w ] [ i ] [ i − 1 ] = d p [ p r e ] [ j ] [ i − 1 ] + h o t    o r    c o l d , j ∈ [ 0 , i − 2 ] dp[now][i][i-1]=dp[pre][j][i-1]+hot\;or\;cold,j\in[0,i-2] dp[now][i][i1]=dp[pre][j][i1]+hotorcold,j[0,i2] 将第 i i i个任务放在最后状态为 j j j的CPU1上
此时我们就可以解决 E a s y    v e r s i o n Easy\;version Easyversion了,具体代码放在文章末尾.


H a r d    v e r s i o n : Hard\;version: Hardversion:

发现范围变大了.对于这种dp题来说,范围变大了,必然是需要某种数据结构进行优化.

但是我们 E a s y Easy Easy版本的 d p dp dp方程太 n a i v e naive naive了,甚至没有优化的资格.

考虑优化一下我们的上面的dp方程,我们会发现其实四种状态和之前的状态都是没有交集的,所以我们其实可以将第一维直接优化掉.根本不需要进行滚动.但是此时我们会发现依旧很难向数据结构那边靠.
所以我们还需要进行优化.继续观察dp方程,我们会发现 1 , 3 1,3 1,3以及 2 , 4 2,4 2,4的状态似乎是对称的.进一步,我们会发现其实上述两种状态是可以进行合并的.考虑优化我们的dp方程的定义,重定义 d p [ i ] [ j ] dp[i][j] dp[i][j]为当前枚举到了第 i i i个任务,其中一个CPU的状态以 i i i为下标的任务,另外一个CPU最后的状态以 j j j为下标的任务最小贡献(因为其中一个CPU最后状态必然是 i i i).那么此时我们的递推方程就变成了下面这个:

d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + h o t    o r    c o l d dp[i][j]=dp[i-1][j]+hot\;or\;cold dp[i][j]=dp[i1][j]+hotorcold 将第 i i i个任务放在最后状态为 i − 1 i-1 i1的CPU上
d p [ i ] [ i − 1 ] = m i n { d p [ i − 1 ] [ j ] + h o t    o r    c o l d } dp[i][i-1]=min\{dp[i-1][j]+hot\;or\;cold\} dp[i][i1]=min{dp[i1][j]+hotorcold} 将第 i i i个任务放在最后状态为 j j j的CPU上

此时我们就可以看出一些端倪了.我们会发现上述dp仍然可以进行滚动.我们借这个滚动来看一下这个性质,我们会发现第一个dp方程就是在原来所有状态的基础上进行一个区间加(因为滚动掉之后左右下标不变),第二个dp方程就是在原本所有状态的基础上进行一个区间加然后再取一个min,将其赋给i-1状态.此时还需要注意的是,第一个dp方程和第二个dp方程之间是有交集的,所以我们得同时修改两个dp方程,不然会导致状态紊乱.此时有经验的人应该想到使用线段树进行维护了.但是仍然存在一个问题,按照上述的状态,我们很难判断 j j j任务和 i i i任务是否相同.此时也就很难使用线段树进行维护了,因为对于不同的 j j j我们既需要加 h o t hot hot又要加 c o l d cold cold.此时我们继续优化 d p dp dp方程,我们可以将 d p dp dp继续重定义为为当前枚举到了第 i i i个任务,其中一个CPU的状态为以 i i i为下标的任务,另外一个CPU最后的状态第 j j j种任务的最小贡献.此时我们就可以使用线段树来进行维护了.因为只有状态为 a [ i ] a[i] a[i]的那个节点才需要加 h o t hot hot,其他的都加 c o l d cold cold即可,我们大可以将区间分成三段来分别考虑.具体维护方式见代码.


下面是具体的代码部分( E a s y Easy Easy版本):

#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 a[maxn];
struct Node{
	int cold,hot;
}node[maxn];ll dp[2][5010][5010];
int main() {
	int T=read();
	while(T--) {
		int n=read();int k=read();
		for(int i=1;i<=n;i++) a[i]=read();
		for(int i=1;i<=k;i++) node[i].cold=read();
		for(int i=1;i<=k;i++) node[i].hot=read();
		for(int i=0;i<=n;i++) {
			for(int j=0;j<=n;j++) {
				dp[0][i][j]=dp[1][i][j]=ll_INF;
			}
		}
		int pre=0,now=1;
		dp[0][0][0]=0;
		for(int i=1;i<=n;i++) {
			//i-1放上面
			for(int j=0;j<=n;j++) {
				//i放上面
				if(a[i-1]==a[i]) dp[now][i][j]=min(dp[now][i][j],dp[pre][i-1][j]+node[a[i]].hot);
				else dp[now][i][j]=min(dp[now][i][j],dp[pre][i-1][j]+node[a[i]].cold);
				//i放下面
				if(a[j]==a[i]) dp[now][i-1][i]=min(dp[now][i-1][i],dp[pre][i-1][j]+node[a[i]].hot);
				else dp[now][i-1][i]=min(dp[now][i-1][i],dp[pre][i-1][j]+node[a[i]].cold);
			}
			//i-1放下面
			for(int j=0;j<=n;j++) {
				//i放上面
				if(a[j]==a[i]) dp[now][i][i-1]=min(dp[now][i][i-1],dp[pre][j][i-1]+node[a[i]].hot);
				else dp[now][i][i-1]=min(dp[now][i][i-1],dp[pre][j][i-1]+node[a[i]].cold);
				//i放下面
				if(a[i-1]==a[i]) dp[now][j][i]=min(dp[now][j][i],dp[pre][j][i-1]+node[a[i]].hot);
				else dp[now][j][i]=min(dp[now][j][i],dp[pre][j][i-1]+node[a[i]].cold);
			}
			if(i!=n) {
				for(int j=0;j<=n;j++) {
					dp[pre][j][i-1]=ll_INF;
					dp[pre][i-1][j]=ll_INF;
				}
			}
			swap(pre,now);
		}
		ll ans=ll_INF;
		for(int i=0;i<=n;i++) {
			for(int j=0;j<=n;j++) {
				ans=min(ans,dp[pre][i][j]);
			}
		}
		cout<<ans<<endl;
	}
	return 0;
}

下面是具体的代码部分(Hard版本):
PS:存在0所以将所有下标都右移一位

#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
#define int long long
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int a[maxn];
struct Node{
	int cold,hot;
}node[maxn];
struct Segment_tree{
	int l,r,lazy,mn;
}tree[maxn*4];
void pushup(int rt) {
	tree[rt].mn=min(tree[ls].mn,tree[rs].mn);
}
void build(int l,int r,int rt) {
	tree[rt].l=l;tree[rt].r=r;tree[rt].lazy=0;tree[rt].mn=ll_INF;
	if(l==r) {
		if(l==1) tree[rt].mn=0;
		return ;
	}
	int mid=(l+r)>>1;
	build(lson);build(rson);
	pushup(rt);
}
void change(int rt,int val) {
	tree[rt].mn+=val;
}
void pushdown(int rt) {
	change(ls,tree[rt].lazy);change(rs,tree[rt].lazy);
	tree[rt].lazy=0;
}
void update(int l,int r,int rt,int val) {
	if(tree[rt].l==l&&tree[rt].r==r) {
		change(rt,val);
		return ;
	}
	if(tree[rt].lazy) pushdown(rt);
	int mid=(tree[rt].l+tree[rt].r)>>1;
	if(r<=mid) update(l,r,ls,val);
	else if(l>mid) update(l,r,rs,val);
	else update(l,mid,ls,val),update(mid+1,r,rs,val);
	pushup(rt);
}
int query(int l,int r,int rt) {
	if(tree[rt].l==l&&tree[rt].r==r) {
		return tree[rt].mn;
	}
	if(tree[rt].lazy) pushdown(rt);
	int mid=(tree[rt].l+tree[rt].r)>>1;
	if(r<=mid) return query(l,r,ls);
	else if(l>mid) return query(l,r,rs);
	else return min(query(l,mid,ls),query(mid+1,r,rs));
}
signed main() {
	int T=read();
	while(T--) {
		int n=read();int k=read();
		for(int i=1;i<=n;i++) {
			a[i]=read();
		}
		for(int i=1;i<=k;i++) {
			node[i].cold=read();
		}
		for(int i=1;i<=k;i++) {
			node[i].hot=read();
		}
		build(1,k+1,1);
		for(int i=1;i<=n;i++) {
			update(1,a[i],1,node[a[i]].cold);
			if(a[i]+2<=k+1)
				update(a[i]+2,k+1,1,node[a[i]].cold);
			update(a[i]+1,a[i]+1,1,node[a[i]].hot);
			int num=query(1,k+1,1);
			//恢复原状,避免状态紊乱
			update(1,a[i],1,-node[a[i]].cold);
			if(a[i]+2<=k+1)
				update(a[i]+2,k+1,1,-node[a[i]].cold);
			update(a[i]+1,a[i]+1,1,-node[a[i]].hot);
			if(a[i-1]==a[i]) {
				update(1,k+1,1,node[a[i]].hot);
			}
			else {
				update(1,k+1,1,node[a[i]].cold);
			}
			int num2=query(a[i-1]+1,a[i-1]+1,1);
			//取一个最大值
			if(num<num2) {
				update(a[i-1]+1,a[i-1]+1,1,-(num2-num));
			}
		}
		cout<<query(1,k+1,1)<<endl;
	}
	return 0;
}

  • 18
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值