题意描述
小 T 同学非常热衷于跑步。为了让跑步更加有趣,他决定制作一款叫做《天天爱打卡》的软件,使得用户每天都可以进行跑步打卡。
开发完成后,小 T 同学计划进行试运行,他找了大 Y 同学来帮忙。试运行共 n n n 天,编号为从 1 1 1 到 n n n。
对大 Y 同学来说,如果某天他选择跑步打卡,那么他的能量值会减少 d d d。初始时,他的能量值是 0 0 0,并且试运行期间他的能量值可以是负数。
而且大 Y 不会连续跑步打卡超过 k k k 天;即不能存在 1 ≤ x ≤ n − k 1\le x\le n-k 1≤x≤n−k,使得他在第 x x x 到第 x + k x+k x+k 天均进行了跑步打卡。
小 T 同学在软件中设计了 m m m 个挑战,第 i i i( 1 ≤ i ≤ m 1\le i \le m 1≤i≤m)个挑战可以用三个正整数 ( x i , y i , v i ) (x_i,y_i,v_i) (xi,yi,vi) 描述,表示如果在第 x i x_i xi 天时,用户已经连续跑步打卡至少 y i y_i yi 天(即第 x i − y i + 1 x_i-y_i+1 xi−yi+1 到第 x i x_i xi 天均完成了跑步打卡),那么小 T 同学就会请用户吃饭,从而使用户的能量值提高 v i v_i vi。
现在大 Y 想知道,在软件试运行的 n n n 天结束后,他的能量值最高可以达到多少?
题解
考场上没时间写离散化, 100 → 56 100\to56 100→56,警惕看错时间仙人。
8pts做法
爆搜进行哪个挑战,判断下是否满足要求即可。
时间复杂度
O
(
2
n
)
O
(
n
2
n
)
O(2^n)~O(n2^n)
O(2n) O(n2n),预计得分8pts。
36pts做法
尝试设计一个
D
P
DP
DP。
首先先将题意中的挑战定义下形式。
对第
i
i
i 个挑战,
(
l
i
,
r
i
,
v
i
)
(l_i,r_i,v_i)
(li,ri,vi) 表示从第
l
i
=
x
i
−
y
i
+
1
l_i=x_i-y_i+1
li=xi−yi+1 天开始,到第
r
i
=
x
i
r_i=x_i
ri=xi 天为止连续跑步,能够得到的能量值为
v
i
v_i
vi。
设
f
i
,
0
/
1
f_{i,0/1}
fi,0/1 分别表示第
i
i
i 天强制不选/不强制不选的最大能量。
在附带一个辅助数组
a
r
r
i
,
j
=
∑
r
k
<
j
,
l
k
=
i
v
k
arr_{i,j}=\sum_{r_k<j,l_k=i}v_k
arri,j=∑rk<j,lk=ivk(具体实现看代码)。
不难发现有转移式
f
i
,
0
=
max
f
i
−
1
,
0
,
f
i
−
1
,
1
f_{i,0} = \max{f_{i-1,0},f_{i-1,1}}
fi,0=maxfi−1,0,fi−1,1
f
i
,
1
=
max
i
−
j
<
k
f
j
−
1
,
0
+
∑
k
∈
[
j
,
i
]
a
r
r
k
−
(
i
−
j
+
1
)
f_{i,1} = \max_{i-j<k}{f_{j -1,0}+\sum_{k\in[j,i]}{arr_k} - (i-j+1)}
fi,1=i−j<kmaxfj−1,0+k∈[j,i]∑arrk−(i−j+1)。
当然,根据定义
f
i
,
1
←
max
f
i
,
1
,
f
i
,
0
f_{i,1}\gets \max{f_{i,1},f_{i,0}}
fi,1←maxfi,1,fi,0
然后在实现辅助数组的时候,只需要开一维的
a
r
r
j
arr_j
arrj,然后每更新一次右端点
i
i
i,就将满足
r
k
=
i
r_k=i
rk=i 的挑战
k
k
k 提出来,让
a
r
r
l
k
←
a
r
r
l
k
+
v
k
arr_{l_k}\gets arr_{l_k}+v_k
arrlk←arrlk+vk。
实现并不难。
36pts赛时代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x = 0, f =1;char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
return x * f;
}
const int maxn = 3e5 + 10;
int n, m, k, d;
int f[maxn][2], arr[maxn];
vector<pair<int,int> > vec[maxn];
signed main(){
//freopen("run.in","r",stdin);
//freopen("run.out","w",stdout);
int testcase = read(), T = read();
while(T--){
n = read(), m = read(), k = read(), d = read();
int x, y, v;
for(int i = 1;i <= n;i++)vec[i].clear();
memset(f,0,sizeof(f));memset(arr,0,sizeof(arr));
for(int i = 1;i <= m;i++){
x = read(), y = read(), v = read();
vec[x].push_back(make_pair(x - y + 1,v));
}
int ans = 0;
for(int i = 1;i <= n;i++){
int sum = 0;
for(auto j : vec[i]){arr[j.first] += j.second;}
for(int j = i;i - j < k && j > 0;j--){
sum += arr[j];
f[i][1] = max(f[i][1],f[j - 1][0] + (j - 1) * d + sum - i * d);
}
f[i][0] = max(f[i - 1][0],f[i - 1][1]);
ans = max(ans,max(f[i][0],f[i][1]));
}
printf("%lld\n",ans);
}
return 0;
}
56pts做法
先将刚刚的柿子改一下:
f
i
,
1
=
max
i
−
j
<
k
f
j
−
1
,
0
+
∑
k
∈
[
j
,
i
]
a
r
r
k
−
(
i
−
j
+
1
)
f_{i,1} = \max_{i-j<k}{f_{j -1,0}+\sum_{k\in[j,i]}{arr_k} - (i-j+1)}
fi,1=i−j<kmaxfj−1,0+k∈[j,i]∑arrk−(i−j+1)
⇔
f
i
,
1
=
−
i
+
max
i
−
j
<
k
(
f
j
−
1
,
0
+
∑
k
∈
[
j
,
i
]
a
r
r
k
+
j
−
1
)
\Leftrightarrow f_{i,1} =-i+ \max_{i-j<k}{(f_{j -1,0}+\sum_{k\in[j,i]}{arr_k} +j-1)}
⇔fi,1=−i+i−j<kmax(fj−1,0+k∈[j,i]∑arrk+j−1)
发现将
i
i
i 提出来之后,后面的东西可以类似扫描线维护。
具体而言,使用线段树维护每一个位置上的
f
j
−
1
,
0
+
∑
k
∈
[
j
,
i
]
a
r
r
k
+
j
−
1
f_{j -1,0}+\sum_{k\in[j,i]}{arr_k} +j-1
fj−1,0+∑k∈[j,i]arrk+j−1。
每次
i
←
i
+
1
i\gets i + 1
i←i+1 之后,将所有满足
r
k
=
i
r_k=i
rk=i 的挑战
k
k
k 提出来,让线段树上区间
[
1
,
l
k
]
[1,l_k]
[1,lk] 中的每一个位置加上
v
k
v_k
vk。
解释:因为只要包含了区间
[
l
k
,
r
k
]
[l_k,r_k]
[lk,rk]就能拿到
v
k
v_k
vk 的贡献,而现在和将来的右端点
r
≥
r
k
r\ge r_k
r≥rk,所以只要
l
≤
l
k
l\le l_k
l≤lk 的位置都会包含这个区间,所以区间加。
然后
f
i
,
1
←
max
[
i
−
k
+
1
,
i
]
f_{i,1}\gets\max[i-k+1,i]
fi,1←max[i−k+1,i]。
这两个操作都是线段树基本操作。
时间复杂度和空间复杂度都是
O
(
n
log
n
)
O(n\log n)
O(nlogn)
56pts赛时代码丑极了:
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x = 0, f =1;char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
return x * f;
}
const int maxn = 3e5 + 10;
int n, m, k, d;
int f[maxn][2], arr[maxn];
vector<pair<int,int> > vec[maxn];
struct Segment_Tree{
struct node{
int maxx, tag;
node(int maxx = 0,int tag = 0):maxx(maxx),tag(tag){}
}d[maxn << 2];
node mergenode(node l,node r){
return node(max(l.maxx,r.maxx));
}
void pushdown(int p){
if(d[p].tag){
d[p << 1].tag += d[p].tag;
d[p << 1].maxx += d[p].tag;
d[p << 1 | 1].tag += d[p].tag;
d[p << 1 | 1].maxx += d[p].tag;
d[p].tag = 0;
}
}
void build(int l,int r,int p){
if(l == r){d[p] = node();return;}
int mid = l + r >> 1;
build(l,mid,p << 1);build(mid + 1,r,p << 1 | 1);
d[p] = mergenode(d[p << 1],d[p << 1 | 1]);
}
void update(int l,int r,int pos,int p,int add){
if(l == r && l == pos){d[p].maxx += add;return;}
int mid = l + r >> 1;pushdown(p);
if(pos <= mid)update(l,mid,pos,p << 1,add);
else update(mid + 1,r,pos,p << 1 | 1,add);
d[p] = mergenode(d[p << 1],d[p << 1 | 1]);
}
void update(int l,int r,int s,int t,int p,int add){
if(s <= l && r <= t){d[p].tag += add;d[p].maxx += add;return;}
int mid = l + r >> 1; pushdown(p);
if(s <= mid)update(l,mid,s,t,p << 1,add);
if(mid < t)update(mid + 1,r,s,t,p << 1 | 1,add);
d[p] = mergenode(d[p << 1],d[p << 1 | 1]);
}
node query(int l,int r,int s,int t,int p){
if(s <= l && r <= t){return d[p];}
int mid = l + r >> 1;pushdown(p);
if(t <= mid)return query(l,mid,s,t,p << 1);
if(mid < s)return query(mid + 1,r,s,t,p << 1 | 1);
return mergenode(query(l,mid,s,t,p << 1),query(mid + 1,r,s,t,p << 1 | 1));
}
int query(int l,int r){return query(0,n + 1,l,r,1).maxx;}
// void update(int pos,int add){update(0,n + 1,pos,1,add);}
void update(int l,int r,int add){update(0,n + 1,l,r,1,add);}
}tree;
signed main(){
//freopen("run.in","r",stdin);
//freopen("run.out","w",stdout);
int testcase = read(), T = read();
while(T--){
n = read(), m = read(), k = read(), d = read();
int x, y, v;
for(int i = 1;i <= n;i++)vec[i].clear();
memset(f,0,sizeof(f));memset(arr,0,sizeof(arr));
tree.build(0,n + 1,1);
for(int i = 1;i <= m;i++){
x = read(), y = read(), v = read();
vec[x].push_back(make_pair(x - y + 1,v));
}
int ans = 0;
bool switchcase = (n <= 1e3);
// switchcase = false;
if(switchcase){
for(int i = 1;i <= n;i++){
int sum = 0;
for(auto j : vec[i]){arr[j.first] += j.second;}
for(int j = i;i - j < k && j > 0;j--){
sum += arr[j];
f[i][1] = max(f[i][1],f[j - 1][0] + (j - 1) * d + sum - i * d);
}
f[i][0] = max(f[i - 1][0],f[i - 1][1]);
ans = max(ans,max(f[i][0],f[i][1]));
}
printf("%lld\n",ans);
}
else{
for(int i = 1;i <= n;i++){
for(auto j : vec[i]){tree.update(1,j.first,j.second);}
f[i][1] = tree.query(max(1LL,i - k + 1),i) - i * d;
f[i][0] = max(f[i - 1][0],f[i - 1][1]);
ans = max(ans,max(f[i][0],f[i][1]));
tree.update(i + 1,i + 1,f[i][0] + i * d);
}
printf("%lld\n",ans);
}
}
return 0;
}
100pts做法
考虑有效的位置有多少个。
如果没思路不妨回头看看DP式子中有几个有效位置。
很显然,对于每个挑战
i
i
i,只有
l
i
−
1
,
l
i
,
r
i
l_i-1,l_i,r_i
li−1,li,ri 这三个位置是有效的。
于是乎离散化,设
x
i
x_i
xi 表示编号为
i
i
i 的真实位置。
那么DP式子也有点变化(变化不大):
f
i
,
1
=
−
x
i
+
max
i
−
j
<
k
(
f
j
−
1
,
0
+
∑
k
∈
[
j
,
i
]
a
r
r
k
+
x
j
−
1
)
f_{i,1} =-x_i+ \max_{i-j<k}{(f_{j -1,0}+\sum_{k\in[j,i]}{arr_k} +x_j-1)}
fi,1=−xi+i−j<kmax(fj−1,0+k∈[j,i]∑arrk+xj−1)
同样线段树上每一个位置存放数字也有点变化。
对于位置
j
j
j 存储:
f
j
−
1
,
0
+
∑
k
∈
[
j
,
i
]
a
r
r
k
+
x
j
−
1
f_{j -1,0}+\sum_{k\in[j,i]}{arr_k} +x_j-1
fj−1,0+∑k∈[j,i]arrk+xj−1。
然后就能从
56
→
100
56\to100
56→100 了考场上想出来了但是没时间了(悲。
时间复杂度和空间复杂度都是
O
(
3
m
log
3
m
)
O(3m\log 3m)
O(3mlog3m)。
100pts代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef long long ll;
inline int read(){
int x = 0, f =1;char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
return x * f;
}
bool st;
const int maxn = 9e5 + 10;
struct Segment_Tree{
int n;
struct node{
ll maxx, tag;
node(ll maxx = 0,ll tag = 0):maxx(maxx),tag(tag){}
}d[maxn << 2];
node mergenode(node l,node r){
return node(max(l.maxx,r.maxx));
}
void pushdown(int p){
if(d[p].tag){
d[p << 1].tag += d[p].tag;
d[p << 1].maxx += d[p].tag;
d[p << 1 | 1].tag += d[p].tag;
d[p << 1 | 1].maxx += d[p].tag;
d[p].tag = 0;
}
}
void build(int l,int r,int p){
if(l == r){d[p] = node();return;}
int mid = l + r >> 1;
build(l,mid,p << 1);build(mid + 1,r,p << 1 | 1);
d[p] = mergenode(d[p << 1],d[p << 1 | 1]);
}
// void update(int l,int r,int pos,int p,int add){
// if(l == r && l == pos){d[p].maxx += add;return;}
// int mid = l + r >> 1;pushdown(p);
// if(pos <= mid)update(l,mid,pos,p << 1,add);
// else update(mid + 1,r,pos,p << 1 | 1,add);
// d[p] = mergenode(d[p << 1],d[p << 1 | 1]);
// }
void update(int l,int r,int s,int t,int p,ll add){
if(s <= l && r <= t){d[p].tag += add;d[p].maxx += add;return;}
int mid = l + r >> 1; pushdown(p);
if(s <= mid)update(l,mid,s,t,p << 1,add);
if(mid < t)update(mid + 1,r,s,t,p << 1 | 1,add);
d[p] = mergenode(d[p << 1],d[p << 1 | 1]);
}
node query(int l,int r,int s,int t,int p){
if(s <= l && r <= t){return d[p];}
int mid = l + r >> 1;pushdown(p);
if(t <= mid)return query(l,mid,s,t,p << 1);
if(mid < s)return query(mid + 1,r,s,t,p << 1 | 1);
return mergenode(query(l,mid,s,t,p << 1),query(mid + 1,r,s,t,p << 1 | 1));
}
void build(int x){n = x;build(0,n + 1,1);}
int query(int l,int r){return query(0,n + 1,l,r,1).maxx;}
// void update(int pos,int add){update(0,n + 1,pos,1,add);}
void update(int l,int r,ll add){update(0,n + 1,l,r,1,add);}
}tree;
int n, m, k;
ll d, f[maxn][2];
vector<pair<int,int> > vec[maxn];
vector<pair<pair<int,int> , int> > tmp;
vector<ll> disc;
int Lk[maxn];
bool ed;
signed main(){
// cerr << "cost : " << (&st - &ed) / 1024 / 1024 << "Mib" << endl;
// freopen("run.in","r",stdin);
// freopen("run.out","w",stdout);
int testcase = read(), T = read();
while(T--){
n = read(), m = read(), k = read(), d = read();
int x, y, v;
disc.clear();tmp.clear();
memset(f,0,sizeof(f));
for(int i = 1;i <= m;i++){
x = read(), y = read(), v = read();
tmp.push_back(make_pair(make_pair(x - y + 1,x),v));
disc.push_back(x - y + 1);disc.push_back(x);disc.push_back(x - y);
}
disc.push_back(-1);sort(disc.begin(),disc.end());
disc.erase(unique(disc.begin(),disc.end()),disc.end());
int tot = disc.size() - 1;tree.build(tot);
// cerr << "tot = " << tot << endl;
for(int i = 1;i <= tot;i++)vec[i].clear();
// puts("12111111");
int l = 1;
for(int i = 0;i < m;i++){
// printf("l = %d r = %d\n",tmp[i].first.first,tmp[i].first.second);
tmp[i].first.first = lower_bound(disc.begin(),disc.end(),tmp[i].first.first ) - disc.begin();
tmp[i].first.second = lower_bound(disc.begin(),disc.end(),tmp[i].first.second) - disc.begin();
vec[tmp[i].first.second].push_back(make_pair(tmp[i].first.first,tmp[i].second));
// printf("l = %d r = %d\n",tmp[i].first.first,tmp[i].first.second);
}
// puts("222222222");
for(int i = 1;i <= tot;i++){
while(disc[i] - disc[l] >= k && l <= i)l++;
Lk[i] = l;
// printf("Lk[%d] = %d, disc[%d] = %d\n",i,Lk[i],i,disc[i]);
}
// for(int i = 0;i < disc.size();i++){printf("disc[%d] = %d\n",i,disc[i]);}
ll ans = 0;
// puts("3333333333");
for(int i = 1;i <= tot;i++){
for(auto j : vec[i]){tree.update(1,j.first,j.second);}
f[i][1] = tree.query(Lk[i],i) - disc[i] * d;
f[i][0] = max(f[i - 1][0],f[i - 1][1]);
ans = max(ans,max(f[i][0],f[i][1]));
tree.update(i + 1,i + 1,f[i][0] + disc[i] * d);
// printf("f[%d][0] = %lld,f[%d][1] = %lld\n",i,f[i][0],i,f[i][1]);
}
printf("%lld\n",ans);
}
return 0;
}