AHUACM寒假集训II(线段树)
阅读原文时间:2023年07月09日阅读:2
B.Mayor’s posters

POJ2528
题目大意:

D.Count Color

POJ2777
题目大意:长为

L

(

L

1

0

5

)

L( L\leq10^5)

L(L≤105)的序列,每个点上可以有

T

(

T

30

)

T(T\leq30)

T(T≤30)种颜色以供染色(整个序列最开始都为颜色1),

O

(

O

1

0

5

)

O(O\leq10^5)

O(O≤105)次操作:

C

C

C

a

a

a

b

b

b

c

c

c:

[

a

,

b

]

[a,b]

[a,b]染上颜色

c

c

c

P

P

P

a

a

a

b

b

b:查询

[

a

,

b

]

[a,b]

[a,b]上共有几种颜色
思路:因为

T

T

T很小,所以我们可以把颜色集合进行状态压缩,

p

u

s

h

u

p

pushup

pushup的时候直接取两个儿子的并即可,懒标记直接设为仅有对应颜色的集合即可。复杂度

O

(

(

L

+

O

)

l

o

g

L

)

O((L+O)logL)

O((L+O)logL)。此外注意本题

a

a

a可能大于

b

b

b。
代码:

#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<LL, int> PII;
//#define int LL
#define lc p*2+1
#define rc p*2+2
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#pragma warning(disable :4996)
const double eps = 1e-8;
const LL mod = 1000000007;
const LL MOD = 998244353;
const int maxn = 100010;

struct node {
    int l, r, dat, lazy;
};

node tr[maxn * 4];

void build(int p, int l, int r)
{
    tr[p].l = l, tr[p].r = r, tr[p].lazy = 0;
    if (l + 1 == r)
    {
        tr[p].dat = 1;
        return;
    }
    int mid = (l + r) / 2;
    build(lc, l, mid), build(rc, mid, r);
    tr[p].dat = tr[lc].dat | tr[rc].dat;
}

void pushdown(int p)
{
    if (tr[p].lazy)
    {
        tr[lc].dat = tr[rc].dat = tr[lc].lazy = tr[rc].lazy = tr[p].lazy;
        tr[p].lazy = 0;
    }
}

void modify(int p, int l, int r, int d)
{
    if (tr[p].l >= l && tr[p].r <= r)
    {
        tr[p].dat = tr[p].lazy = 1 << d;
        return;
    }
    pushdown(p);
    int mid = (tr[p].l + tr[p].r) / 2;
    if (l < mid)
        modify(lc, l, r, d);
    if (r > mid)
        modify(rc, l, r, d);
    tr[p].dat = tr[lc].dat | tr[rc].dat;
}

int query(int p, int l, int r)
{
    if(tr[p].l >= l && tr[p].r <= r)
        return tr[p].dat;
    pushdown(p);
    int mid = (tr[p].l + tr[p].r) / 2;
    if (r <= mid)
        return query(lc, l, r);
    if (l >= mid)
        return query(rc, l, r);
    return query(lc, l, mid) | query(rc, mid, r);
}

int L, T, O;

void solve()
{
    build(0, 1, L + 1);
    char t;
    int a, b, c;
    for (int i = 1; i <= O; i++)
    {
        cin >> t;
        if (t == 'C')
        {
            cin >> a >> b >> c;
            int l = min(a, b), r = max(a, b);
            modify(0, l, r + 1, c - 1);
        }
        else
        {
            cin >> a >> b;
            int l = min(a, b), r = max(a, b);
            int num = query(0, l, r + 1), ans = 0;
            for (int i = 0; i < T; i++)
                ans += (num >> i) & 1;
            cout << ans << endl;
        }
    }
}

int main()
{
    IOS;
    cin >> L >> T >> O;
    solve();

    return 0;
}
E.Who Gets the Most Candies?

POJ2886
题目大意:

N

N

N个人围一圈,每个人有

A

[

i

]

A[i]

A[i],表示这个人沿顺时针

(

A

[

i

]

>

0

)

(A[i]>0)

(A[i]>0)或逆时针

(

A

[

i

]

<

0

)

(A[i]<0)

(A[i]<0)方向数的第

A

[

i

]

|A[i]|

∣A[i]∣个人是下一个要退出的,每次游戏从第

K

K

K个人开始,退出的次序

r

n

k

rnk

rnk的约束个数就是获得的糖果数,问谁获得糖果最多,有多个则输出先退出的。

思路:

i

i

