猴王&派遣题解

来源:互联网 发布:中金公司待遇 知乎 编辑:程序博客网 时间:2024/06/10 07:42

猴王题目地址: http://fzoj.xndxfz.com/JudgeOnline/problem.php?id=1636
题目描述
很久很久以前,在一个广阔的森林里,住着n只好斗的猴子。起初,它们各干各的,互相之间也不了解。但是这并不能避免猴子们之间的争吵,当然,这只存在于两个陌生猴子之间。当两只猴子争论时,它们都会请自己最强壮的朋友来代表自己进行决斗。显然,决斗之后,这两只猴子以及它们的朋友就互相了解了,这些猴子之间将再也不会发生争论了,即使它们曾经发生过冲突。
假设每一只猴子都有一个强壮值,每次决斗后都会减少一半(比如10会变成5,5会变成2.5)。并且我们假设每只猴子都很了解自己。就是说,当它属于所有朋友中最强壮的一个时,它自己会站出来,走向决斗场。

输入
输入分为两部分。
第一部分,第一行有一个整数n(n<=100000),代表猴子总数。
接下来的n行,每行一个数表示每只猴子的强壮值(小于等于32768)。
第二部分,第一行有一个整数m(m<=100000),表示有m次冲突会发生。
接下来的m行,每行包含两个数x和y,代表第x个猴子和第y个猴子之间发生冲突。

输出
输出每次决斗后在它们所有朋友中的最大强壮值。数据保证所有猴子决斗前彼此不认识。

样例输入
5
20
16
10
10
4
4
2 3
3 4
3 5
1 5
样例输出
8
5
5
10

题解如下
这是一道左偏树的裸题,我们一开始可以把每个猴子看作一颗独立的以它自己为根节点的左偏树。有两个猴子打架就将这两个猴子所在的左偏树的左右子树合并成一颗左偏树,再将两颗树的树根的值除以2,再合并入左右子树所合并的左偏树中去。其实一开始我是想用堆(二叉堆)的,但是堆虽然方便查找这个堆中的最大元素,但是不方便将两个堆进行合并。如果要强行合并只有将其中一个堆中的元素一个弹出,再插入到另一个堆中,时间复杂度太高,而本题需要频繁进行堆的合并操作,用堆不合适。

#include<cstdio>#include<cstring>#include<algorithm>#include<iostream>using namespace std;struct node{    int data,ls,rs,fa;}f[100005];struct mon{    int a1,a2;}c[100005]; int n,m,dis[100005];void init()//初始化&读入数据 {    memset(dis,0,sizeof(dis));    memset(c,0,sizeof(c));    memset(f,0,sizeof(f));    int i;    scanf("%d",&n);    for(i=1;i<=n;i++)    {        scanf("%d",&f[i].data);        f[i].fa=i;    }    scanf("%d",&m);    for(i=1;i<=m;i++)    {        scanf("%d%d",&c[i].a1,&c[i].a2);    }    return ;}int merge(int a,int b)//将分别以a,b为根节点的左偏树进行合并 {    if(a==0) return b;    if(b==0) return a;    if(f[a].data<f[b].data) swap(f[a],f[b]);// 因为最后返回的值是合并后的树的根节点,所以要保证树根的值最大     f[a].rs=merge(f[a].rs,b); f[f[a].rs].fa=a;    if(dis[f[a].ls]<dis[f[a].rs]) swap(f[a].ls,f[a].rs);//维护左右孩子节点,使左儿子的dis值大于右儿子的dis值     if(f[a].rs==0)    {        dis[a]=0;    }    else    {        dis[a]=dis[f[a].rs]+1;    }    return a;}int findfa(int k)//寻找k所在左偏树的根节点 {    if(f[k].fa==k)    {        return f[k].fa;    }    else    {        f[k].fa=findfa(f[k].fa);        return f[k].fa;    }}int monkey(int x,int y)//合并两棵左偏树,进行上面题解所说的操作 {    int xx=findfa(x),yy=findfa(y);    f[xx].data/=2; f[yy].data/=2;//树的根节点的值除以2(两个最强壮的猴子的强壮值减半)    int temp1=merge(f[xx].ls,f[xx].rs);//合并第一棵左偏树的左子树和右子树    f[xx].ls=0; f[xx].rs=0;    int temp2=merge(f[yy].ls,f[yy].rs);//合并第二棵左偏树的左子树和右子树    f[yy].ls=0; f[yy].rs=0;    int temp3=merge(temp1,temp2);//将两棵合并好的左偏树再次合并    int temp4=merge(xx,yy);//将两个根节点看作两棵独立的子树进行合并    int temp5=merge(temp3,temp4);//最后合并成一棵左偏树    f[xx].fa=f[yy].fa=f[x].fa=f[y].fa=f[temp3].fa=f[temp4].fa=f[temp1].fa=f[temp2].fa=temp5;    return f[temp5].data;}int main(){    init();    int i;    for(i=1;i<=m;i++)    {        printf("%d\n",monkey(c[i].a1,c[i].a2));    }    return 0;}

派遣题目地址: http://fzoj.xndxfz.com/JudgeOnline/problem.php?id=1748
题目描述

