传送门: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][i−1][j]+hotorcold 将第 i i i个任务放在最后状态为 i − 1 i-1 i−1的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][i−1][i]=dp[pre][i−1][j]+hotorcold,j∈[0,i−2] 将第 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][i−1]+hotorcold 将第 i i i个任务放在最后状态为 i − 1 i-1 i−1的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][i−1]=dp[pre][j][i−1]+hotorcold,j∈[0,i−2] 将第 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[i−1][j]+hotorcold 将第 i i i个任务放在最后状态为 i − 1 i-1 i−1的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][i−1]=min{dp[i−1][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;
}