i以内的约数最多的最小数字可以用倍数法

O

(

N

l

o

g

N

)

O(NlogN)

O(NlogN)预处理出来,之后对于每轮游戏直接模拟即可,用

B

I

T

BIT

BIT来维护一下各个位置上的人有没有退出,在

B

I

T

BIT

BIT上二分可以求得剩下的人中相对排名对应的原始位置。

代码:

#include<iostream>
#include<string>
#include<stack>
#include<queue>
#include<vector>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<utility>
#include<cstdio>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
//#define int LL
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#pragma warning(disable :4996)
const double eps = 1e-8;
const LL mod = 1000000007;
const LL MOD = 998244353;
const int maxn = (1 << 19) + 10;

int cnt[maxn], S[maxn];
int N, K;
string name[maxn];
int num[maxn], dat[maxn], n;

void add(int i, int x)
{
    while (i <= (1 << 19))
    {
        dat[i] += x;
        i += i & (-i);
    }
}

int sum(int i)
{
    int ans = 0;
    while (i > 0)
    {
        ans += dat[i];
        i -= i & (-i);
    }

    return ans;
}

int getpos(int rk)
{
    int k = 0, s = 0;
    for (int i = 18; i >= 0; i--)
    {
        int t = k + (1 << i);
        if (s + dat[t] < rk)
        {
            k = t;
            s += dat[t];
        }
    }

    return k + 1;
}

void init()
{
    for (int i = 1; i <= 5e5; i++)
    {
        for (int j = i; j <= 5e5; j += i)
            cnt[j]++;
    }
    for (int i = 1; i <= 5e5; i++)
    {
        if (cnt[i] > cnt[S[i - 1]])
            S[i] = i;
        else
            S[i] = S[i - 1];
    }
}

void solve()
{
    string ans1;
    int ord = 1, lst = N - 1, ans2 = cnt[S[N]], rnk = K;
    int pos = getpos(rnk), start;
    while (true)
    {
        int A = num[pos];
        ans1 = name[pos];
        if (ord == S[N])
            break;
        add(pos, -1);
        if (A > 0)
        {
            rnk = (rnk - 1 + A) % lst;
            if (rnk == 0)
                rnk = lst;
            pos = getpos(rnk);
        }
        else
        {
            rnk = ((rnk + A) % lst + lst) % lst;
            if (rnk == 0)
                rnk = lst;
            pos = getpos(rnk);
        }
        ord++;
        lst--;
    }
    cout << ans1 << ' ' << ans2 << endl;
}

int main()
{
    IOS;
    init();
    while (cin >> N >> K)
    {
        memset(dat, 0, sizeof(dat));
        for (int i = 1; i <= N; i++)
        {
            cin >> name[i] >> num[i];
            add(i, 1);
        }
        solve();
    }

    return 0;
}
F.花神游历各国

LOJ10128
题目大意:
长为

N

(

N

1

0

5

)

N(N\leq10^5)

N(N≤105)的序列,各元素

0

a

i

1

0

9

0\leq a_{i}\leq10^9

0≤ai​≤109,

M

(

M

2

×

1

0

5

)

M(M\leq2\times10^5)

M(M≤2×105)
次操作:

1

1

1

l

l

l

r

r

r:

[

l

,

r

]

[l,r]

[l,r]内所有元素开根号向下取整

2

2

2

l

l

l

r

r

r:询问

[

l

,

r

]

[l,r]

[l,r]内所有元素和

(

1

l

r

N

)

(1\leq l\leq r\leq N)

(1≤l≤r≤N)

思路:
直接向区间加上一个数那样用懒标记处理取平方根显然没有办法维护,注意到

1

0

9

10^9

109内的数最多操作6次就会变为1,而对1和0本身取根号是没有变化的,于是我们可以直接对每个区间修改暴力地进行单点修改,每个节点记录所辖区间内0与1的个数,如果全部是0或者1,那么之后就不去这个区间做修改,这样每个数最多被修改6次,每次修改

O

(

l

o

g

N

)

O(logN)

O(logN),总的复杂度还是

O

(

(

N

+

M

)

l

o

g

N

)

O((N+M)logN)

O((N+M)logN)

代码:

#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<LL, int> PII;
#define int LL
#define lc p*2+1
#define rc p*2+2
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#pragma warning(disable :4996)
const double eps = 1e-8;
const LL mod = 1000000007;
const LL MOD = 998244353;
const int maxn = 100010;

