AT1757 花火 题解
这是我根据官方题解复盘出来的做法。
\(O(n^2)\)DP显然。
令\(dp[i][j]\)表示i时刻在位置j的最小代价。
转移:
\[
dp[i][j]=min_{k<=j}(dp[i-1][k])+ ( i 时间内在位置 j 所有烟花的不满值之和)
\]
令 \(dp'[i][j]=min_{k<=j}(dp[i][k])\) (就是做了一个前缀min)
\(dp[i][j]=dp'[i-1][j]+\) ( \(i\) 时间内在位置 \(j\) 所有烟花的不满值之和)
\(dp'[i][j]=min(dp'[i][j-1],dp[i][j])\)
从“ \(i\) 时间内在位置 \(j\) 所有烟花的不满值之和”入手,令\(f(i)\)表示在 \(i\) 位置,某一烟花造成的不满值。我们可以发现, $f(i)=|x_i-i| $( \(x_i\) 表示烟花绽放的位置) 。
显然,\(f(i)\)是一个下凸函数,那么对于在同一时刻绽放的所有烟花,\(f(i)\)的和也为下凸函数。(若干个下凸函数的和仍然是下凸函数)
考虑我们的 \(dp\) 过程,是先加了同一时刻绽放的所有烟花的不满值的和,然后做了一遍前缀min,然后重复。
最开始dp函数是一条 \(y=0\) 的直线,加上 同一时刻绽放的所有烟花的不满值的和(一个下凸函数)之后仍是一个下凸函数,然后做一遍前缀min,仍然是一个下凸函数,而且对于下凸函数取min就是把后面一段导数大于0的一段的导数改为0。
为了方便维护下凸函数,官方题解采取了维护差分序列的方法,下面是我根据官方题解写的代码,线段树区间覆盖部分蒯的是这篇博客。
#include<bits/stdc++.h>
using namespace std;
const long long maxn = 100007,INF=214748364700000;
long long Max[maxn << 2], changetag[maxn << 2], addtag[maxn << 2];
long long a[maxn],t[maxn],m,ans(INF),dp[maxn];
long long n, LL;
inline void PushUp(long long rt) {
Max[rt] = max(Max[rt<<1], Max[rt<<1|1]);
}
inline void PushDown(long long rt) {
if (changetag[rt] != -INF) {
Max[rt<<1] = changetag[rt];
Max[rt<<1|1] = changetag[rt];
changetag[rt << 1] = changetag[rt];
addtag[rt << 1] = 0;
changetag[rt << 1 | 1] = changetag[rt];
addtag[rt << 1 | 1] = 0;
changetag[rt] = -INF;
} else if (addtag[rt]) {
Max[rt<<1] += addtag[rt];
Max[rt<<1|1] += addtag[rt];
if (changetag[rt<<1] != -INF) changetag[rt<<1] += addtag[rt];
else addtag[rt<<1] += addtag[rt];
if (changetag[rt<<1|1] != -INF) changetag[rt<<1|1] += addtag[rt];
else addtag[rt<<1|1] += addtag[rt];
addtag[rt] = 0;
}
}
void build(long long rt, long long l, long long r) {
changetag[rt] = -INF;
addtag[rt] = 0;
if (l == r) {
Max[rt] = 0;
return;
}
long long m = (l + r) >> 1;
build(rt<<1, l, m);
build(rt<<1|1, m+1, r);
// PushUp(rt);
}
void change(long long rt, long long l, long long r, long long L, long long R, long long C) {
if (L <= l && r <= R) {
Max[rt] = C;
changetag[rt] = C;
addtag[rt] = 0;
return;
}
long long m = (l + r) >> 1;
PushDown(rt);
if (L <= m) change(rt<<1, l, m, L, R, C);
if (R > m) change(rt<<1|1, m+1, r, L, R, C);
PushUp(rt);
}
void add(long long rt, long long l, long long r, long long L, long long R, long long C) {
if (L <= l && r <= R) {
Max[rt] = Max[rt] + C;
if (changetag[rt] == -INF) addtag[rt] += C;
else changetag[rt] += C;
return;
}
long long m = (l + r) >> 1;
PushDown(rt);
if (L <= m) add(rt<<1, l, m, L, R, C);
if (R > m) add(rt<<1|1, m+1, r, L, R, C);
PushUp(rt);
}
long long query(long long rt, long long l, long long r, long long L, long long R) {
if (L <= l && r <= R) return Max[rt];
long long m = (l + r) >> 1, res = -INF;
PushDown(rt);
if (L <= m) res = max(res, query(rt<<1, l, m, L, R));
if (R > m) res = max(res, query(rt<<1|1, m+1, r, L, R));
return res;
}
long long read() {
long long x(0);
char ch(getchar());
while(ch>'9'||ch<'0')ch=getchar();
while(ch<='9'&&ch>='0') {
x=x*10+ch-'0';
ch=getchar();
}
return x;
}
int main() {
// freopen("testdata.in","r",stdin);
n=read();
LL=read();
for (long long i = 1; i <= n; ++i)
scanf("%lld%lld",&t[i],&a[i]);
build(1, 1, LL);
long long head(1),dps(a[1]);
while (head<=n) {
if(a[head])add(1,1,LL,1,a[head],-1);
if(a[head]!=LL)add(1,1,LL,a[head]+1,LL,1);
while(t[head]==t[head+1]) {
++head;
dps+=a[head];
if(a[head])add(1,1,LL,1,a[head],-1);
if(a[head]!=LL)add(1,1,LL,a[head]+1,LL,1);
}
long long l(1),r(LL),mid((l+r)>>1);
while(l<r) {
if(query(1,1,LL,mid,mid)<0)l=mid+1;
else r=mid;
mid=(l+r)>>1;
}
// prlong longf("%d ",r);
if(r!=LL)
change(1,1,LL,r,LL,0);
else if(query(1,1,LL,LL,LL)>0)change(1,1,LL,LL,LL,0);
// for(long long i=1; i<=LL; i++)
// prlong longf("%d ",query(1,1,LL,i,i));
// prlong longf("\n");
++head;
dps+=a[head];
}
dp[0]=dps;
for(long long i=1; i<=LL; i++) {
dp[i]=dp[i-1]+query(1,1,LL,i,i);
ans=min(ans,dp[i]);
}
cout<<ans<<endl;
return 0;
}