Part 0
测试点1,2,3。枚举,复杂度
Part 1
对于t=0的测试点。
我们看一看这个状态转移式子。
记dp[i]为i这个点到达SZ市所需的最小资金dis[i]为SZ市到这个点i的路径长度。我们要求t这个点的答案,那么就有。(s为t到根路径上的点)
看到这个式子,不妨尝试一下斜率优化,于是将式子转化。
然后按照斜率优化的套路
那么我们要求的就是
。
对于点t到根到路径上的点x和y。如果点x比点y优,那么
,也就是
这就可以看成两个点构成直线的斜率。
于是我们维护一个斜率单调递增的单调栈(也就是下凸包)。
这一步的跳跃如果看不懂有三种解决方案。1手动模拟。2参考一下这篇题解的第二个性质,第一个性质由于K不具有单调性故不存在。3模拟一下上凸包,可以发现中间那些点不可能最优。
因为P[t]不具有单调性(深度大的点的P不一定比深度小的点的P大)故我们每次查询答案要在单调栈上二分。由于我们维护的点的斜率递增,那么我们拿每个点与前面的点比较,如果他们之间的斜率大于p[t],那么之后的斜率也大于p[t],此时后面的点都没有前面的更优。故我们只用二分最后一个比前面的更优的点。(注意一下二分的边界)
这个部分分的代码:
#include<cstdio>
#define M 200005
int fa[M];
long long Q[M],L[M],dp[M],D[M],P[M];
long long dis[M];
struct node{long long x,y;}stk[M];
int top;
bool cmp(node A,node B,long long K){//B比A优
return 1.0*K*(B.x-A.x)>B.y-A.y;
}
int Get_mx(long long K){
int L=2,R=top,res=1;//注意二分边界
while(L<=R){
int mid=(L+R)>>1;
if(cmp(stk[mid-1],stk[mid],K)){
L=mid+1;
res=mid;
}else R=mid-1;
}
return res;
}
bool check(node A,node B,node C){//是否A--C的斜率小于A--B的斜率
return 1.0*(C.y-A.y)*(B.x-A.x)<1.0*(B.y-A.y)*(C.x-A.x);
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=2;i<=n;i++)scanf("%d%lld%lld%lld%lld",&fa[i],&D[i],&P[i],&Q[i],&L[i]);
stk[++top]=(node){0,0};
for(int i=2;i<=n;i++){
dis[i]=dis[i-1]+D[i];//dis为这个点到SZ市的距离
int p=Get_mx(P[i]);//二分最优点
dp[i]=stk[p].y-P[i]*stk[p].x+Q[i]+P[i]*dis[i];//计算答案
node New=(node){dis[i],dp[i]};
while(top>1&&check(stk[top-1],stk[top],New))top--;//维护下凸包
stk[++top]=New;
printf("%lld\n",dp[i]);
}
return 0;
}
Part 1
对于t=1
我们要一直维护一个从根结点到这个点的单调栈。故我们每次弹出都不能是真的弹出,因为之后还要回溯。对于一个单调栈,我们可以用一个可以回溯的栈。在每次插入一个元素时,我们可以找到这个元素应该插入到什么位置,我们存下当时的top。然后把这个位置的元素放到栈的最后面,然后把top指向这个位置。然后撤销操作就是把这个位置的元素放到当前top的位置,再把top指向之前的位置即可。
const int SIGN=-2019;//直接插入的标志
struct Stack{
int top;
vector<node>stk;
stack<int>used;//存上一个top的位置
void clear(){
top=-1;//初始top为0(个人习惯)
stk.clear();while(!used.empty())used.pop();
}
void Push(node New){//0~top的元素才算是stk内的元素,后面的是为了撤销用的(本来是要在开一个栈的,但是懒QWQ)
int L=1,R=top,res=SIGN;
while(L<=R){//找到它应该插入的位置,就是之前的一直弹到的位置
int mid=(L+R)>>1;
if(check(stk[mid-1],stk[mid],New)){
res=mid;
R=mid-1;
}else L=mid+1;
}
if(res==SIGN){//如果可以之间插在后面,但这个时候不可以直接插,别忘了top后面还有元素
stk.push_back(stk[++top]);//把这个元素加到栈(但不是top的后面)的最后面
stk[top]=New;
used.push(SIGN);
}else {
int tmp=top;
stk.push_back(stk[res]);
stk[res]=New;
top=res;
used.push(tmp);
}
}
void Backtrace(){
int Lst=used.top();used.pop();//上一个top
stk[top]=stk[stk.size()-1];stk.pop_back();//把这个元素回到它原来的位置
if(Lst==SIGN)top--;//如果上一步是直接插入
else top=Lst;//top重置
}
long long Query(long long K){
if(top<0)return 1e18;
int L=1,R=top,res=0;
while(L<=R){//同Part0的Get_mx
int mid=(L+R)>>1;
if(cmp(stk[mid-1],stk[mid],K)){
L=mid+1;
res=mid;
}else R=mid-1;
}
return Calc(stk[res],K);
}
}S;
总代码如下:
#include<cstdio>
#include<vector>
#include<stack>
#define M 200005
using namespace std;
struct E{
int to,nx;
}edge[M<<1];
int tot,head[M];
void Addedge(int a,int b){
edge[++tot].to=b;
edge[tot].nx=head[a];
head[a]=tot;
}
int fa[M];
long long Q[M],L[M],dp[M],D[M],P[M];
long long dis[M];
struct node{
long long x,y;
}stk[M];
bool cmp(node A,node B,long long K){//B比A更优
return 1.0*K*(B.x-A.x)>=B.y-A.y;
}
bool check(node A,node B,node C){
return 1.0*(C.y-A.y)*(B.x-A.x)<=1.0*(B.y-A.y)*(C.x-A.x);
}
long long Calc(node A,long long K){//算截距
return A.y-A.x*K;
}
struct Stack{···}
void dfs(int now){
dis[now]=dis[fa[now]]+D[now];
if(now!=1)dp[now]=S.Query(P[now])+Q[now]+P[now]*dis[now];//算答案
node New=(node){dis[now],dp[now]};
S.Push(New);
for(int i=head[now];i;i=edge[i].nx){
int nxt=edge[i].to;
dfs(nxt);
}
S.Backtrace();
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=2;i<=n;i++){
scanf("%d%lld%lld%lld%lld",&fa[i],&D[i],&P[i],&Q[i],&L[i]);
Addedge(fa[i],i);
}
S.clear();
dfs(1);
for(int i=2;i<=n;i++)printf("%lld\n",dp[i]);
return 0;
}
Part 2
正解
有了L这个约束之后,我们就需要知道从根到这个点的dep大于L的元素所构成的单调栈(同样要支持撤销)。我们可以维护一个个小区间的元素然后分别计算答案,取最小值即为一个大区间的答案。区间问题有很多种解法,像线段树,树状数组。这里用树状数组维护一个后缀。每一个树状数组的节点维护的就是他所管辖的元素所构成的单调栈(当然加入时是按dep加入的)。
代码实现如下:
struct Bin{
Stack S[M];
void Build(){for(int i=1;i<=n;i++)S[i].clear();}
void Updata(int x,node New){
while(x){
S[x].Push(New);
x-=lowbit(x);
}
}
void Backtrace(int x){//回撤
while(x){
S[x].Backtrace();
x-=lowbit(x);
}
}
long long Query(int x,long long K){
long long res=1e18;
while(x<=n){//答案就是每个小的区间的最小值
res=min(S[x].Query(K),res);
x+=lowbit(x);
}
return res;
}
}B;
那么正解的代码就是这样:
#include<cstdio>
#include<stack>
#include<vector>
#define M 200005
#define lowbit(x) x&-x
using namespace std;
struct E{
int to,nx;
}edge[M<<1];
int tot,head[M];
void Addedge(int a,int b){
edge[++tot].to=b;
edge[tot].nx=head[a];
head[a]=tot;
}
int n,m;
int fa[M];
long long Q[M],Len[M],dp[M],D[M],P[M];
long long dis[M];
struct node{
long long x,y;
};
int top;
bool cmp(node A,node B,long long K){//B比A优
return 1.0*K*(B.x-A.x)>B.y-A.y;
}
bool check(node A,node B,node C){
return 1.0*(C.y-A.y)*(B.x-A.x)<1.0*(B.y-A.y)*(C.x-A.x);
}
long long Calc(node A,long long K){//算截距
return A.y-A.x*K;
}
const int SIGN=-2019;//直接插入的标志
struct Stack{···};
struct Bin{···}B;
int FindL(int R,long long len){//取这个深度区间的最优左端点
int L=1,ans=R+1;
while(L<=R){
int mid=(L+R)>>1;
if(dis[mid]>=len){
ans=mid;
R=mid-1;
}else L=mid+1;
}
return ans;
}
void dfs(int now,int dep){
dis[dep]=dis[dep-1]+D[now];//这里的dis[i]对于表示now到根结点路径上的点,dep为i的点到根结点的距离
if(now!=1){
int Lx=FindL(dep,dis[dep]-Len[now]);
dp[now]=B.Query(Lx,P[now])+Q[now]+P[now]*dis[dep];
}
node New=(node){dis[dep],dp[now]};
if(head[now])B.Updata(dep,New);//叶子节点不用加
for(int i=head[now];i;i=edge[i].nx){
int nxt=edge[i].to;
dfs(nxt,dep+1);
}
if(head[now])B.Backtrace(dep);
}
int main(){
scanf("%d%d",&n,&m);
for(int i=2;i<=n;i++){
scanf("%d%lld%lld%lld%lld",&fa[i],&D[i],&P[i],&Q[i],&Len[i]);
Addedge(fa[i],i);
}
B.Build();
dfs(1,1);
for(int i=2;i<=n;i++)printf("%lld\n",dp[i]);
return 0;
}