HDU5956 The Elder 下凸壳的维护

题意:

一棵树,1为根。每个点只能走向祖先,时间为距离的平方,另外走到祖先后会停留P单位时间。求每个点到达根的最小时间的最大值。

 

思路:

显然dp方程为:f[i]=(d[i]-d[j])^2+P+f[j]  ( j 是 i 的祖先)

转化成向量点积形式:f[i]=d[i]^2+P+(1 , 2*d[i] ) * ( f[j]+d[j]^2 , -d[j] )

注意到深度越大,向量(1 , 2*d[i] )会逆时针旋转

于是维护点集i的所有祖先的  ( f[j]+d[j]^2 , -d[j] ) 的下凸壳,就能求出f[i]的最小值

注意到向量( f[j]+d[j]^2 , -d[j] )只会添加在下凸壳右边,于是只要用个队列来保存就可以了

查询的时候只要一直扔掉左边的点,找到最大值为止,然后退出当前dfs的时候加回来就可以了

如果一个个保存删掉的点,再一个个加回来的话,在菊花图的情况下复杂度就会变成O(n^2),事实上目前网上的题解都是这么做的,估计出题人也没意识到复杂度的问题,因为这看起来很像O(n)。

我的做法是每次dfs记录当前队列的左端点,和右端点,操作完后,事实上只是替换了数组里的tail位置的元素,把head和tail还有这个元素事先保存下来,最后恢复head,tail和这个元素就可以了,这样复杂度就是完美的O(n)了。

也不知道这种做法叫什么,毕竟自己乱想出来的,就叫回溯队列好了233

后来一想这个做法还是n方。。。因为每次head和tail的删除操作都可能是O(n)的,所以要二分查找head和tail分别应该删到哪,这样算法复杂度就是O(nlogn)了

这里如果不用斜率,而把两边的dx都乘到对面去的话是有问题的,因为f[j]+d[j]^2的规模是1e14,2*d[i]的规模是1e7,乘起来理论上会爆,然而数据好像还是没卡掉这种做法

 

代码1:O(n^2)做法

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100005;
struct P
{
	ll x,y;
	ll operator*(const P &b)const
	{
		return x*b.x+y*b.y;
	}
}stk[N];
int n,p;
int head,tail;
vector<P>link[N];
ll f[N],d[N];
long double slope(P a,P b)
{
	long double tmp=1.0;
	return tmp*(b.y-a.y)/(b.x-a.x);
}
void dfs(int u,int fa,int deep)
{
	int phead=head,ptail=tail;
	d[u]=deep;
	if (u==1) f[u]=-p;
	else
	{
		P tmp={1,2*d[u]};
		while(tail-head+1>=2&&stk[head]*tmp>=stk[head+1]*tmp) head++;
		f[u]=d[u]*d[u]+p+stk[head]*tmp;
	}
	P tmp={f[u]+d[u]*d[u],-d[u]};
	while(tail-head+1>=2&&
		slope(stk[tail-1],stk[tail])>=slope(stk[tail],tmp)) tail--;
	P mem=stk[tail+1];
	stk[++tail]=tmp;

	for (int i=0;i<link[u].size();i++)
	{
		int v=link[u][i].x;
		if (v==fa) continue;
		dfs(v,u,deep+link[u][i].y);
	}
	tail--;
	stk[tail+1]=mem;
	head=phead; tail=ptail;
}
int main()
{
	int t; scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&p);
		for (int i=1;i<n;i++)
		{
			ll x,y,z;
			scanf("%d%d%lld",&x,&y,&z);
			if (z<0) n/=0;
			link[x].push_back({y,z});
		}
		head=1; tail=0;
		dfs(1,1,0);
		ll ans=0;
		for (int i=1;i<=n;i++) ans=max(ans,f[i]);
		printf("%lld\n",ans);

		for (int i=1;i<=n;i++) link[i].clear();
	}
}

 