struct node {
    int l, r, dat;
    int cnt;//区间<=1之个数
};

int N, M;
int A[maxn];
node tr[maxn * 4];

void build(int p, int l, int r)
{
    tr[p].l = l, tr[p].r = r, tr[p].cnt = 0;
    if (l + 1 == r)
    {
        tr[p].dat = A[l];
        if (A[l] <= 1)
            tr[p].cnt = 1;
        return;
    }
    int mid = (tr[p].l + tr[p].r) / 2;
    build(lc, l, mid), build(rc, mid, r);
    tr[p].dat = tr[lc].dat + tr[rc].dat;
    tr[p].cnt = tr[lc].cnt + tr[rc].cnt;
}

void modify(int p, int l, int r)
{
    if (tr[p].cnt == tr[p].r - tr[p].l)//该区间不用修改
        return;
    if (tr[p].l + 1 == tr[p].r)
    {
        tr[p].dat = floor(sqrt(tr[p].dat));
        if (tr[p].dat <= 1)
            tr[p].cnt = 1;
        return;
    }
    int mid = (tr[p].l + tr[p].r) / 2;
    if (l < mid)
        modify(lc, l, r);
    if (r > mid)
        modify(rc, l, r);
    tr[p].dat = tr[lc].dat + tr[rc].dat;
    tr[p].cnt = tr[lc].cnt + tr[rc].cnt;
}

int query(int p, int l, int r)
{
    if (tr[p].l >= l && tr[p].r <= r)
        return tr[p].dat;
    int mid = (tr[p].l + tr[p].r) / 2;
    if (r <= mid)
        return query(lc, l, r);
    if (l >= mid)
        return query(rc, l, r);
    return query(lc, l, mid) + query(rc, mid, r);
}

void solve()
{
    build(0, 1, N + 1);
    cin >> M;
    int x, l, r;
    for (int i = 1; i <= M; i++)
    {
        cin >> x;
        if (x == 1)
        {
            cin >> l >> r;
            int a = min(l, r), b = max(l, r);
            cout << query(0, a, b + 1) << endl;
        }
        else
        {
            cin >> l >> r;
            int a = min(l, r), b = max(l, r);
            modify(0, a, b + 1);
        }
    }
}

signed main()
{
    IOS;
    cin >> N;
    for (int i = 1; i <= N; i++)
        cin >> A[i];
    solve();

    return 0;
}
I.二逼平衡树

luoguP3380/LOJ106
题目大意:

思路:
树套树模板题,下标线段树中每个节点用一个动态开点权值线段树维护其区间内的元素,时空都是

O

(

N

l

o

g

2

N

)

O(Nlog^2N)

O(Nlog2N),
代码:

#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<LL, int> PII;
//#define int LL
#define lch p*2+1
#define rch p*2+2
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#pragma warning(disable :4996)
const double eps = 1e-8;
const LL mod = 1000000007;
const LL MOD = 998244353;
const int maxn = 50010;
const int SIZE = 17000000;//注意大小

int totb = 0;
struct nodeb {
    int dat, lc, rc;
};
nodeb trb[SIZE];//内层权值线段树,动态开点

struct nodea {
    int root, l, r;
};
nodea tra[maxn * 4];//外层常规下标线段树

//a:外层树操作,b:内层树操作

int buildb()
{
    ++totb;
    trb[totb].dat = trb[totb].lc = trb[totb].rc = 0;

    return totb;
}

void modifyb(int p, int l, int r, int val, int delta)
{
    if (l + 1 == r)
    {
        trb[p].dat += delta;
        return;
    }
    int mid = (l + r) / 2;
    if (val < mid)
    {
        if (!trb[p].lc)
            trb[p].lc = buildb();
        modifyb(trb[p].lc, l, mid, val, delta);
    }
    else
    {
        if (!trb[p].rc)
            trb[p].rc = buildb();
        modifyb(trb[p].rc, mid, r, val, delta);
    }
    trb[p].dat = trb[trb[p].lc].dat + trb[trb[p].rc].dat;
}

int rankb(int p, int l, int r, int x)
{
    if (p == 0 || l + 1 == r)
        return 0;
    int mid = (l + r) / 2;
    if (x >= mid)
        return trb[trb[p].lc].dat + rankb(trb[p].rc, mid, r, x);
    else
        return rankb(trb[p].lc, l, mid, x);
}

int N, M, A[maxn];

