牛客练习赛91(并查集+二维偏序+二分)


BC.魔法学院

题意

亚可喜欢上了收集不包括空格的可见字符(ASCII码为33~126),在她眼中,一个字符的价值为其ASCII码大小,比如’a’的价值为97。

目前她已经收集了nn个不包括空格的可见字符,第ii个字符为SiS_{i}。可是她想要把自己收集的nn个字符的价值和最大化,因此去请求了戴安娜的帮助。

戴安娜有mm种魔法,第ii种魔法可以将[li,ri][l_{i},r_{i}]区间的一个字符替换为cic_{i}。因为戴安娜出色的魔力,所以每种魔法都可以使用无限次。

请问戴安娜使用完若干次魔法后,亚可收集的nn个字符的最大价值和可以是多少?

数据范围

\(1<=n<=10^7\)

\(1<=m<=10^6\)

多组测试样例

输入

3 3
aaa
1 3 b
2 3 c
3 3 d

输出

297

分析

按照字符 \(c\) 的价值降序排序,后面的字符一定不能覆盖前面的字符。

这样用并查集就可以解决了

代码

const int N = 2e7 + 5;
 
    int n, m, k, _;
    struct TMP
    {
        int l, r;
        char opt;
        bool operator<(TMP o){
            return opt > o.opt;
        }
        void read(){ sdd(l, r); sc(opt); }
        TMP(int l = 0, int r = 0, char ch = 0) : l(l), r(r), opt(ch){}
    };
    vector v;
    char ch[N];
    int fa[N];

int Find(int x)
{
    if(x == fa[x]) return x;
    return fa[x] = Find(fa[x]);
}

signed main()
{
    // IOS;
    while(~ sdd(n, m)){
        ss(ch + 1);
        ch[n + 1] = 0;
        rep(i, 1, m){
            TMP tmp;
            tmp.read();
            v.pb(tmp);
        }
        sort(v.begin(), v.end());
        for(int i = 1; i <= n + 1; i ++) fa[i] = i;
        ll ans = 0;
        int sz = v.size(), res = 0;
        for(int i = 0; i < sz; i ++){
            int x = v[i].l, y = v[i].r;
            int now = Find(x);
                while(now <= y){
                    ch[now] = max(v[i].opt, ch[now]);
                    fa[now] = Find(now + 1);
                    now = Find(now + 1);
                }
        }
        for(int i = 1; i <= n; i ++) ans += ch[i];
        pll(ans);
    }
    // PAUSE;
    return 0;
}






D.监狱逃亡

题意

勇者为救公主杀入魔塔,不料在魔塔三层遭遇魔王偷袭,失去了神圣剑与神圣盾,而自身也被关入监狱。

监狱是一块3×n3\times n的区域,每块格子都有一个价值。勇者目前在监狱的左上角(1,1)(1,1)处,而他需要逃亡到监狱的右下角(3,n)(3,n)处。

勇者每次可以向右或者向下移动一格,请问勇者逃亡到右下角,其路径价值和大于等于0的不同方法数一共有多少种,答案对10000000071000000007取模。

数据范围

\(1<=n<=5*10^5\)

\(-10^9<=a_i<=10^9\)

多组测试样例

输入

2
0 -1
-1 -1
-1 3

输出

3

分析

可以发现只会向下走两次,故可以得到

\(s_1[l]+s_2[r]-s_2[l-1]+s_3[n]-s_3[r-1]>=0\)

\(s_1[l]-s_2[l-1]+s_3[n]>=s_3[r-1]-s_2[r]\)

\(a[l]=s_1[l]-s_2[l-1]+s_3[n],\ b[r]=s_3[r-1]-s_2[r]\)

然后寻找 $a[i]>=b[j] && j >= i $ 的个数

树状数组+离散化就可以了

代码

const int mod = 1e9 + 7;
const int N = 1e6 + 5;
 
    int n, m, k, _;
    ll a[N], b[N];
    ll sum[5][N];
    vector v;
    ll c[N];

int getid(ll x)
{
    return lower_bound(v.begin(), v.end(), x) - v.begin() + 1;
}

void add(int i, int x)
{
    while(i < N){
        c[i] += x;
        i += lowbit(i);
    }
}