在一个忍者的帮派里,一些忍者们被选中派遣给顾客,然后依据自己的工作获取报偿。在这个帮派里,有一名忍者被称之为 Master。除了 Master以外,每名忍者都有且仅有一个上级。为保密,同时增强忍者们的领导力,所有与他们工作相关的指令总是由上级发送给他的直接下属,而不允许通过其他的方式发送。现在你要招募一批忍者,并把它们派遣给顾客。你需要为每个被派遣的忍者 支付一定的薪水,同时使得支付的薪水总额不超过你的预算。另外,为了发送指令,你需要选择一名忍者作为管理者,要求这个管理者可以向所有被派遣的忍者 发送指令,在发送指令时,任何忍者(不管是否被派遣)都可以作为消息的传递 人。管理者自己可以被派遣,也可以不被派遣。当然,如果管理者没有被排遣,就不需要支付管理者的薪水。你的目标是在预算内使顾客的满意度最大。这里定义顾客的满意度为派遣的忍者总数乘以管理者的领导力水平,其中每个忍者的领导力水平也是一定的。写一个程序,给定每一个忍者 i的上级 Bi,薪水Ci,领导力L i,以及支付给忍者们的薪水总预算 M,输出在预算内满足上述要求时顾客满意度的最大值。

1 ≤N ≤ 100,000 忍者的个数;

1 ≤M ≤ 1,000,000,000 薪水总预算;

0 ≤Bi < i 忍者的上级的编号;

1 ≤Ci ≤ M 忍者的薪水;

1 ≤Li ≤ 1,000,000,000 忍者的领导力水平。

输入
从标准输入读数据 。
第一行 包含 两个整数 N和 M,其中 N表示忍者的个数, 表示忍者的个数, M表示薪水的总预 算。
接下来 N行描述忍者们的 上级、 薪水以及领导力。 其中的 第 i行包含三个整 数 Bi , Ci , Li分别表示第 分别表示第 i个忍者的 上级 ,薪水以及领导力。 Master Master满足 Bi = 0, 并且每一个忍者的老板编号 定小于自己Bi < i。

输出
输出 一个数,表示在预算内顾客的满意度的最大值。

样例输入
5 4
0 3 3
1 3 5
2 2 2
1 2 4
2 3 1
样例输出
6

题解如下
这依旧是一道左偏树的题,最开始将每个忍者看作一个节点,可以用邻接表的方式存储每个节点的从属关系。接下来从序号为1的节点开始建树(因为题目所说每个节点都满足其父节点的序号小于这个节点的序号所以不难推测出1就是Master Master)。建树的时候记录该节点所管理的节点数和该节点所管理的忍者的费用总和,每建好一棵树就维护一下费用总和,如果费用总和超过了最大可使用的费用就每次删除树根的元素(因为树根元素的费用最大),直到费用总和不超过最大可使用的费用。然后就一层一层递归上去。

#include<cstdio>#include<cstring>#include<algorithm>#include<iostream>using namespace std;typedef long long ll;struct edge{    int son,next;}e[1000005];struct node{    int data,ls,rs,dis;}f[1000005];ll sum[1000005],ans=0,l[1000005];int n,m,c[1000005],first[1000005],last=0,cont[1000005];void addedge(int a,int b){    last++;    e[last].son=b;    e[last].next=first[a];    first[a]=last;    return ;}void init()//初始化&输入&处理数据 {    memset(c,0,sizeof(c));    memset(l,0,sizeof(l));    memset(first,0,sizeof(first));    memset(sum,0,sizeof(sum));    memset(cont,0,sizeof(cont));    scanf("%d%d",&n,&m);    int i,k;    for(i=1;i<=n;i++)    {        scanf("%d%d%lld",&k,&c[i],&l[i]);        f[i].data=c[i];        if(k) addedge(k,i);//用邻接表存储从属关系     }    return ;}int merge(int a,int b)//合并两棵树 {    if(a==0) return b;    if(b==0) return a;    if(c[a]<c[b]) swap(a,b);    f[a].rs=merge(f[a].rs,b);    if(f[f[a].ls].dis<f[f[a].rs].dis) swap(f[a].ls,f[a].rs);    f[a].dis=f[f[a].rs].dis+1;    sum[a]=sum[f[a].ls]+sum[f[a].rs]+c[a];//求根节点所代表的左偏树的费用总和     cont[a]=cont[f[a].ls]+cont[f[a].rs]+1;//求出根节点的领导力     return a;}int work(int k){    sum[k]=c[k]; cont[k]=1;    int i,t=k;    for(i=first[k];i!=0;i=e[i].next)//按照邻接表所记录的从属关系建树     {        t=merge(t,work(e[i].son));    }    while(sum[t]>m)//维护费用总和直到费用总和小于最大可使用费用     {        t=merge(f[t].ls,f[t].rs);//每次都删除根节点     }    ans=max(ans,1ll*cont[t]*l[k]);    return t;}int main(){    init();    f[0].dis=-1;    work(1);    printf("%lld\n",ans);    return 0;}
原创粉丝点击