代码2:O(nlogn)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100005;
struct P
{
    ll x,y;
    ll operator*(const P &b)const
    {
        return x*b.x+y*b.y;
    }
}stk[N];
int n,p;
int head,tail;
vector<P>link[N];
ll f[N],d[N];
long double slope(P a,P b)
{
    long double tmp=1.0;
    return tmp*(b.y-a.y)/(b.x-a.x);
}
int gethead(P v)
{
	int l=head,r=tail;
	while(l<r)
	{
		int mid=(l+r)/2;
		if (stk[mid]*v>=stk[mid+1]*v) l=mid+1;
		else r=mid;
	}
	return l;
}
int gettail(P v)
{
	int l=head,r=tail;
	while(l<r)
	{
		int mid=(l+r)/2;
		if (slope(stk[mid],stk[mid+1])<slope(stk[mid+1],v)) l=mid+1;
		else r=mid;
	}
	return l;
}
void dfs(int u,int fa,int deep)
{
    int phead=head,ptail=tail;
    d[u]=deep;
    if (u==1) f[u]=-p;
    else
    {
        P tmp={1,2*d[u]};
        head=gethead(tmp);

        //while(tail-head+1>=2&&stk[head]*tmp>=stk[head+1]*tmp) head++;
        f[u]=d[u]*d[u]+p+stk[head]*tmp;
    }
    P tmp={f[u]+d[u]*d[u],-d[u]};
    tail=gettail(tmp);
//    while(tail-head+1>=2&&
//        slope(stk[tail-1],stk[tail])>=slope(stk[tail],tmp)) tail--;
    P mem=stk[tail+1];
    stk[++tail]=tmp;

    for (int i=0;i<link[u].size();i++)
    {
        int v=link[u][i].x;
        if (v==fa) continue;
        dfs(v,u,deep+link[u][i].y);
    }
    tail--;
    stk[tail+1]=mem;
    head=phead; tail=ptail;
}
int main()
{
    int t; scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&p);
        for (int i=1;i<n;i++)
        {
            ll x,y,z;
            scanf("%d%d%lld",&x,&y,&z);
            link[x].push_back({y,z});
        }
        head=1; tail=0;
        dfs(1,1,0);
        ll ans=0;
        for (int i=1;i<=n;i++) ans=max(ans,f[i]);
        printf("%lld\n",ans);

        for (int i=1;i<=n;i++) link[i].clear();
    }
}

hdu3043Number game(dp+dfs)

题意:给你所有1-n的波浪型排列,输出按字典序将这些序列排序后第k小的数列。

思路:可以先预处理出第i个位置是j的上波浪方案数和下波浪方案数。对于每一位,从小到大枚举j,如果当前的总方案数刚好大于等于k,那么答案数列中这个位置一定是j,如此dfs找下去。

因为输入数据有很多的t,一直WA在这个地方,测数据时也发现数据不对劲,问学长要了数据才发现这个坑。

#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<functional>
using namespace std;
const int N=25;
unsigned long long m,fu[N][N],fd[N][N];
int i,j,k,t,n;
int a[N];
void dfs(int d,long long k,int pre,int tp)
{
    if (d==0) return ;
    long long sum=0;
    int i;
    if (tp==-1)
    {
        for (i=1;i<pre;i++)
        {
            if (sum+fu[d][i]>=k) break;
            sum+=fu[d][i];
        }
    }
    else
    {
        for (i=pre;i<=d;i++)
        {
            if (sum+fd[d][i]>=k) break;
            sum+=fd[d][i];
        }
    }
    a[d]=i;
    dfs(d-1,k-sum,i,-tp);
}
int main()
{
    fd[1][1]=fu[1][1]=1;
    for (i=2;i<=20;i++)
    {
        for (j=1;j<=i;j++)
        {
            for (k=j;k<=i-1;k++) fu[i][j]+=fd[i-1][k];
            for (k=1;k<=j-1;k++) fd[i][j]+=fu[i-1][k];
        }
    }
    while(~scanf("%d",&t))
    	while(t--)
    {
    	scanf("%d%lld",&n,&m);
        if (n>250) n/=0;
        long long sum=0;
        for (i=1;i<=n;i++)
        {
            if (sum+fd[n][i]>=m)
            {
                a[n]=i;
                dfs(n-1,m-sum,i,-1);
                break;
            }
            sum+=fd[n][i];
            if (sum+fu[n][i]>=m)
            {
                a[n]=i;
                dfs(n-1,m-sum,i,1);
                break;
            }
            sum+=fu[n][i];
        }
        for (i=2;i<=n;i++)
            for (j=1;j<i;j++)
                if (a[i]<=a[j]) a[j]++;

        for (i=n;i>1;i--) printf("%d ",a[i]);
        printf("%d\n",a[1]);
    }
}

