zoukankan      html  css  js  c++  java
  • ●BZOJ 3672 [Noi2014]购票

    题链:

    http://www.lydsy.com/JudgeOnline/problem.php?id=3672

    题解:

    斜率优化DP,点分治(树上CDQ分治...)

    这里有一个没有距离限制的简单版:BZOJ 1767 [Ceoi2009]harbingers

    定义$DP[i]$为从i出发到1号点的最小花费,$dis_i$为i到1号点的距离:

    转移:

    $DP[i]=min(DP[j]+(dis_i-dis_j)P_j)+Q_i$

    $quad=min(DP[j]-dis_jP_i)+dis_iP_i+Q_i$

    显然这个$O(N^2)$的转移会超时。


    考虑优化:

    假设对于当前计算的DP[i],有两个转移来源点:k,j,同时dis[k]<dis[j],设j点比k点优。

    那么有:$DP[j]-dis_j*P_i-(DP[k]-dis_k*P_i)<0$

    则: $frac{DP[j]-DP[k]}{dis[j]-dis[k]}<P[i]$

    如果令 Slope(j,k)=$frac{DP[j]-DP[k]}{dis[j]-dis[k]}$

    那么得到结论,如果 $dis_k<dis_j$,且Slope(j,k)<P[i]的话,则j点优于k点。

    同时如果存在三个转移来源点:k,j,i,满足$dis_k<dis_j<dis_i$,

    同时Slope(i,j)<Slope(j,k),则j点无效。

    所以对于每个来源点二元组(dis[j],DP[j]),只需要在平面上维护一个下凸壳即可。


    1).如果问题不在一颗树上,而是在一个序列上,这个应该比较容易吧:一个裸的CDQ就可以解决了。

    2).而如果问题没有limit这个距离限制,即使在树上,也比较容易了,因为随着DFS遍历树时,dis是单增的,所以可以直接维护单调栈(详细见BZOJ 1767 [Ceoi2009]harbingers

    但是现在既在树上又有距离限制怎么办呢?这里采用点分治来实现CDQ分治的功能,(可以叫做树上CDQ分治么)

    对于当前的子树,我们令根为u,(这个根是子树内的点在前往1号点时都要经过的地方)。

    并找到子树内的重心cg(the center of gravity),

    然后先递归处理cg为根时含有u的那颗子树,

    不难发现,cg的其它子树的点(令这些点的集合为R)在前往1号点时,都会进过cg~u这一条链,

    即这条链上的点可能会成为R里的点的转移点。

    同时为了满足距离这一限制,即每个点i最多只能向上到达 $dis_i-limit_i$这个位置

    所以把R里的点按 $dis_i-limit_i$排序后,从大到小枚举R里的i点,并把cg~u这条链上dis小于$dis_i-limit_i$的点用单调栈维护一个下凸壳。

    接着在凸壳里二分最优的转移来源点即可。

    整个过程是$O(Nlog_2^2N)$的。


    (伤不起,读入居然要long long!)

    代码:PoPoQQQ的代码写得很棒呀!

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define MAXN 200050
    #define ll long long
    using namespace std;
    ll DP[MAXN],P[MAXN],Q[MAXN],G[MAXN],dis[MAXN];
    int fa[MAXN],siz[MAXN];
    int N,t;
    struct Edge{
    	ll val[MAXN];
    	int to[MAXN],nxt[MAXN],head[MAXN],ban[MAXN],ent;
    	Edge(){ent=2;}
    	void Adde(int u,int v,ll w){
    		to[ent]=v; val[ent]=w; nxt[ent]=head[u]; head[u]=ent++;
    	}
    }E;
    struct Mostk{
    	int s[MAXN],top;
    	#define Slope(i,j) (1.0*(DP[i]-DP[j])/(dis[i]-dis[j]))
    	void Reset(){top=0;}
    	void Push(int i){
    		if(top&&dis[s[top]]==dis[i])
    			{if(DP[i]<DP[s[top]]) top--; else return;}
    		while(top>1&&Slope(s[top-1],s[top])<Slope(s[top],i)) top--;
    		s[++top]=i;
    	}
    	int Query(int i){//二分单调栈
    		static int l,r,mid,ret;
    		if(!top) return 0;
    		l=2; r=top; ret=1;
    		while(l<=r){
    			mid=(l+r)>>1;
    			if(Slope(s[mid-1],s[mid])>=P[i]) ret=mid,l=mid+1;
    			else r=mid-1;
    		}
    		return s[ret];
    	}
    }S;
    void dfs(int u){
    	for(int i=E.head[u];i;i=E.nxt[i]){
    		int v=E.to[i];
    		dis[v]=dis[u]+E.val[i];
    		dfs(v);
    	}
    }
    void getcg(int u,int &cg,int &num,int sum){
    	int maxnum=0; siz[u]=1;
    	for(int i=E.head[u];i;i=E.nxt[i]){
    		int v=E.to[i]; if(E.ban[i]) continue;
    		getcg(v,cg,num,sum);
    		maxnum=max(maxnum,siz[v]);
    		siz[u]+=siz[v];
    	}
    	maxnum=max(maxnum,sum-siz[u]);
    	if(maxnum<=num) num=maxnum,cg=u;
    }
    void trave(int u,int &cr,int *R){
    	R[++cr]=u;
    	for(int i=E.head[u];i;i=E.nxt[i]){
    		if(E.ban[i]) continue;
    		trave(E.to[i],cr,R);
    	}
    }
    bool cmp(int i,int j){
    	return dis[i]-G[i]>dis[j]-G[j];
    }
    void solve(int u,int num){
    	static int R[MAXN],cr,maxnum;
    	if(num==1) return; 
    	int cg=u; maxnum=num;
    	getcg(u,cg,maxnum,num);
    	//-----------------------------准备递归u区域
    	for(int i=E.head[cg];i;i=E.nxt[i]) E.ban[i]=1;
    	solve(u,num-siz[cg]+1);
    	//-----------------------------开始解决当前层的贡献
    	cr=0; S.Reset();
    	for(int i=E.head[cg];i;i=E.nxt[i]) trave(E.to[i],cr,R);
    	sort(R+1,R+cr+1,cmp);
    	for(int i=1,j=cg,k;i<=cr;i++){
    		while(j!=fa[u]&&dis[j]>=dis[R[i]]-G[R[i]]) S.Push(j),j=fa[j];
    		k=S.Query(R[i]);
    		if(k) DP[R[i]]=min(DP[R[i]],DP[k]+(dis[R[i]]-dis[k])*P[R[i]]+Q[R[i]]);
    	}
    	//-----------------------------递归解决剩下子树区域
    	for(int i=E.head[cg];i;i=E.nxt[i])
    		solve(E.to[i],siz[E.to[i]]);
    }
    void read(ll &x){
    	static int sn; static char ch;
    	x=0; sn=1; ch=getchar();
    	while(ch<'0'||'9'<ch){if(ch=='-')sn=-1;ch=getchar();}
    	while('0'<=ch&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    	x=x*sn;
    }
    int main(){
    	scanf("%d%d",&N,&t);
    	memset(DP,0x3f,sizeof(DP));
    	DP[1]=0; ll f,s,p,q,g;
    	for(int i=2;i<=N;i++){
    		read(f); read(s); read(p); read(q); read(g);
    		fa[i]=f; P[i]=p; Q[i]=q; G[i]=g;
    		E.Adde(f,i,s);
    	}
    	dfs(1);
    	solve(1,N);
    	for(int i=2;i<=N;i++) printf("%lld
    ",DP[i]);
    	return 0;
    }
    

      

  • 相关阅读:
    DDOS攻击与防御
    .NET Core 二维码生成Demo
    腾讯云服务器远程登录卡顿严重导致无法登录解决办法
    signalR【一、官方案例】
    Python题目:生成100个随机不重复的字符串【杭州多测师】【杭州多测师_王sir】
    把字符串当中重复的字符打印出来【杭州多测师】【杭州多测师_王sir】
    PC#1 ping PC#2,请描述PC1和PC2之间的通信过程【杭州多测师】【杭州多测师_王sir】
    逻辑题【杭州多测师】【杭州多测师_王sir】
    编程题:给一个网址获取分配网址的协议,域名,路径,端口,参数1的值,参数2的值,并以字典的形式打印出结果【杭州多测师】【杭州多测师_王sir】
    经典的Python题目之找到a开头的元素、组成新的列表【多测师王sir】【杭州多测师_王sir】
  • 原文地址:https://www.cnblogs.com/zj75211/p/8168827.html
Copyright © 2011-2022 走看看