NOI 2014 购票 (树上各种科技)

版权声明:本文为博主原创文章,未经博主允许必须转载。 https://blog.csdn.net/qq_35950004/article/details/88253384

以前看着这个芳菲菲其弥章的题面以为这题又是什么数据结构神题。。。。。。
其实就是 加点横坐标单调但查询斜率不单调的斜率优化 + 时间限制 + 树上查询。
解法1(套路解法):
因为时间限制,我们用线段树维护每个小区间的凸包,最后求个最小值就行。
因为树上查询所以树链剖分。
因为这些操作常数都不是很大,时间复杂度分析出来也没啥意思。。。。
AC Code:

#include<bits/stdc++.h>
#define maxn 200005
#define inf 0x3f3f3f3f3f3f3f3f
#define LL long long
using namespace std;

char cb[1<<15],*cs=cb,*ct=cb;
#define getc() (cs==ct && (ct=(cs=cb)+fread(cb,1,1<<15,stdin),cs==ct)?0:*cs++)
template<class T>inline void read(T &res)
{
	char ch;
	for(;!isdigit(ch=getc()););
	for(res=ch-'0';isdigit(ch=getc());res=ch-'0'+res*10);
}

int n,t;
int info[maxn],Prev[maxn],to[maxn],cnt_e;
LL cst[maxn];
inline void Node(int u,int v,LL ct){ Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v,cst[cnt_e]=ct; }

LL dep[maxn],p[maxn],q[maxn],l[maxn];
int fa[maxn],tp[maxn],id[maxn],bl[maxn],siz[maxn],son[maxn],tot,cnt_l;

void dfs1(int now)
{
	siz[now] = 1 , son[now] = -1;
	for(int i=info[now];i;i=Prev[i])
	{
		dep[to[i]] = dep[fa[to[i]] = now] + cst[i];
		dfs1(to[i]);
		siz[now] += siz[to[i]];
		if(son[now] == -1 || (siz[son[now]] < siz[to[i]])) 
			son[now] = to[i];
	}
}

void dfs2(int now)
{
	id[now] = ++tot;
	if(son[now]!=-1) bl[son[now]] = bl[now] , dfs2(son[now]);
	for(int i=info[now];i;i=Prev[i])
		if(to[i]!=son[now])
		{
			tp[bl[to[i]] = ++cnt_l] = to[i];
			dfs2(to[i]);
		}
}

struct Point
{
	LL x,y;
	Point(LL x=0,LL y=0):x(x),y(y){}
	double operator *(const Point &B)const
	{
		return (B.y-y) * 1.0 / (B.x-x);
	}
};

bool flag = 0;

vector<Point>pt[maxn<<2];

LL solveseg(int now,LL K)
{
	vector<Point>&p = pt[now];	
	if(p.size() == 0) return inf;
	if(p.size() == 1) return -K*p[0].x+p[0].y;
	int L = 1 , R = p.size()-1 , mid; // mid point
	for(;L<R;)
	{
		mid = (L+R+1) >> 1;
		if(p[mid]*p[mid-1] < K) L = mid;
		else R = mid-1;
	}
	if(p[L]*p[L-1] >= K) L--;
	return -K*p[L].x+p[L].y;
}
 
LL solvesegs(int now,int l,int r,int ql,int qr,LL K)
{
	if(ql > r || qr < l) return inf;
	if(ql <= l && r <= qr)
	{
		return solveseg(now,K);
	}
	int mid = (l+r) >> 1;
	return min(solvesegs(now<<1,l,mid,ql,qr,K),solvesegs(now<<1|1,mid+1,r,ql,qr,K));
}

LL solvepath(int now,LL K,LL lim)
{
	if(!now) return inf;
	if(dep[now] < lim) return inf;
	if(dep[tp[bl[now]]]<lim)
	{
		int L=id[tp[bl[now]]],R = id[now],mid;
		for(;L<R;)
		{
			mid = (L+R) >> 1;
			if(dep[mid]<lim)L=mid+1;
			else R=mid;
		}
		return solvesegs(1,1,n,L,id[now],K);
	}
	return min(solvesegs(1,1,n,id[tp[bl[now]]],id[now],K),solvepath(fa[tp[bl[now]]],K,lim));
}
void insertseg(int now,Point pts)
{
	vector<Point>&p = pt[now];
	for(int siz=p.size();siz>=2 && pts*p[siz-1]<=p[siz-1]*p[siz-2];p.pop_back(),siz--);
	p.push_back(pts);
}
void insertsegs(int now,int l,int r,int pos,Point pts)
{
	insertseg(now,pts);
	if(l==r) return;
	int mid = (l+r) >> 1;
	if(pos <= mid)insertsegs(now<<1,l,mid,pos,pts);
	else insertsegs(now<<1|1,mid+1,r,pos,pts);
}

LL dp[maxn];
void solve(int now)
{ 
	if(now == 8)
	flag = 1;
	if(now > 1)
	{
		dp[now] = solvepath(fa[now],p[now],dep[now]-l[now])+q[now]+dep[now]*p[now];
	}
	insertsegs(1,1,n,id[now],Point(dep[now],dp[now]));
	if(now == 8) 
	flag = 0;
	for(int i=info[now];i;i=Prev[i])
		solve(to[i]);
}

