2021牛客暑期多校训练营9

2021牛客暑期多校训练营9

E-Eyjafjalla

思路

以点1为根,可以发现这棵树有这样的性质:深度越深,温度越低。所以我们从当前点往根节点走,一直走到父亲节点的温度不在 [ l , r ] [l, r] [l,r] 区间内时停下,此时所在的节点就是病毒能够扩散到的深度最低的节点(这一步可以用倍增来做)。

以这个节点为根节点的子树包含了所有被感染的节点,也就是说这颗子树外的节点不可能被感染。被感染的条件是温度在区间 [ l , r ] [l, r] [l,r]​​ 之间,所以此时问题就转化为了找出这颗子树下有多少温度大于等于 l l l​​ 的节点,而主席树的基本操作就是统计某一权值区间内有多少节点,由此我们可以结合dfs序(或者树剖)将树上操作转化为区间操作,于是用倍增+主席树(dfs序建树)就能AC辽~

代码

比赛的时候觉得这用树剖做不是易如反掌,然后赛后写了一天写不出来,最后还是和队友一个方法做了qwq(队友yyds

#include <bits/stdc++.h>

using namespace std;

const int N = 4e5 + 10, M = 1e5 + 10;

int n, q;
int a[N], tot, dfstot;
vector<int> nums;
int head[N], es[N * 2], nxt[N], dfsl[N], dfsr[N], par[N][30];

struct query //询问
{
    int x, l, r;
}qs[N];

struct Node
{
    int l, r;
    int cnt;
} tr[N * 4 + M * 35];

int root[N], idx;

//离散化后找节点位置 -- l = find(l)
int find(int x)
{
    return lower_bound(nums.begin(), nums.end(), x) - nums.begin();
}

//建树 -- root[0] = build(0, num.size() - 1)
int build(int l, int r)
{
    int p = ++ idx;
    if (l == r)
        return p;
    int mid = l + r >> 1;
    tr[p].l = build(l, mid), tr[p].r = build(mid + 1, r);
    return p;
}

//修改 -- root[i] = insert(root[i - 1], 0, nums.size() - 1, find(a[x]));
// i:当前是第i个版本 x:要修改的值的位置
int insert(int p, int l, int r, int x)
{
    int q = ++ idx;
    tr[q] = tr[p];
    if (l == r)
    {
        tr[q].cnt ++ ;
        return q;
    }
    int mid = l + r >> 1;
    if (x <= mid)
        tr[q].l = insert(tr[p].l, l, mid, x);
    else
        tr[q].r = insert(tr[p].r, mid + 1, r, x);
    tr[q].cnt = tr[tr[q].l].cnt + tr[tr[q].r].cnt;
    return q;
}

//查询x到y区间中范围在[s, t]之间的节点个数
//调用 query(root[x], root[y], s, t, 0, nums.size() - 1) 获得个数
int query(int x, int y, int s, int t, int l, int r)
{
    if (s <= l && r <= t)
    {
        return tr[y].cnt - tr[x].cnt;
    }

    int mid = (l + r) >> 1;
    int res = 0;
    if (s <= mid)
    {
        res += query(tr[x].l, tr[y].l, s, t, l, mid);
    }
    if (mid < t)
    {
        res += query(tr[x].r, tr[y].r, s, t, mid + 1, r);
    }
    return res;
}

//查询p到q区间中的第k大数
//调用 query(root[r], root[l - 1], 0, nums.size() - 1, k) 获得其数组位置
int query(int q, int p, int l, int r, int k)
{
    if (l == r) return r;
    int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt;
    int mid = l + r >> 1;
    if (k <= cnt) return query(tr[q].l, tr[p].l, l, mid, k);
    else return query(tr[q].r, tr[p].r, mid + 1, r, k - cnt);
}

void addEdge(int u, int v)
{
    es[++idx] = v;
    nxt[idx] = head[u];
    head[u] = idx;
}

//边dfs边建树
void dfs(int u, int fa)
{
    dfsl[u] = ++dfstot; //dfsl维护了这颗子树dfs序的最小值
    root[tot + 1] = insert(root[tot], 0, nums.size() - 1, find(a[u])); //建树
    tot++; //第tot个版本
    par[u][0] = fa;
    for(int i = 1; i < 20; i++)
        par[u][i] = par[par[u][i - 1]][i - 1];//倍增
    
    for(int i = head[u]; ~i; i = nxt[i])
    {
        int v = es[i];
        if(v == fa)
            continue;
        dfs(v, u);
    }
    dfsr[u] = dfstot; //dfsr维护了这颗子树dfs序的最大值
    //得到dfsl与dfsr后,这颗子树就变成了一个区间,也就是[dfsl[u], dfsr[u]]这个区间
}

void init()
{
    memset(head, -1, sizeof head);
}

int cnt;

int main()
{
    init();
    scanf("%d", &n);
    for(int i = 0, u, v; i < n - 1; i++)
    {
        scanf("%d%d", &u, &v);
        addEdge(u, v);
        addEdge(v, u);
    }
    for (int i = 1; i <= n; i ++ )
    {
        scanf("%d", &a[i]);
        nums.push_back(a[i]);
    }

    scanf("%d", &q);
    for(int i = 1; i <= q; i++)
    {
        int x, l, r;
        scanf("%d%d%d", &x, &l, &r);
        qs[i] = {x, l, r};
        //将l, r也加入nums(因为是权值线段树)
        nums.push_back(l);
        nums.push_back(r);
    }

    //离散化处理
    sort(nums.begin(), nums.end());
    nums.erase(unique(nums.begin(), nums.end()), nums.end());

    root[0] = build(0, nums.size() - 1); //初始版本

    dfs(1, -1); //dfs并建树

    for(int i = 1; i <= q; i++)
    {
        int x = qs[i].x;
        int l = qs[i].l;
        int r = qs[i].r;
        if(a[x] < l || a[x] > r) //特判,如果第一个节点就不符合直接退出
        {
            printf("0\n");
            continue;
        }
        for(int i = 19; i >= 0; i--)
            if(par[x][i] > 0 && a[par[x][i]] <= r)
                x = par[x][i]; //从当前点往根节点走,一直走到父亲节点的温度不在[l, r]区间内时停下
        l = find(l); //找离散化数组中的对应位置
        r = find(r);
        printf("%d\n", query(root[dfsl[x] - 1], root[dfsr[x]], l, r, 0, nums.size() - 1)); //r的版本减去l - 1的版本,也就是得到了值在l到r之间的数的个数
    }

    return 0;
}

H-Happy Number

思路

把2当作0,3当作1,6当作2,可以发现其实是类似3进制数的转换。但是它的规律是2,3,6,22,23,26,32,36,……,并不是单纯的3进制。我们求出每个长度区间的快乐数个数并求前缀和,对于给定的数n,找到其在哪个长度区间中,n减去这个长度区间之前有多少数后再转换为3进制,再将其不足该长度区间的位用0补全,对应2,3,6输出即可。

代码

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

const int N = 1e5 + 10;

ll pow3[N];
ll pre[N];

void init(int n)
{
    pow3[0] = 1;
    pre[1] = 0;
    for(int i = 1; i <= n; i++)
    {
        pow3[i] = pow3[i - 1] * 3LL;
        pre[i] = pow3[i] + pre[i - 1];
    }
}

vector<int> get3(ll x)
{
    vector<int> vec(21);
    while(x)
    {
        int pos = lower_bound(pow3, pow3 + 20, x) - pow3;
        if(pow3[pos] != x)
            pos--;
        vec[pos] = x / pow3[pos];
        x -= vec[pos] * pow3[pos];
    }
    return vec;
}

int mp[] = {2, 3, 6};

int main()
{
    init(20);
    int n;
    cin >> n;
    n--;
    if(!n)
        return cout << 2 << endl, 0;
    int len = lower_bound(pre, pre + 21, n) - pre;
    if(n == pre[len])
        len++;
    n -= pre[len - 1];
    vector<int> vec = get3(n);
    bool flag = 0;
    for(int i = len - 1; i >= 0; i--)
    {
        flag = 1;
        cout << mp[vec[i]];
    }
    cout << endl;
    return 0;
}

J-Jam

思路

参考:牛客多校第九场E+J.Jam J强力推荐 2021牛客暑期多校训练营9 J题: Jam

列出所有可行的情况,分别有四种:当前方向直走+左转,当前方向直走+对面方向直走,当前方向直走+左侧方向左转,右转。当然也可以画图连线后把图展平看,不过我实在是不太会展平面图qwq

可以发现右转一定是可行的,所以右转可以先不考虑;对于前三种情况,前两种的最终值是个定值(可以看我列的参考,讲的很详细),所以可以枚举最后一种情况什么时候能使前两种情况得到的定值最小。(讲的好模糊,还是看我的参考吧qwq)

我麻了,我看了一个下午的代码找错误,最后是ans初值没赋max……

代码

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;

const int N = 5;

char mp[] = {0, 'N', 'E', 'S', 'W'};
int a[N][N];
int M[N]; //straight
int L[N]; //left
int R[N]; //right

int cal(int a, int b, int c, int d)
{
    a = max(a, 0);
    b = max(b, 0);
    c = max(c, 0);
    d = max(d, 0);
    return max(a + d, b + c);
}

int main()
{
    int T;
    cin >> T;
    while(T--)
    {
        for(int i = 1; i <= 4; i++)
        {
            for(int j = 1; j <= 4; j++)
            {
                cin >> a[i][j];
            }
        }
        for(int i = 1; i <= 4; i++)
        {
            R[i] = a[i][(i + 2) % 4 + 1];
            L[i] = a[i][i % 4 + 1];
            M[i] = a[i][(i + 1) % 4 + 1];
        }
        int ans = INF;
        for(int i = 0; i <= 100; i++)
        {
            for(int j = 0; j <= 100; j++)
            {
                for(int k = 0; k <= 100; k++)
                {
                    ans = min(ans, i + j + k + cal(L[1] - i, L[3], M[1] - j, M[3] - k) + cal(L[2] - j, L[4] - k, M[2], M[4] - i));
                    ans = min(ans, i + j + k + cal(L[2] - i, L[4], M[2] - j, M[4] - k) + cal(L[3] - j, L[1] - k, M[3], M[1] - i));
                    ans = min(ans, i + j + k + cal(L[3] - i, L[1], M[3] - j, M[1] - k) + cal(L[4] - j, L[2] - k, M[4], M[2] - i));
                    ans = min(ans, i + j + k + cal(L[4] - i, L[2], M[4] - j, M[2] - k) + cal(L[1] - j, L[3] - k, M[1], M[3] - i));
                }
            }
        }
        for(int i = 1; i <= 4; i++)
            ans = max(ans, R[i]);
        cout << ans << endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值