void builda(int p, int l, int r)
{
    tra[p].root = buildb();
    tra[p].l = l, tra[p].r = r;
    if (l + 1 == r)
    {
        modifyb(tra[p].root, -1e8, 1e8 + 10, A[l], 1);
        return;
    }
    int mid = (l + r) / 2;
    builda(lch, l, mid), builda(rch, mid, r);
    for (int i = l; i < r; i++)
        modifyb(tra[p].root, -1e8, 1e8 + 10, A[i], 1);
}

void modifya(int p, int pos, int x)
{
    modifyb(tra[p].root, -1e8, 1e8 + 10, A[pos], -1);
    modifyb(tra[p].root, -1e8, 1e8 + 10, x, 1);
    if (tra[p].l + 1 == tra[p].r)
        return;
    int mid = (tra[p].l + tra[p].r) / 2;
    if (pos < mid)
        modifya(lch, pos, x);
    else
        modifya(rch, pos, x);
}

int ranka(int p, int l, int r, int x)//求出[l,r)内小于x的数的个数
{
    if (tra[p].l >= l && tra[p].r <= r)
        return rankb(tra[p].root, -1e8, 1e8 + 10, x);
    int mid = (tra[p].l + tra[p].r) / 2;
    if (r <= mid)
        return ranka(lch, l, r, x);
    if (l >= mid)
        return ranka(rch, l, r, x);
    return ranka(lch, l, mid, x) + ranka(rch, mid, r, x);
}

int val(int p, int l, int r, int rk)
{
    int lo = -1e8, hi = 1e8 + 10;
    while (hi - lo > 1)
    {
        int mid = (lo + hi) / 2;
        if (ranka(0, l, r, mid) + 1 <= rk)//找排名<=rk的最大值
            lo = mid;
        else
            hi = mid;
    }

    return lo;
}

int pred(int p, int l, int r, int x)
{
    int rk = ranka(0, l, r, x);
    if (rk < 1)
        return -2147483647;
    return val(0, l, r, rk);
}

int succ(int p, int l, int r, int x)
{
    int rk = ranka(0, l, r, x + 1) + 1;
    if (rk > r - l)
        return 2147483647;
    return val(0, l, r, rk);
}

void solve()
{
    builda(0, 1, N + 1);
    int opt, l, r, x, k, pos;
    for (int i = 1; i <= M; i++)
    {
        cin >> opt;
        switch (opt)
        {
            case 1:
                cin >> l >> r >> x;
                cout << ranka(0, l, r + 1, x) + 1 << endl;
                break;
            case 2:
                cin >> l >> r >> k;
                cout << val(0, l, r + 1, k) << endl;
                break;
            case 3:
                cin >> pos >> x;
                modifya(0, pos, x);
                A[pos] = x;
                break;
            case 4:
                cin >> l >> r >> x;
                cout << pred(0, l, r + 1, x) << endl;
                break;
            case 5:
                cin >> l >> r >> x;
                cout << succ(0, l, r + 1, x) << endl;
                break;
        }
    }
}

int main()
{
    IOS;
    cin >> N >> M;
    for (int i = 1; i <= N; i++)
        cin >> A[i];
    solve();

    return 0;
}
J.冰火战士

luoguP6619/LOJ3299
思路:
当温度确定时,消耗的总能量为冰火两方可参赛战士能量总和最少的一方

×

2

\times2

×2,于是我们只要求出这个最小值即可。
发现对于温度

K

K

K,可以参赛的冰系战士总能量是一个

K

K

K处的前缀和,而可以参赛的火系战士总能量是一个

K

K

K处的后缀和,可以发现冰系战士的总能量是随着温度增加而递增的,而火系是递减的,所以显然二者最小值在二者曲线交点处取最大,但由于二者的曲线不是连续的,所以最大值有两个可能的点,分别是最大的火系总能量

\geq

≥冰系的温度

k

1

k_{1}

k1​,以及最小的火系总能量

\leq

≤冰系的温度

k

2

k_{2}

k2​,这两个点的结果取最大值就是最多能量了,我们可以对温度离散化后用

B

I

T

BIT

BIT来维护冰,火两系战士的能量,而

k

1

,

k

2

k1,k2

k1,k2两个点可以通过在

B

I

T

BIT

BIT上二分来仅用一个

l

o

g

log

log的复杂度求得,

B

I

T

BIT

BIT上的二分可以理解为就是在上面做倍增,不过要注意维护一下已经跳过部分的贡献,冰系的很好维护,火系的由于需要的是后缀和,需要额外记录一下总和

F

I

R

E

