NOIP2017提高组模拟赛5 (总结)

NOIP2017提高组模拟赛5 (总结)

第一题 最远

    奶牛们想建立一个新的城市.它们想建立一条长度为N (1 <= N <= 1,000,000)的 主线大街,然后建立K条 (2 <= K <= 50,000)小街, 每条小街的尽头有一间房子(小街的其它位置没有房子).每条小街在主线大街的P_i 处分支,(0 <= P_i <= N) , 小街的长度是 L_i (1 <= L_i <= 1,000,000).FJ想知道最远的两个房子之间的距离是多少。

  其实就是将一个点F拆成向左L向右R的两个点,然后将所有点排序(不用排序也行,直接找),①选第一个L,从后往前选第一个不与L同一个F的R。②选最后一个R,从前往后选第一个不与R同一个F的L。①②答案中的最大值即为两点之间的最远距离。
证明如下:
这里写图片描述

#include<cstdio>
#include<algorithm>
#include<cmath>

#define imax(a,b) ((a>b)?(a):(b))

typedef long long ll;

using namespace std;

const int N=120000;
int n,k,dt,ans;
struct data{ int ti,wi; } d[N];

void add(int len,int we) { d[++dt].ti=len; d[dt].wi=we; }

bool cmp(data A,data B) { return (A.ti<B.ti); }

int main()
{
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
    scanf("%d%d",&n,&k);
    dt=0; ans=0;
    for(int i=1;i<=k;i++)
    {
        int a,b; scanf("%d%d",&a,&b);
        add(a+b,i); add(a-b,i);
    }
    sort(d+1,d+1+dt,cmp);
    for(int i=dt;i>1;i--)
    if(d[i].wi!=d[1].wi)
    {
        ans=imax(ans,d[i].ti-d[1].ti);
        break;
    }
    for(int i=1;i<dt;i++)
    if(d[i].wi!=d[dt].wi)
    {
        ans=imax(ans,d[dt].ti-d[i].ti);
        break;
    }
    printf("%d\n",ans);
    return 0;
}

第二题 01游戏

    有一种游戏, 刚开始有A个0和B个1. 你的目标是最后变成A+B个1. 每一次,你选中任意K个数字, 把他们的值取反(原来是0的变1, 原来是1的变0).请问至少需要多少次才能达到目标?假如不可能达到目标,就输出-1.

数学方法:
  把01看成A+B个点。
  设S=A+B,X=最少要翻转次数,ti=点i翻转两次的次数。
  ①\((XK-A)/2=\sum_{i=1}^n ti\) ∴XK-A≥0且为偶数
  ②假设各点ti达到最大。
  点i为1则ti=X/2
  点i为0则ti=(X-1)/2
  得到\((XK-A)/2≤A*((X-1)/2)+B*(X/2)\)
答案不超过S,枚举判断是否符合①②条件,符合则可行。找不到的输出-1。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>

typedef long long ll;

using namespace std;

int ng;
long long A,B,K,S;

int main()
{
    freopen("b.in","r",stdin);
    freopen("b.out","w",stdout);
    scanf("%d",&ng);
    while(ng--)
    {
        scanf("%lld%lld%lld",&A,&B,&K);
        S=A+B; long long ans;
        for(ans=0;ans<=S;ans++)
        if(A==0 || ((K*ans-A)>=0 && ((K*ans-A)&1)==0 && (K*ans-A)/2<=A*((ans-1)/2)+B*(ans/2))) break;
        if(ans<=S) printf("%lld\n",ans); else printf("-1\n");
    }
    return 0;
}

第三题 bst计数

    相信大家对二叉查找树都很熟悉了,现在给你N个整数的序列,每个整数都在区间[1,N]内,且不重复。现在要你按照给定序列的顺序,建立一个二叉查找树,把第一整数作为根,然后依次插入后面的整数。
    每个结点X的插入过程其实就是模拟下面的 insert(X, root)过程:
insert( number X, node N )
{
    increase the counter  C by  1  //每次进来都会使C加1
    if X is less than the numberin node N //如果X小于结点N的值
    {
    if  N has no left child //N没有左孩子把X作为N左孩子
    create a new node with thenumber X and set it to be the left child of node N
    else  insert(X, left child of node N)//递归,从N左孩子插入
    }
    else (X is greater than thenumber in node N)
    {
    if N has no right child
    create a new node with thenumber X and set it to be the right child of node N
    else
    insert(X, right child of nodeN)
    }
}
    你要求的是:每次把序列的一个整数插入到二叉查找数后,当目前为止计数累加器C的值是多少?请把它输出。注意:第一次插入根,计数器C的值是0,你可以理解为插入根是不执行insert()操作的,其后每插入一个结点,C都类加,也就是每次进入过程insert( number X, node N ),都会执行increase the counter  C by  1,使得C不断增大。

  C每次累加的其实是插入X后,X的深度。
  可以得知,depX=depXFather+1。
  如何找出X的父亲呢?
  根据排序二叉树的性质,二叉排序树的中序遍历的序列有序。假设插入X后,在二叉树的中序遍历中,X与XFather一定是相邻的。
  那么与X相邻的有两个,哪一个才是XFather?其实比较一下两点插入的时间,时间晚的一定是XFather。与X相邻表示一个是XFather,另一个一定不在XFather这棵子树中(如果在,则一定为X的儿子,但还没插入,仅此一种情况)而是XFather的Father(由性质可得),所以时间晚的就是XFather。
  直接做可以用线段树或splay过掉。
  但还有一种更优的做法。
  逆向思维,从后往前做,对于点i,1->i的序列是有序的,直接可得距离i最近的两个点,求出iFather。然后将i点删除,仍需维护1->i-1有序。可以用链表,删除点i等于
  R[L[i]]=R[i]; L[R[i]]=L[i];

#include<cstdio>
#include<algorithm>
#include<cmath>

#define imax(a,b) ((a>b)?(a):(b))

typedef long long ll;

using namespace std;

const int N=301000;
int n,d[N+10],tn[N+10],L[N+10],R[N+10];
int son[N+10][5],f[N+10],fa[N+10];
long long ans;

int main()
{
    freopen("c.in","r",stdin);
    freopen("c.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&d[i]),tn[d[i]]=i;
    for(int i=1;i<=n;i++) L[i]=tn[d[i]-1],R[i]=tn[d[i]+1];
    L[0]=0; R[0]=tn[1];
    L[n+1]=tn[n]; R[n+1]=n+1; 
    for(int i=n;i>=1;i--)
    {
        int ll=L[i],rr=R[i];
        int yu=imax(ll,rr);
        fa[i]=yu;
        R[ll]=rr; L[rr]=ll;
    }
    ans=0ll; f[1]=0;
    for(int i=2;i<=n;i++) f[i]=f[fa[i]]+1;
    for(int i=1;i<=n;i++) printf("%lld\n",(ans+=f[i]));
    return 0;
}

转载于:https://www.cnblogs.com/kekxy/p/7526115.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值