传送门: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;
}