[APIO2016] 烟火表演 题解
[APIO2016] 烟火表演 题解
知识点
可并堆(左偏树),Slope Trick,优化 DP。
分析
\(7\%\)
初一数学绝对值问题,中位数处理即可……
\(26\%\)
注意到距离范围极小,那么可以 DP:设 \(f_{u,i}\) 为到点 \(u\) 时,后面的烟花引爆时间为 \(v\) 的最小代价。
直接做是 \(O(n|\Sigma|^2)\) 的,可以拿到分数。
\(55\%\)
你有能力写这个不如直接上正解……
\(100\%\)
首先,对于每个叶子结点,DP 数组上传父节点时就相当于一个常数函数加一次绝对值函数,这是一个下凸包,那么根据闵可夫斯基和的性质,结合数学归纳法,可知之后每一个 DP 数组都相当于一个下凸函数 \(f_u{(x)}\),并且所有斜率都是整数,并且级别在 \(O(n)\),可以使用 Slope Trick。
设现在有一个函数 \(f_u(x)\) 向父节点转移,边的长度为 \(w\),生成的新函数为 \(f_u'(x)\)。
对于一个 \(f_u(x)\),由于斜率全都是整数,那么肯定会在某个区间 \([L,R]\) 或者单个点 \(\{ X \}\) 有一段连续的最小值(单个点在这里也当做 \(L=R\) 的区间看待),那么可以得到:
这个式子其实就代表我们把 \(f_u(x)\) 的最小值区间 \([L,R]\) 向右推 \(w\) 个单位长度形成了 \(f_u'(x)\) 的最小值区间 \([L+w,R+w]\)。然后我们就可以把 \(f_u'(x)\) 和父节点的 \(f_{fa_u}(x)\) 合并。
那么现在来考虑如何找出一个 \(f_u(x)\) 的 \([L,R]\) 区间:我们看到从子节点传上来的 \(f_{v}'(x),v \in Son_u\),它在 \([L,+\infin)\) 部分形成三段斜率分别为 \(-1,0,1\) 的线段和射线,设 \(u\) 有 \(cnt\) 个子节点,那么 \(f_u(x)\) 的最后一段的斜率一定是 \(cnt\),也就是每个子节点的斜率为 \(1\) 的那一段叠加而成,那么我们可以把子节点的 \([L+w,R+w]\) 排序后从右边将结点一个个减掉,重复 \(cnt-1\) 次,就可以找到自己的 \([L,R]\) 了。
那么我们可以用大根可并堆实现,选用配对堆或左偏树皆可:
-
\(f_u(x) \to f_u'(x)\):
将 \(u\) 的左偏树中 \([L,R]\) 两结点删除,加入 \([L+w,R+w]\)。
-
\(f_u'(u) \to f_{fa_u}(x)\)
直接把 \(u\) 的左偏树合并到 \(fa_u\) 的左偏树上。
-
在 \(f_u(x)\) 中找到 \([L,R]\):
弹出堆顶 \(cnt-1\) 次,剩下堆的堆顶两个即为 \(L,R\)。
最后再来考虑统计答案:首先 \(f_1(0)\) 为 \(\sum_{i=2}^{n+m} C_{i}\),然后我们又有由斜率为 \(-1\) 叠加成的的函数的所有转折点,那么就可以很简单的求出了。
代码
//#define Plus_Cat ""
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define ll long long
#define RCL(a,b,c,d) memset(a,b,sizeof(c)*(d))
#define tomin(a,...) ((a)=min({(a),__VA_ARGS__}))
#define tomax(a,...) ((a)=max({(a),__VA_ARGS__}))
#define FOR(i,a,b) for(int i(a); i<=(int)(b); ++i)
#define DOR(i,a,b) for(int i(a); i>=(int)(b); --i)
#define EDGE(g,i,x,y) for(int i(g.h[x]),y(g[i].v); ~i; y=g[i=g[i].nxt].v)
using namespace std;
constexpr int N(3e5+10);
namespace IOEcat {
#define isD(c) ('0'<=(c)&&(c)<='9')
#define DE(...) E(#__VA_ARGS__,__VA_ARGS__)
struct Icat {
char getc() {
return getchar();
}
template<class T>void operator ()(T &x) {
static bool sign(0);
static char ch(0);
sign=0,x=0;
while(ch=getc(),!isD(ch))if(ch=='-')sign=1;
do x=(x<<1)+(x<<3)+(ch^48);
while(ch=getc(),isD(ch));
if(sign)x=-x;
}
template<class T,class...Types>void operator ()(T &x,Types&...args) {
return (*this)(x),(*this)(args...);
}
} I;
struct Ocat {
void putc(char c) {
putchar(c);
}
template<class T>void operator ()(T x,const char lst='\n') {
static int top(0);
static char st[100];
if(x<0)x=-x,putc('-');
do st[++top]=(x%10)^48,x/=10;
while(x);
while(top)putc(st[top--]);
putc(lst);
}
template<class T,class...Types>void operator ()(const T x,const char lst='\n',const Types...args) {
return (*this)(x,lst),(*this)(args...);
}
} O;
struct Ecat {
template<class T>void operator ()(const char *fmt,const T x) {
cerr<<fmt<<':'<<x<<'.'<<endl;
}
template<class T,class...Types>void operator ()(const char *fmt,const T x,const Types...args) {
while(*fmt^',')cerr<<*fmt++;
return cerr<<':'<<x<<" ,",(*this)(++fmt,args...);
}
} E;
} using namespace IOEcat;
int n,m;
int fa[N],pa[N];
ll ans;
vector<int> g[N];
namespace Subtask1 { /*中位数*/ /*7%*/
bool Check() {
return n==1;
}
int Cmain() {
sort(pa+2,pa+n+1);
FOR(i,2,n)ans+=abs(pa[i]-pa[(n+2)>>1]);
O(ans,'\n');
return 0;
}
}
namespace Subtask2 { /*O(n^3) DP*/ /*26%*/
constexpr int N(3e2+10);
int f[N][N];
bool max_dis(int u,ll dis) {
if((dis+=pa[u])>300)return 0;
for(int v:g[u])if(!max_dis(v,dis))return 0;
return 1;
}
bool Check() {
return n<=300&&max_dis(1,0);
}
void dp(int u) {
if(u>n)return RCL(f[u],INF,f[u],1),f[u][0]=0,void();
for(int v:g[u]) {
dp(v);
FOR(i,0,N-5) {
int mi(INF);
FOR(j,0,i)tomin(mi,abs(j+pa[v]-i)+f[v][j]);
f[u][i]+=mi;
}
}
}
int Cmain() {
dp(1),ans=INF;
FOR(i,0,N-5)tomin(ans,(ll)f[1][i]);
O(ans,'\n');
return 0;
}
}
namespace Subtask { /*斜堆维护函数转折点*/ /*100%*/
struct Self_Adjusting_Heap {
int tot;
int rt[N];
struct node {
int ls,rs;
ll val;
} tr[N<<1];
int &operator ()(int i) {
return rt[i];
}
node &operator [](int i) {
return tr[i];
}
int New(ll val) {
return tr[++tot]={0,0,val},tot;
}
int Merge(int p,int q) {
if(!p||!q)return p|q;
if(tr[p].val<tr[q].val)swap(p,q);
return tr[p].rs=Merge(tr[p].rs,q),swap(tr[p].ls,tr[p].rs),p;
}
void Pop(int &p) {
p=Merge(tr[p].ls,tr[p].rs);
}
} hp;
int Cmain() {
DOR(u,n,2) {
ans+=pa[u];
FOR(i,2,g[u].size())hp.Pop(hp(u));
ll R(hp[hp(u)].val);
hp.Pop(hp(u));
ll L(hp[hp(u)].val);
hp.Pop(hp(u));
hp(u)=hp.Merge(hp(u),hp.New(R+pa[u]));
hp(u)=hp.Merge(hp(u),hp.New(L+pa[u]));
hp(fa[u])=hp.Merge(hp(fa[u]),hp(u));
}
FOR(i,1,g[1].size())hp.Pop(hp(1));
while(hp(1))ans-=hp[hp(1)].val,hp.Pop(hp(1));
O(ans,'\n');
return 0;
}
}
int main() {
#ifdef Plus_Cat
freopen(Plus_Cat ".in","r",stdin),freopen(Plus_Cat ".out","w",stdout);
#endif
I(n,m),n+=m;
FOR(i,2,n)I(fa[i],pa[i]),g[fa[i]].push_back(i);
if(Subtask1::Check())return Subtask1::Cmain();
if(Subtask2::Check())return Subtask2::Cmain();
return Subtask::Cmain();
}