bzoj3672 [Noi2014]购票

14 篇文章 0 订阅
12 篇文章 0 订阅

(http://www.elijahqi.win/2018/01/19/bzoj3672-noi2014%E8%B4%AD%E7%A5%A8/%20%E2%80%8E)
Description
今年夏天,NOI在SZ市迎来了她30周岁的生日。来自全国 n 个城市的OIer们都会从各地出发,到SZ市参加这次盛会。
全国的城市构成了一棵以SZ市为根的有根树,每个城市与它的父亲用道路连接。为了方便起见,我们将全国的 n 个城市用 1 到 n 的整数编号。其中SZ市的编号为 1。对于除SZ市之外的任意一个城市 v,我们给出了它在这棵树上的父亲城市 fv 以及到父亲城市道路的长度 sv。
从城市 v 前往SZ市的方法为:选择城市 v 的一个祖先 a,支付购票的费用,乘坐交通工具到达 a。再选择城市 a 的一个祖先 b,支付费用并到达 b。以此类推,直至到达SZ市。
对于任意一个城市 v,我们会给出一个交通工具的距离限制 lv。对于城市 v 的祖先 a,只有当它们之间所有道路的总长度不超过 lv 时,从城市 v 才可以通过一次购票到达城市 a,否则不能通过一次购票到达。对于每个城市 v,我们还会给出两个非负整数 pv,qv 作为票价参数。若城市 v 到城市 a 所有道路的总长度为 d,那么从城市 v 到城市 a 购买的票价为 dpv+qv。
每个城市的OIer都希望自己到达SZ市时,用于购票的总资金最少。你的任务就是,告诉每个城市的OIer他们所花的最少资金是多少。

Input
第 1 行包含2个非负整数 n,t,分别表示城市的个数和数据类型(其意义将在后面提到)。输入文件的第 2 到 n 行,每行描述一个除SZ之外的城市。其中第 v 行包含 5 个非负整数 f_v,s_v,p_v,q_v,l_v,分别表示城市 v 的父亲城市,它到父亲城市道路的长度,票价的两个参数和距离限制。请注意:输入不包含编号为 1 的SZ市,第 2 行到第 n 行分别描述的是城市 2 到城市 n。
Output
输出包含 n-1 行,每行包含一个整数。其中第 v 行表示从城市 v+1 出发,到达SZ市最少的购票费用。同样请注意:输出不包含编号为 1 的SZ市。

Sample Input
7 3

1 2 20 0 3

1 5 10 100 5

2 4 10 10 10

2 9 1 100 10

3 5 20 100 10

4 4 20 0 10

Sample Output

40

150

70

149

300

150
HINT

对于所有测试数据,保证 0≤pv≤106,0≤qv≤1012,1≤fv< v;保证 0< sv≤lv≤2×1011,且任意城市到SZ市的总路程长度不超过 2×1011。

输入的 t 表示数据类型,0≤t<4,其中:

当 t=0 或 2 时,对输入的所有城市 v,都有 fv=v-1,即所有城市构成一个以SZ市为终点的链;

当 t=0 或 1 时,对输入的所有城市 v,都有 lv=2×1011,即没有移动的距离限制,每个城市都能到达它的所有祖先;

当 t=3 时,数据没有特殊性质。

n=2×10^5
点分+cdq分治+斜率优化
怎么做 首先先做一个朴素的dp出来设dp[i]表示从1号点走到我这个节点的最小代价那么一个显然的做法可以拿到30分 无脑暴力dp直接从前面去枚举转移但是针对大的数据gg 当我们再次讲dp方程写出的时候发现很好啊像斜率优化dp但是我化简完之后发现斜率不单调怎么办 继续把式子化简发现可以把原式看做在前面所有点对构成的平面中我们维护一个凸包然后每次都找到我这条直线和我凸包相切的那个点就是我前面的最优决策了但是这显然需要我们动态维护凸包怎么搞..现在暂时还不会膜了icefox巨佬的代码 发现可以cdq搞 看这个标签自己yy了很久无果然后看了代码发现自己cdq只会背板子 没有真正理解 这个是怎么搞的 首先我点分出每个重心然后在重心上找到他上一个重心然后把这条链包括自己更新对然后将我的所有子树也像这个一样全部更新一次即可 然后因为我需要满足条件dis(i)-dis(j)≤l(i)所以我将子树的这个需处理出来 排序 因为随着这个的递降我上半段那条链的可用情况在递增那么我们就可以随着上面的递增构造凸包 然后每次二分找一下相切的那个点 对于我这个画出来比较奇怪 从原点开始斜率逐渐递增并且是正的 dp[i]=min{dp[j]+(dis[i]dis[j])p[i]+q[i]} 化简 dp[i]=q[i]+dis[i]p[i]+min{dp[j]dis[j]p[i]} 看括号内的式子 我们如果以p[i]为斜率 dp[j]为纵坐标 dis[j]为横坐标去画图可以发现这个就是以-p[i]为斜率的直线和之前的点构成的凸包上求一下切线

朴素dp

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 220000
#define ll long long
using namespace std;
inline char gc(){
    static char now[1<<16],*S,*T;
    if (T==S){T=(S=now)+fread(now,1,1<<16,stdin);if (T==S) return EOF;}
    return *S++;
}
inline ll read(){
    ll x=0;char ch=gc();
    while(ch<'0'||ch>'9') ch=gc();
    while(ch<='9'&&ch>='0') x=x*10+ch-'0',ch=gc();
    return x;
}
struct node{
    int y,z,next;
}data[N];ll dp[N],q[N];
int s[N],p[N],fa[N],l[N],num,h[N],dis[N],n,t;
inline void dfs(int x){
    int now=fa[x],pre=x;int d=0;
    while(now!=0&&d+s[pre]<=l[x]){
        dp[x]=min(dp[x],dp[now]+(ll)(dis[x]-dis[now])*p[x]+q[x]);d+=s[pre];now=fa[now];pre=fa[pre];
    }
    for (int i=h[x];i;i=data[i].next){
        int y=data[i].y,z=data[i].z;dis[y]=dis[x]+z;dfs(y);
    }
}
int main(){
//  freopen("bzoj3672.in","r",stdin);
    n=read();t=read();for (int i=1;i<=n;++i) dp[i]=1LL<<60;
    dp[1]=0;
    for (int i=2;i<=n;++i){
        fa[i]=read();data[++num].y=i;data[num].next=h[fa[i]];h[fa[i]]=num;
        data[num].z=s[i]=read();p[i]=read();q[i]=read();l[i]=read();
    } dfs(1);
    //for (int i=1;i<=n;++i) printf("%d ",dis[i]);puts("");
    for(int i=2;i<=n;++i) printf("%lld\n",dp[i]);
    return 0;
}

重写了下代码然后就死活写不出来了查错半天..

#include<queue>
#include<cstdio>
#include<algorithm>
#define ll long long
#define inf 1LL<<60
#define N 220000
#define fi first
#define se second
#define pa pair<ll,int>
using namespace std;
inline char gc(){
    static char now[1<<16],*S,*T;
    if (T==S){T=(S=now)+fread(now,1,1<<16,stdin);if (T==S) return EOF;}
    return *S++;
} 
inline ll read(){
    ll x=0;char ch=gc();
    while(ch<'0'||ch>'9') ch=gc();
    while(ch<='9'&&ch>='0') x=x*10+ch-'0',ch=gc();
    return x;
}bool visit[N];
int n,tt,fa[N],p[N],f[N],num,h[N],size[N],sum,root,q[N];pa qq[N];
ll s[N],q1[N],lim[N],dis[N],dp[N];
struct node{
    int y,next;
}data[N];
inline void get_root(int x){
    f[x]=0;size[x]=1;
    for (int i=h[x];i;i=data[i].next){
        int y=data[i].y;if (visit[y]) continue;
        get_root(y);size[x]+=size[y];f[x]=max(f[x],size[y]);
    }f[x]=max(f[x],sum-size[x]);
    if (f[x]<f[root]&&size[x]>1) root=x;
}
inline void dfs(int x){
    qq[++num]=make_pair(dis[x]-lim[x],x);
    for (int i=h[x];i;i=data[i].next){
        int y=data[i].y;if (visit[y]) continue;dfs(y);
    }
}
inline bool cmp(const pa &a,const pa &b){return a.fi>b.fi;}
inline double slope(int a,int b){
    return (double) (dp[a]-dp[b])/(dis[a]-dis[b]);
}
inline void solve(int x,int sz){
    if (sz==1) return;root=0;sum=sz;get_root(x);int rt=root;
    for (int i=h[rt];i;i=data[i].next) visit[data[i].y]=1;
    solve(x,size[x]-size[rt]+1);num=0;
    for (int i=h[rt];i;i=data[i].next) dfs(data[i].y);
    sort(qq+1,qq+num+1,cmp);int now=rt,h1=1,t1=0;
    for (int i=1;i<=num;++i){
        while(now!=fa[x]&&dis[now]>=qq[i].fi){
            while(h1<t1&&slope(q[t1-1],q[t1])<slope(q[t1],now)) --t1;
            q[++t1]=now;now=fa[now];
        }int l=h1+1,r=t1;if (h1>t1) continue;
        while(l<=r){
            int mid=l+r>>1;
            if(slope(q[mid-1],q[mid])<p[qq[i].se]) r=mid-1;else l=mid+1;
        }int j=q[r],nw=qq[i].se;
        dp[nw]=min(dp[nw],dp[j]+(dis[nw]-dis[j])*p[nw]+q1[nw]); 
    }
    for (int i=h[rt];i;i=data[i].next) solve(data[i].y,size[data[i].y]);
}
int main(){
    freopen("bzoj3672.in","r",stdin);
    n=read();tt=read();for (int i=1;i<=n;++i) dp[i]=inf;dp[1]=0;
    for (int i=2;i<=n;++i){
        fa[i]=read();s[i]=read();p[i]=read();q1[i]=read();lim[i]=read();
        data[i].y=i;data[i].next=h[fa[i]];h[fa[i]]=i;dis[i]=dis[fa[i]]+s[i];
    }root=0;f[0]=0x3f3f3f3f;solve(1,n);
    for (int i=2;i<=n;++i) printf("%lld\n",dp[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值