int ask(int i)
{
    int ans = 0;
    while(i){
        ans += c[i];
        i -= lowbit(i);
    }
    return ans;
}

ll adp(ll &x)
{
    x %= mod;
    x += mod;
    x %= mod;
    return x;
}

signed main()
{
    //IOS;
    while(~ sd(n)){
        for(int k = 1; k <= 3; k ++){
            for(int i = 1; i <= n; i ++){
                sd(m);
                sum[k][i] = m + sum[k][i - 1];
            }
        }
        for(int i = 1; i <= n; i ++){
            a[i] = sum[1][i] - sum[2][i - 1] + sum[3][n];
            b[i] = sum[3][i - 1] - sum[2][i];
            v.pb(a[i]);
            v.pb(b[i]);
        }
        sort(v.begin(), v.end());
        v.erase(unique(v.begin(), v.end()),v.end());
        rep(i, 1, n){
            a[i] = getid(a[i]);
            b[i] = getid(b[i]);
        }
        ll ans = 0;
        for(int i = 1; i <= n; i ++){
            add(a[i], 1);
            ans += (i - ask(b[i] - 1));
            adp(ans);
        }
        pll(ans);
    }
    // PAUSE;
    return 0;
}






E.游戏人生

题意

白正在玩的游戏到了boss阶段,该阶段一共有nn个回合,对于每个回合,由白先行动,然后boss再进行攻击。

每回合白可以选择下面一种方式进行操作:

1.普通攻击,对boss造成AA点伤害。

2.狂暴攻击,当自己生命值大于BB时,可以消耗BB点生命值对boss造成2×A2\times A点伤害。

3.防御,本回合boss对你造成的xx点伤害变为x2\frac{x}{2}(向下取整)。

经过攻略查询,现已知boss的初始生命值为hphp,以及每个回合对玩家造成的伤害为aia_{i}

游戏要求白必须在nn个回合内战胜boss(即在nn个回合的某个回合内使boss的生命值降为00或者更低),并保证这个过程中白的生命值一直大于00

在保证通关游戏的前提下,请计算白的最少初始生命值。若无法通关游戏,则输出?1-1

数据范围

\(1<=hp, A, B<=10^9\)

\(1<=n<=10^5\)

\(a_i<=10^9\)

多组测试样例

输入

1
5 1 1
3
1 1 1

输出

5

分析

当时看着就像个二分,这个贪心真是绝了

假设 \(x\) 为答案,那么每次都是狂暴攻击,直到 \(x\) 变为 \(0\), 然后将之前的狂暴攻击转为普通攻击,普通攻击还可以变成防御,这样每次 \(boss\) 都会加 \(A\),也就是说,此时我需要加最多的血量也就是 \(max(B, \frac{a[i]}{2}向上取整)\)

代码

const int N = 1e5 + 5;
 
    int n, m, k, _;
    ll a[N];
    ll H, A, B;

bool C(ll x)
{
    priority_queue q, maxx;
    ll hp = H;
    for(int i = 1; i <= n; i ++){
        if(hp - A <= 0) return 1;
        if(x > B && hp - 2 * A <= 0) return 1;
        hp -= 2 * A;
        x -= (a[i] + B);
        maxx.push(a[i]);
        if(x <= 0){
            while(x <= 0){
                if(q.empty() == 0 && q.top() >= B){
                    hp += A;
                    x += q.top();
                    q.pop();
                }
                else if(maxx.empty() == 0){
                    hp += A;
                    x += B;
                    q.push((maxx.top() + 1) / 2);
                    maxx.pop();
                }
                else if(q.empty() == 0){
                    hp += A;
                    x += q.top();
                    q.pop();
                }
                else return 0;
            }
        }
        if(hp <= 0 && x > 0) return 1;
    }
    return (hp <= 0 && x > 0);
}

signed main()
{
    //IOS;
    rush(){
        sll3(H, A, B);
        sd(n);
        rep(i, 1, n) sll(a[i]);
        ll l = 1, r = 1e18, ans = -1;
        // dbg(C(5));
        while(r >= l){
            ll mid = l + r >> 1;
            if(C(mid)){
                ans = mid;
                r = mid - 1;
            }
            else{
                l = mid + 1;
            }
        }
        pll(ans);
    }
    // PAUSE;
    return 0;
}