NOI2014购票(树的cdq分治+凸壳)

题目链接:

http://uoj.ac/problem/7

 

思路:

如果没有距离限制L的话只要dfs一遍同时维护到根的凸壳就可以(二分位置后可以做到O(1)添加,O(1)恢复)。

这题有个距离限制的话就可以将重心下面的点按d[v]-l[v]排序后,与重心到根的点一起双指针更新。

 

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=200005;
struct E
{
	int v; ll z;
};
vector<E>link[N];
ll d[N],p[N],q[N],l[N],f[N];
int fa[N];

struct P
{
	ll d,f;
	ll operator*(const P &b)const
	{
		return d*b.d+f*b.f;
	}
}stk[N];
int top;
long double slope(P a,P b)
{
    long double tmp=1.0;
    return tmp*(b.f-a.f)/(b.d-a.d);
}
void add(P p)
{
	if (top<=1)
	{
		stk[top++]=p;
		return;
	}
	while(top>=2&&slope(p,stk[top-1])>slope(stk[top-1],stk[top-2])) top--;
	stk[top++]=p;
}
void upd(int id)
{
	P vec={-p[id],1};
	int l=0,r=top-1;
	while(l<r)
	{
		int mid=(l+r)/2;
		if (vec*stk[mid]>vec*stk[mid+1]) l=mid+1;
		else r=mid;
	}
	f[id]=min(f[id],p[id]*d[id]+q[id]+vec*stk[l]);
}

int cent,mxsz;
int size[N];
int vis[N];
void findcent(int u,int n)
{
	size[u]=1;
	int mx=0;
	for (E e:link[u])
	{
		int v=e.v;
		if (vis[v]) continue;
		d[v]=d[u]+e.z;
		findcent(v,n);
		size[u]+=size[v];
		mx=max(mx,size[v]);
	}
	mx=max(mx,n-size[u]);
	if (mx<=mxsz) cent=u,mxsz=mx;
}

int nd[N],tot;
void add(int u)
{
	nd[tot++]=u;
	for (E e:link[u])
		if (!vis[e.v]) add(e.v);
}
bool cmp(int i,int j)
{
	return d[i]-l[i]>d[j]-l[j];
}
void solve(int u,int n)
{
	if (n==1) return;
	cent=u;  mxsz=n;
	findcent(u,n);
	int rt=cent;
	for (E e:link[rt]) vis[e.v]++;
	if (u==4&&n==2)
		u=4;
	solve(u,n-size[rt]+1);

	for (E e:link[rt]) vis[e.v]--;
	for (E e:link[rt]) add(e.v);
	sort(nd,nd+tot,cmp);
	int now=rt;
	for (int i=0;i<tot;i++)
	{
		int id=nd[i];
		while(now!=fa[u]&&d[now]>=d[id]-l[id]) add({d[now],f[now]}),now=fa[now];
		if (top) upd(id);
	}
	tot=0; top=0;
	for (E e:link[rt]) solve(e.v,size[e.v]);
}
int main()
{
	int n,t; scanf("%d%d",&n,&t);
	for (int i=2;i<=n;i++)
	{
		f[i]=1ll<<62;
		ll s; scanf("%d%lld",&fa[i],&s);
		link[fa[i]].push_back({i,s});
		scanf("%lld%lld%lld",&p[i],&q[i],&l[i]);
	}
	solve(1,n);
	for (int i=2;i<=n;i++) printf("%lld\n",f[i]);
}

 

Codeforces Packmen Strike Back (二分+dp)

题目链接:

http://codeforces.com/contest/883/problem/D

题意:

给一个字符串,P代表人,每个人往左走到底或者往右走到底,问最多获得多少星号,在这个前提下最少走多少秒收集完。

 

思路:

二分答案,check的时候看前i个人最多可以收集到哪个位置。

转移有三种情况

1. 第i个人往左走,但无法收集完第i-1个人收集的星号,无法让i-1转向

2. 第i个人往左走,可以收集完第i-1个人收集的星号,可以让i-1转向

3. 第i个人往右走,左边都收集完

 

思维难度还是有点大

#include<bits/stdc++.h>
using namespace std;

const int N=1000005;

char s[N];
int sum[N],a[N];
int tot;
int R[N];
int n;

bool have(int r,int l)
{
	l=max(0,l);
	if (l>=r) return 0;
	return sum[r]-sum[l]>0;
}

int f[N];
bool check(int len)
{
	f[0]=0; a[0]=0;
	for (int i=1;i<=tot;i++)
	{
		f[i]=0;
		if (!have(a[i]-len-1,f[i-1])) f[i]=a[i];
		if (i>=2&&!have(a[i]-len-1,f[i-2])) f[i]=max(f[i],a[i-1]+len);
		if (!have(a[i],f[i-1])) f[i]=max(f[i],a[i]+len);
		if (f[i]==0) return 0;
	}
	if (have(n,f[tot])) return 0;
	return 1;
}
int main()
{
	scanf("%d",&n);
	scanf("%s",s+1);
	for (int i=1;i<=n;i++)
	{
		sum[i]=sum[i-1];
		if (s[i]=='*') sum[i]++;
		if (s[i]=='P') a[++tot]=i;
	}
	if (tot==0) printf("0 0");
	else
	{
		if (tot==1)
		{
			int cnt1=0,cnt2=0,dl=0,dr=0;
			for (int i=1;i<=n;i++)
				if (s[i]=='*')
				{
					if (i<a[1]) cnt1++,dl=max(dl,a[1]-i);
					else cnt2++,dr=max(dr,i-a[1]);
				}
			if (cnt1>cnt2) printf("%d %d",cnt1,dl);
			if (cnt1<cnt2) printf("%d %d",cnt2,dr);
			if (cnt1==cnt2) printf("%d %d",cnt1,min(dl,dr));
		}
		else
		{
			int cnt=0;
			for (int i=1;i<=n;i++)
				if (s[i]=='*') cnt++;
			R[n+1]=n+1;
			for (int i=n;i>=1;i--)
				if (s[i]=='*') R[i]=i;
				else R[i]=R[i+1];
			int l=0,r=n;
			while(l<r)
			{
				int mid=(l+r)/2;
				if (!check(mid)) l=mid+1;
				else r=mid;
			}
			printf("%d %d",cnt,l);
		}
	}
}