int main()
{
	read(n),read(t);
	for(int i=2;i<=n;i++)
	{
		int f;LL s;
		read(f),read(s),read(p[i]),read(q[i]),read(l[i]);
		Node(f,i,s);
	}
	
	dfs1(1),tp[bl[1]=++cnt_l]=1,dfs2(1);
	
	solve(1);
	for(int i=2;i<=n;i++)
		printf("%lld\n",dp[i]);
}

解法2:因为是树链问题,我们可以点分治(类比序列上将序列分成两半的CDQ分治)然后贡献,注意要让每段树链分成 log n \log n 条点分树上的树链,然后每条树链对可以贡献的点算贡献,取个Min就行。
AC Code:

#include<bits/stdc++.h>
#define maxn 400005
#define LL long long
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;

char cb[1<<15],*cs=cb,*ct=cb;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<15,stdin),cs==ct) ? 0 : *cs++)
template<class T>inline void read(T &res)
{
	char ch;
	for(;!isdigit(ch=getc()););
	for(res=ch-'0';isdigit(ch=getc());res=res*10+ch-'0');
}

int n,t;
int fa[maxn],info[maxn],Prev[maxn<<1],to[maxn<<1],cnt_e;
LL p[maxn],q[maxn],l[maxn],cst[maxn],disfa[maxn],ans[maxn],dep[maxn];

inline void Node(int u,int v,LL ct){ Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v,cst[cnt_e]=ct; }

bool vis[maxn],usd[maxn];
int siz[maxn],Min,rt;

bool flag = 0;

void frt(int now,int ff,int tsz)
{
	int Max = tsz - siz[now];
	for(int i=info[now];i;i=Prev[i])
		if(!vis[to[i]] && to[i]!=ff)
			frt(to[i],now,tsz) , Max = max(Max , siz[to[i]]);
	if(rt == -1 || Max < Min)
		rt = now , Min = Max;
}

int findrt(int now,int tsz)
{
	Min = 0x3f3f3f3f , rt = -1;
	frt(now,0,tsz);
	return rt;
}

void ser1(int now,int ff)
{
	siz[now] = 1;
	for(int i=info[now];i;i=Prev[i])
		if(!vis[to[i]] && to[i]!=ff)
		{
			ser1(to[i],now);
			siz[now] += siz[to[i]];
		}
}

int sq[maxn];
LL dis[maxn];

void ser2(int now,int ff,LL dist)
{
	sq[++sq[0]] = now , dis[now] = l[now] - dist , siz[now] = 1;
	for(int i=info[now];i;i=Prev[i])
		if(to[i]!=ff && !vis[to[i]])
			ser2(to[i],now,dist+cst[i]),
			siz[now] += siz[to[i]];
}

inline bool cmp(const int &u,const int &v){ return dis[u] < dis[v]; }

struct Point 
{
	LL x,y;
	Point(LL x=0,LL y=0):x(x),y(y){}
	double operator *(const Point &B)const
	{
		return (B.y-y) * 1.0 / (B.x-x);
	}
};

vector<Point>pt;

void Insert(Point pts)
{
	for(int siz=pt.size();siz>=2 && pts*pt[siz-1]>=pt[siz-1]*pt[siz-2];pt.pop_back(),siz--);
	pt.push_back(pts);
}

LL Query(LL K)
{
	if(pt.size() == 0) return inf;
	if(pt.size() == 1) return pt[0].y - K * pt[0].x;
	int L = 1 , R = pt.size()-1 , mid;
	for(;L<R;)
	{
		mid = (L+R+1) >> 1;
		if(pt[mid] * pt[mid-1] >= K) L = mid;
		else R = mid - 1;
	}
	if(pt[L] * pt[L-1] <= K) L--;
	return pt[L].y - K * pt[L].x;
}

void Solve(int now,int lim)
{
	flag = 0;
	vis[now] = 1;
	if(fa[now] && !vis[fa[now]])
	{
		ser1(fa[now],0);
		Solve(findrt(fa[now],siz[fa[now]]),lim);
	}
	sq[0] = 0;
	ser2(now,0,0);
	sort(sq+1,sq+1+sq[0],cmp);
	for(int i=1,j=fa[now];i<=sq[0];i++)
	{
		int u = sq[i];
		for(;j!=lim && dep[now]-dep[j]<=dis[u];j=fa[j])
		{
			Insert(Point(dep[j],ans[j]));
		}
		ans[u] = min(ans[u] , Query(p[u])+q[u]+dep[u]*p[u]);
	}
	pt.clear();
	for(int i=info[now];i;i=Prev[i])
		if(!vis[to[i]])
		{
			Solve(findrt(to[i],siz[to[i]]),fa[now]);
		}
}

void dfs(int now,int ff)
{
	siz[now] = 1;
	for(int i=info[now];i;i=Prev[i])
		if(to[i]!=ff)
		{
			dep[to[i]] = dep[now] + cst[i];
			dfs(to[i],now);
			siz[now] += siz[to[i]];
		}
}

int main()
{
	read(n),read(t);
	for(int i=2;i<=n;i++)
	{
		int f;LL s;
		read(f),read(s),read(p[i]),read(q[i]),read(l[i]);
		Node(f,i,s),Node(i,f,s);
		fa[i] = f , disfa[i] = s;
	}
	
	memset(ans,0x3f,sizeof ans);
	ans[1] = 0;
	dfs(1,0);
	flag = 1;
	Solve(findrt(1,n),0);
	
	for(int i=2;i<=n;i++)
	printf("%lld\n",ans[i]);
}

听说还可以二进制分组。。。。。。

猜你喜欢

转载自blog.csdn.net/qq_35950004/article/details/88253384