JZOJ 2019.01.25【NOIP提高组】模拟 B 组

日常吐槽:今天题目的质量很高,题目难度也适中。

3894. 改造二叉树

Description小Y在学树论时看到了有关二叉树的介绍:在计算机科学中,二叉树是每个结点最多有两个子结点的有序树。通常子结点被称作“左孩子”和“右孩子”。二叉树被用作二叉搜索树和二叉堆。随后他又和他人讨论起了二叉搜索树。
什么是二叉搜索树呢?二叉搜索树首先是一棵二叉树。设key[p]表示结点p上的数值。对于其中的每个结点p,若其存在左孩子lch,则key[p]>key[lch];若其存在右孩子rch,则key[p]<key[rch];注意,本题中的二叉搜索树应满足对于所有结点,其左子树中的key小于当前结点的key,其右子树中的key大于当前结点的key。
小Y与他人讨论的内容则是,现在给定一棵二叉树,可以任意修改结点的数值。修改一个结点的数值算作一次修改,且这个结点不能再被修改。若要将其变成一棵二叉搜索树,且任意时刻结点的数值必须是整数(可以是负整数或0),所要的最少修改次数。
相信这一定难不倒你!请帮助小Y解决这个问题吧。
Input
第一行一个正整数n表示二叉树结点数。结点从1~n进行编号。
第二行n个正整数用空格分隔开,第i个数ai表示结点i的原始数值。
此后n - 1行每行两个非负整数fa, ch,第i + 2行描述结点i + 1的父亲编号fa,以及父子关系ch,(ch = 0 表示i + 1为左儿子,ch = 1表示i + 1为右儿子)。
结点1一定是二叉树的根。
Output
仅一行包含一个整数,表示最少的修改次数。
Sample Input
3
2 2 2
1 0
1 1
Sample Output
2
Data Constraint20 % :n <= 10 , ai <= 100.
40 % :n <= 100 , ai <= 200
60 % :n <= 2000 .
100 % :n <= 10 ^ 5 ,  ai < 2 ^ 31. 

 解法

   中序遍历 + LIS(最长不下降子序列)

   这种有一颗二叉树中左右儿子和父节点权值的普遍关系大多可以用X序遍历弄成一个数列操作,类似于2018年的普及组复赛最后一题正解,这一题也要中序遍历。

   LIS的n ^ 2做法只能拿60,满分用单调队列的nlogn做法,这里有一些细节需要注意:

   1、因为点的权值必须为整数,所以需要进行一个操作,设中序遍历后的序列为a[i],则把每个a[i] - i,这样做最长不下降子序列的时候可以确保两个不连续的项中间可以修改为整数,如2 1 3 4, LIS求出只用修改一个,但实际要修改两次,所以操作后即为1 -1 0 0,正确。

   2、用最长不下降子序列而不是最长上升子序列,因为有1的操作,见举的例子。

   3、二分时最终的l指d数组中(定义和贴的博客中一样)大于新增值k的最小值位置,r指小于新增值k的最大值位置

代码

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int N = (int)1e5 + 10;
int n, v[N], son[N][2], a[N], d[N], len = 1;
void build(int u)
{
    if (!u) return;
    build(son[u][0]);
    a[++a[0]] = v[u];
    build(son[u][1]);
}
void find(int k)
{
    int l = 1, r = len;
    while (l <= r)
    {
        int mid = (l + r) / 2;
        if (d[mid] <= k)
            l = mid + 1;
        else
            r = mid - 1;
    }
    d[l] = k;
}
void lis()
{
    d[len] = a[1];
    for (int i = 2; i <= n; i++)
    {
        if (a[i] >= d[len]) d[++len] = a[i];
        else find(a[i]);
    }
}
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &v[i]);
    for (int i = 2; i <= n; i++)
    {
        int u, k;
        scanf("%d%d", &u, &k);
        son[u][k] = i;
    }
    build(1);
    for (int i = 1; i <= n; i++) a[i] -= i;
    lis();
    printf("%d\n", n - len);
    return 0;
}

LIS nlogn做法:https://www.cnblogs.com/itlqs/p/5743114.html

3895. 数字对

Description

小H是个善于思考的学生,现在她又在思考一个有关序列的问题。
她的面前浮现出一个长度为n的序列{ai},她想找出一段区间[L, R](1 <= L <= R <= n)。
这个特殊区间满足,存在一个k(L <= k <= R),并且对于任意的i(L <= i <= R),ai都能被ak整除。这样的一个特殊区间 [L, R]价值为R - L。
小H想知道序列中所有特殊区间的最大价值是多少,而有多少个这样的区间呢?这些区间又分别是哪些呢?你能帮助她吧。

Input
第一行,一个整数n.
第二行,n个整数,代表ai.
Output
第一行两个整数,num和val,表示价值最大的特殊区间的个数以及最大价值。
第二行num个整数,按升序输出每个价值最大的特殊区间的L.
Sample Input
输入1:
5
4 6 9 3 6
输入2:
5
2 3 5 7 11
Sample Output
输出1:
1 3
2
输出2:
5 0
1 2 3 4 5

Data Constraint

30%: 1 <= n <= 30 , 1 <= ai <= 32.
60%: 1 <= n <= 3000 , 1 <= ai <= 1024.
80%: 1 <= n <= 300000 , 1 <= ai <= 1048576.
100%: 1 <= n <= 500000 , 1 <= ai < 2 ^ 31.

解法

  暴力,枚举最小数,用l,r两个指针,拓展到不能拓展时最小数直接跳到r+1的位置,如果r+1的数是原最小值的因数则l指针不变,否则l = r + 1

  注意不要再加记录所有最小值的l指针然后跳来跳去的优化,会重复

代码

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int N = 500010;
int n, a[N], ans[N], l, r, k = 1, len = 0, lnum, rnum;
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    l = r = k;
    while (k <= n)
    {
        while (l > 1 && a[l - 1] % a[k] == 0) l--;
        while (r < n && a[r + 1] % a[k] == 0) r++;
        if (r - l > len)
        {
            len = r - l;
            memset(ans, 0, sizeof ans);
            ans[++ans[0]] = l;
        }
        else if (r - l == len) ans[++ans[0]] = l;
        if (r >= n) break;
        if (a[k] % a[r + 1]) l = r = r + 1;
        else r = r + 1;
        k = r;
    }
    if (!len)
    {
        printf("%d 0\n", n);
        for (int i = 1; i <= n; i++) printf("%d ", i);
        return 0;
    }
    printf("%d %d\n", ans[0], len);
    for (int i = 1; i <= ans[0]; i++) printf("%d ", ans[i]);
    return 0;
}

第三题改完后放于新博客,作为Tarjan例题

转载于:https://www.cnblogs.com/featherZHY/p/10320273.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值