FIRE

FIRE以及每个温度处的火系选手的能量值

F

[

i

]

F[i]

F[i],温度

T

T

T时火系选手的总温度就是

F

I

R

E

S

[

T

]

+

F

[

T

]

FIRE-S[T]+F[T]

FIRE−S[T]+F[T]。题目还要求在求得最大能量的前提下,温度要尽可能地大,显然如果答案取在

k

1

k_{1}

k1​,那么就已经是最大的了,而如果取在

k

2

k_{2}

k2​,答案为

x

x

x,我们可以再在

B

I

T

BIT

BIT上面二分来寻找火系总能量

x

\geq x

≥x的温度来作为答案即可。
复杂度

O

(

Q

l

o

g

N

)

O(QlogN)

O(QlogN)

代码:

#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
//#define LL int
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#pragma warning(disable :4996)
const double eps = 1e-8;
const LL mod = 1000000007;
const LL MOD = 998244353;
const int maxn = (1 << 21) + 5;

int read()
{
    int x = 0, f = 1;
    char c = getchar();
    while (c < '0' || c > '9')
    {
        if (c == '-')
            f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9')
    {
        x = x * 10 + c - '0';
        c = getchar();
    }

    return x * f;
}

void write(int x)
{
    if (x < 0)
    {
        putchar('-');
        x = -x;
    }
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}

int Q, M;
int N[maxn], T[maxn], X[maxn], Y[maxn], K[maxn];
int FIRE, F[(1 << 21) + 5];//fire总能量,各点上fire能量
int S[(1 << 21) + 5];
int dat[(1 << 21) + 5][2];//0:ice,1:fire

int compress()
{
    vector<int>xs;
    for (int i = 1; i <= Q; i++)
    {
        if (N[i] == 1)
            xs.push_back(X[i]);
    }
    sort(xs.begin(), xs.end());
    xs.erase(unique(xs.begin(), xs.end()), xs.end());
    for (int i = 1; i <= Q; i++)
    {
        if (N[i] == 1)
        {
            int tmp = X[i];
            X[i] = upper_bound(xs.begin(), xs.end(), X[i]) - xs.begin();
            S[X[i]] = tmp;
        }
    }

    return xs.size();
}

void add(int i, int x, int t)
{
    while (i <= (1 << 21))
    {
        dat[i][t] += x;
        i += i & (-i);
    }
}

int sum(int i, int t)
{
    int ans = 0;
    while (i > 0)
    {
        ans += dat[i][t];
        i -= i & (-i);
    }

    return ans;
}

PII query1()//(最右的fire>=ice点)
{
    int k = 0, fire = FIRE, ice = 0;
    for (int i = 20; i >= 0; i--)
    {
        int t = k + (1 << i);
        if (fire - dat[t][1] + F[t] >= ice + dat[t][0])
        {
            fire -= dat[t][1];
            ice += dat[t][0];
            k = t;
        }
    }

    return PII(k, sum(k, 0));
}

PII query2()//(最左的fire<=ice点)
{
    int k = 0, fire = FIRE, ice = 0;
    for (int i = 20; i >= 0; i--)
    {
        int t = k + (1 << i);
        if (fire - dat[t][1] + F[t] > ice + dat[t][0])
        {
            k = t;
            fire -= dat[t][1];
            ice += dat[t][0];
        }
    }

    return PII(k + 1, FIRE - sum(k + 1, 1) + F[k + 1]);
}

int query3(int x)//(最右的fire>=x点)
{
    int k = 0, fire = FIRE;
    for (int i = 20; i >= 0; i--)
    {
        int t = k + (1 << i);
        if (fire - dat[t][1] + F[t] >= x)
        {
            k = t;
            fire -= dat[t][1];
        }
    }
    return k;
}

void solve()
{
    M = compress();
    for (int i = 1; i <= Q; i++)
    {
        if (N[i] == 1)
        {
            add(X[i], Y[i], T[i]);
            if (T[i])
            {
                FIRE += Y[i];
                F[X[i]] += Y[i];
            }
        }
        else
        {
            int x = X[K[i]], y = Y[K[i]], t = T[K[i]];
            if (t)
            {
                FIRE -= y;
                F[x] -= y;
            }
            add(x, -y, t);
        }
        PII a = query1(), b = query2();
        int k1 = a.first, v1 = a.second;
        int k2 = b.first, v2 = b.second;
        int ans1, ans2;
        if (v1 > v2)
            ans2 = v1, ans1 = k1;
        else
            ans2 = v2, ans1 = k2;
        if (ans2 == v2)
            ans1 = query3(ans2);
        if (!ans2)
            puts("Peace");
        else
        {
            write(S[ans1]), putchar(' ');
            write(ans2 * 2), putchar('\n');
        }
    }
}

int main()
{
    Q = read();
    for (int i = 1; i <= Q; i++)
    {
        N[i] = read();
        if (N[i] == 1)
            T[i] = read(), X[i] = read(), Y[i] = read();
        else
            K[i] = read();
    }
    solve();

    return 0;
}
K.混合果汁

luoguP4602/LOJ2555
题目大意:

N

(

N

1

0

5

)

N(N\leq10^5)

N(N≤105)种果汁,每种果汁有美味度

d

i

d_{i}

di​,单价

p

i

p_{i}

pi​,购买上限

l

i

(

1

d

i

,

p

i

,

l

i

1

0

5

)

l_{i}(1\leq d_{i},p_{i},l_{i}\leq10^5)

li​(1≤di​,pi​,li​≤105),有

M

(

M

1

0

5

)

M(M\leq10^5)

M(M≤105)次询问,每次询问给出拥有的钱

g

i

g_{i}

gi​,至少购买的果汁升数

L

i

(

1

g

i

,

L

i

1

0

18

)

L_{i}(1\leq g_{i},L_{i}\leq10^{18})

Li​(1≤gi​,Li​≤1018),混合果汁的美味度为所购买的果汁中美味度最低的,对于每个询问,需要使所购买满足要求的混合果汁中美味度最大,如果不能购买满足要求的果汁,输出

1

-1

−1。

思路:
我们考虑单独

1

1

1个询问,显然我们可以二分美味度

d

d

d,

c

h

e

c

k

check

check时考虑所有

d

i

d

d_{i}\geq d

di​≥d的果汁,贪心地从单价低的开始购买,看能不能卖出满足要求的即可。
现在有多组询问,每次都直接二分显然是不行的,于是我们可以建一个主席树维护单价

p

p

p上的信息,对所有的

d

d

d从大到小可持久化(实现时对果汁按

d

d

d排序之后对下标可持久化,二分也改为二分下标即可),每个节点维护所辖区间的果汁总购买上限以及购买他们所需要的总价格,对于每个查询,二分

c

h

e

c

k

check

check时只需要查询主席树上对应的版本就可以了,这样每个询问可以在

O

(

l

o

g

2

N

)

O(log^2N)

O(log2N)的时间内回答,总的复杂度为

O

(

N

l

o

g

2

N

)

O(Nlog^2N)

O(Nlog2N)。

代码:

#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
#define int LL
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#pragma warning(disable :4996)
const double eps = 1e-8;
const LL mod = 1000000007;
const LL MOD = 998244353;
const int maxn = 100010;

struct node {
    int lc, rc, sum, lim;
}tr[maxn * 40];
struct juice {
    int d, p, l;
}J[maxn];
int tot = 0, root[maxn], N, M, mx = -INF;

bool cmp(const juice& a, const juice& b)
{
    return a.d < b.d;
}

int build(int l, int r)//[)
{
    int p = ++tot;
    if (l + 1 == r)
    {
        tr[p].lim = tr[p].sum = 0;
        return p;
    }
    int mid = (l + r) / 2;
    tr[p].lc = build(l, mid), tr[p].rc = build(mid, r);
    tr[p].lim = tr[tr[p].lc].lim + tr[tr[p].rc].lim;
    tr[p].sum = tr[tr[p].lc].sum + tr[tr[p].rc].sum;

    return p;
}

int modify(int now, int l, int r, int x, int val)
{
    int p = ++tot;
    tr[p] = tr[now];
    if (l + 1 == r)
    {
        tr[p].sum += x * val, tr[p].lim += val;
        return p;
    }
    int mid = (l + r) / 2;
    if (x < mid)
        tr[p].lc = modify(tr[now].lc, l, mid, x, val);
    else
        tr[p].rc = modify(tr[now].rc, mid, r, x, val);
    tr[p].lim = tr[tr[p].lc].lim + tr[tr[p].rc].lim;
    tr[p].sum = tr[tr[p].lc].sum + tr[tr[p].rc].sum;

    return p;
}

bool query(int p, int q, int l, int r, int k, int s)//k:购买上限,s:所持有的钱数
{
    if (k > tr[q].lim - tr[p].lim || s < 0)
        return false;
    if (l + 1 == r)
        return l * k <= s;
    int mid = (l + r) / 2;
    int llim = tr[tr[q].lc].lim - tr[tr[p].lc].lim, lsum = tr[tr[q].lc].sum - tr[tr[p].lc].sum;
    if (k <= llim)
        return query(tr[p].lc, tr[q].lc, l, mid, k, s);
    else
        return query(tr[p].rc, tr[q].rc, mid, r, k - llim, s - lsum);
}

void solve()
{
    sort(J + 1, J + N + 1, cmp);
    int lst = J[N].d;
    root[0] = build(1, mx + 1);
    for (int i = N; i >= 1; i--)
        root[N - i + 1] = modify(root[N - i], 1, mx + 1, J[i].p, J[i].l);
    int g, l;
    for (int i = 1; i <= M; i++)
    {
        cin >> g >> l;
        int lo = 0, hi = N + 1;//对下标二分
        while (hi - lo > 1)
        {
            int mid = (hi + lo) / 2;
            if (query(root[0], root[N - mid + 1], 1, mx + 1, l, g))
                lo = mid;
            else
                hi = mid;
        }
        cout << (lo ? J[lo].d : -1) << endl;
    }
}

signed main()
{
    IOS;
    cin >> N >> M;
    for (int i = 1; i <= N; i++)
    {
        cin >> J[i].d >> J[i].p >> J[i].l;
        mx = max(mx, J[i].p);
    }
    solve();

    return 0;
}
L.陌上花开

luoguP3810/LOJ112
题目大意:

1

N

1

0

5

,

1

K

2

×

1

0

5

1\leq N\leq10^5,1\leq K\leq2\times10^5

1≤N≤105,1≤K≤2×105

思路:
三维偏序模板题,第一维排序,第二维用树状数组维护,第三维用动态开点的权值线段树维护。
(树状数组的每个节点维护一颗维护其所辖第二维区间上的第三维值域上面信息的线段树。)

代码:

#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<LL, int> PII;
//#define int LL
#define lch p*2+1
#define rch p*2+2
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#pragma warning(disable :4996)
const double eps = 1e-8;
const LL mod = 1000000007;
const LL MOD = 998244353;
const int maxn = 200010;
const int SIZE = 30000000;

int tot = 0;

struct point {
    int a, b, c, id;
    bool operator<(const point& rhs)
    {
        if (a == rhs.a)
        {
            if (b == rhs.b)
                return c < rhs.c;
            return b < rhs.b;
        }
        return a < rhs.a;
    }
    bool operator==(const point& rhs)
    {
        return a == rhs.a && b == rhs.b && c == rhs.c;
    }
};

struct node {
    int dat, lc, rc;
};
node tr[SIZE];//内层权值线段树,动态开点

int build()
{
    ++tot;
    tr[tot].dat = tr[tot].lc = tr[tot].rc = 0;

    return tot;
}

void modify(int p, int l, int r, int val, int delta)
{
    if (l + 1 == r)
    {
        tr[p].dat += delta;
        return;
    }
    int mid = (l + r) / 2;
    if (val < mid)
    {
        if (!tr[p].lc)
            tr[p].lc = build();
        modify(tr[p].lc, l, mid, val, delta);
    }
    else
    {
        if (!tr[p].rc)
            tr[p].rc = build();
        modify(tr[p].rc, mid, r, val, delta);
    }
    tr[p].dat = tr[tr[p].lc].dat + tr[tr[p].rc].dat;
}

int query(int p, int l, int r, int x)
{
    if (p == 0 || l + 1 == r)
        return 0;
    int mid = (l + r) / 2;
    if (x >= mid)
        return tr[tr[p].lc].dat + query(tr[p].rc, mid, r, x);
    else
        return query(tr[p].lc, l, mid, x);
}

int N, K;
int ans[maxn];
point A[maxn];
int dat[maxn], n;

void add(int posx, int posy, int x)
{
    while (posx <= n)
    {
        if (!dat[posx])
            dat[posx] = build();
        modify(dat[posx], 1, K + 5, posy, x);
        posx += posx & (-posx);
    }
}

int sum(int posx, int posy)
{
    int tmp = 0;
    while (posx > 0)
    {
        tmp += query(dat[posx], 1, K + 5, posy + 1);
        posx -= posx & (-posx);
    }

    return tmp;
}

void solve()
{
    n = K + 5;
    sort(A + 1, A + N + 1);
    int tmp = 1;
    for (int i = 1; i <= N; i++)
    {
        if (A[i] == A[i + 1])
        {
            tmp++;
            continue;
        }
        add(A[i].b, A[i].c, tmp);
        ans[sum(A[i].b, A[i].c) - 1] += tmp;
        tmp = 1;
    }
    for (int i = 0; i < N; i++)
        cout << ans[i] << endl;
}

int main()
{
    IOS;
    cin >> N >> K;
    for (int i = 1; i <= N; i++)
    {
        cin >> A[i].a >> A[i].b >> A[i].c;
        A[i].id = i;
    }
    solve();

    return 0;
}
M.A Plus B Problem

2021CCPC桂林站B
题目大意:
两个长度为

N

(

N

1

0

6

)

N(N\leq10^6)

N(N≤106)的数字

A

,

B

A,B

A,B做加法,结果也为一个长度为

N

N

N的数字(多出来的高位被忽略),

Q

(

Q

1

0

6

)

Q(Q\leq10^6)

Q(Q≤106)个次操作,每次操作把选择两个加数的其中一个,将其某一位数字替换为

0

0

0到

9

9

9中的一个数字,询问每次操作后,两个加数以及和中,总共有多少数字发生变化。

思路:
主要是要解决更改数字后的进位问题,对于每一位,最多向前进

1

1

1位,所以只要该位上两个加数的数字相加不等于

9

9

9,那么这一位向前进位与否就仅取决于自己,否则还要取决于更低一位是否进位,设当前位为

i

i

i,

i

i

i右边的第一个加数和不为

9

9

9的位记为

j

j

j,那么第

i

i

i位和数上的值就等于

(

A

i

+

B

i

+

[

A

j

+

B

j

10

]

)

%

10

(A_{i}+B_{i}+[A_{j}+B_{j}\geq 10])\%10

(Ai​+Bi​+[Aj​+Bj​≥10])%10
那么显然如果更改后某一位上的进位状态没有发生变化,就仅会改变

2

2

2个数字,如果发生了变化,那么受其原先进位状态影响的若干个连续的更高位也会发生变化,记

i

i

i位左侧首个加数和不为

9

9

9的位为

j

j

j,

i

i

i位所能影响的就是

j

j

j到

i

i

i这若干位,于是变化的位数就是这些位数

+

1

+1

+1了。
我们可以维护所有加数和不等于

9

9

9的位,并且要插入,删除,查询前驱后继,所以我们用

s

e

t

set

set就可以轻松维护,在

O

(

Q

l

o

g

N

)

O(QlogN)

O(QlogN)内解决问题。
注意一些边界情况要特别处理一下。

代码:

#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
//#define int LL
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#pragma warning(disable :4996)
const double eps = 1e-8;
const LL mod = 1000000007;
const LL MOD = 998244353;
const int maxn = 1000010;

int N, Q;
string S[2];
int num[2][maxn];
set<int>ss;

void solve()
{
    ss.insert(N + 1), ss.insert(0);
    for (int i = 1; i <= N; i++)
    {
        num[0][i] = S[0][i - 1] - '0', num[1][i] = S[1][i - 1] - '0';
        if (num[0][i] + num[1][i] != 9)
            ss.insert(i);
    }
    int r, c, d;
    for (int i = 1; i <= Q; i++)
    {
        int ans1 = 2, ans2;
        cin >> r >> c >> d;
        bool up1 = false, up2 = false;
        int up = 0;
        int a = num[1][c] + num[0][c];
        int succ = *ss.upper_bound(c);
        up = num[1][succ] + num[0][succ] >= 10;
        if (a + up >= 10)
            up1 = true;
        if (a != 9)
            ss.erase(c);
        num[r - 1][c] = d;
        int b = num[1][c] + num[0][c];
        if (b != 9)
            ss.insert(c);
        succ = *ss.upper_bound(c);
        up = num[1][succ] + num[0][succ] >= 10;
        if (b + up >= 10)
            up2 = true;
        ans2 = (b + up) % 10;
        if (a == b)
        {
            cout << ans2 << ' ' << 0 << endl;
            continue;
        }
        if (up1 ^ up2)
        {
            int pred = *(--ss.lower_bound(c));
            ans1 += c - pred;
            if (pred == 0)
                ans1--;
        }
        cout << ans2 << ' ' << ans1 << endl;
    }
}

int main()
{
    IOS;
    cin >> N >> Q >> S[0] >> S[1];
    solve();

    return 0;
}