hdu5584LCM Walk(上海现场赛L题)

题意:规定LCM走法:假设现在在(x,y)点,则可以走到(x,y+lcm(x,y))或(x+lcm(x,y),y)两个点。给你一个点,问能有多少个起点走到(x,y)。

比赛的时候看到1e9根本不敢搜,后来还是学长想出了做法。

假设能从(x,y')走到(x,y),那么y'一定是y的约数,暴力枚举所有的约数判断能否走到(x,y)就好了。复杂度O(sqrt(n)*log(n)*log(n))。

#include<cstdio>
int t,x,y,ans;
int gcd(int a,int b)
{
	if (b==0) return a;
	else return gcd(b,a%b);
}
long long lcm(int a,int b)
{
	return (long long )a*b/gcd(a,b);
}
void dfs(int x,int y)
{
	ans++;
	for (int i=1;i*i<=x;i++)
		if (x%i==0)
		{
			int j=x/i;
			if (i+lcm(i,y)==x) dfs(i,y);
			if (j!=i&&j+lcm(j,y)==x) dfs(j,y);
		}
	for (int i=1;i*i<=y;i++)
		if (y%i==0)
		{
			int j=y/i;
			if (i+lcm(x,i)==y) dfs(x,i);
			if (j!=i&&j+lcm(x,j)==y) dfs(x,j);
		}
}
int main()
{
	scanf("%d",&t);
	for (int cas=1;cas<=t;cas++)
	{
		scanf("%d%d",&x,&y);
		ans=0;
		dfs(x,y);
		printf("Case #%d: %d\n",cas,ans);
	}
}

hdu5573Binary Tree(dfs)(上海现场赛B题)

这道题当时的idea是我想出来的,ACalvin学长听完我的思路后直接上,1A,印象深刻!

题意就是说从一个深度为k的完全二叉树,找一条根到叶子节点的路径,使得它们的编号能够通过加减法得到N。

我的做法是枚举叶子节点往上dfs,发现如果当前的值小于N,就必须加上当前的编号才有可能达到N,同理,如果当前的值大于N,就必须减去当前的编号才有可能达到N。

当时感觉枚举几个就会很快得到答案,心里还是有点虚的,1A的时候真是兴奋啊。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
long long a[100],b[100];
int n,k;
bool dfs(long long sum,long long now,int d)
{
	if (d==0)
	{
		if (now==0&&sum==n) return 1;
		else return 0;
	}
	if (sum<n) a[d]=now,b[d]=1,dfs(sum+now,now/2,d-1);
	else a[d]=now,b[d]=0,dfs(sum-now,now/2,d-1);
}
int main()
{
	int t;
	scanf("%d",&t);
	for (int cas=1;cas<=t;cas++)
	{
		scanf("%d%d",&n,&k);
		for (long long i=1<<(k-1);;i++)
			if (dfs(0,i,k)) break;
		printf("Case #%d:\n",cas);
		for (int i=1;i<=k;i++)
			printf("%d ",a[i]),printf(b[i]?"+\n":"-\n");

	}
}