zoukankan      html  css  js  c++  java
  • 【10.29校内测试】【线段树】【DP】【二进制Trie树求最小值最大】

    Solution

    标程太暴力惹QAQ 相当于是26棵线段树的说QAQ

    不过我写了另一种写法,从大到小枚举每一个字母,标记字典序在这个字母之上的位置为1,每次都建一棵线段树,维护1的数量,即区间和。

    修改操作就是先查询这个区间1的数量,排序本质上就是把1一起放在这个区间前面或后面,最后查询每个位置,如果为1并且没有被标记过,就标记成当前枚举的字母即可。

    将看似复杂的问题转化为了简单的区间修改和查询QAQ

    不过需要各种常数优化才能过QAQ

    Code

    #include<bits/stdc++.h>
    #define RG register
    using namespace std;
    
    int TR[400005], tag[400005];
    
    char a[100005];
    int s[100005];
    
    inline int read() {
        int x = 0;    char ch = getchar();
        while(ch > '9' || ch < '0')    ch = getchar();
        while(ch >= '0' && ch <= '9') {
            x = x * 10 + ch - '0';    ch = getchar();
        }
        return x;
    }
    
    inline void update(int nd) {
        TR[nd] = TR[nd << 1] + TR[nd << 1 | 1];
    }
    
    inline void push_down(int nd, int l, int r) {
        if(~tag[nd]) {
            int mid = (l + r) >> 1;
            TR[nd << 1] = tag[nd] * (mid - l + 1);
            TR[nd << 1 | 1] = tag[nd] * (r - mid);
            tag[nd << 1] = tag[nd];
            tag[nd << 1 | 1] = tag[nd];
            tag[nd] = -1;
        }
    }
    
    inline void build(int nd, int l, int r) {
        tag[nd] = -1;
        if(l == r) {
            TR[nd] = s[l];    return ;
        }
        int mid = (l + r) >> 1;
        build(nd << 1, l, mid);
        build(nd << 1 | 1, mid + 1, r);
        update(nd);
    }
    
    inline int query(int nd, int l, int r, int L, int R) {
        if(TR[nd] == 0)    return 0;
        if(L > R)    return 0;
        if(l >= L && r <= R) {
            int tmp = TR[nd];
            TR[nd] = 0; tag[nd] = 0;
            return tmp;
        }
        push_down(nd, l, r);
        int mid = (l + r) >> 1; int ans = 0;
        if(L <= mid)    ans += query(nd << 1, l, mid, L, R);
        if(R > mid)        ans += query(nd << 1 | 1, mid + 1, r, L, R);
        update(nd);
        return ans;
    }
    
    inline void modify(int nd, int l, int r, int L, int R, int d) {
        if(L > R)    return ;
        if(l >= L && r <= R) {
            tag[nd] = d;
            TR[nd] = (r - l + 1) * d;
            return ;
        }
        push_down(nd, l, r);
        int mid = (l + r) >> 1;
        if(L <= mid)    modify(nd << 1, l, mid, L, R, d);
        if(R > mid)        modify(nd << 1 | 1, mid + 1, r, L, R, d);
        update(nd);
    }
    
    int d[100005];
    inline void get(int nd, int l, int r) {
        if(l == r) {
            d[l] = TR[nd];    return ;
        }
        push_down(nd, l, r);
        int mid = (l + r) >> 1;
        get(nd << 1, l, mid);    get(nd << 1 | 1, mid + 1, r);
    }
    
    int n, m, l[100005], r[100005], x[100005], color[100005];
    int main() {
        freopen("string.in", "r", stdin);
        freopen("string.out", "w", stdout);
        n = read(), m = read();
        scanf("%s", a + 1);
        for(int i = 1; i <= m; i ++)
            l[i] = read(),    r[i] = read(), x[i] = read();
        for(RG int i = 25; i >= 0; i --) {
            for(RG int j = 1; j <= n; j ++)
                if(a[j] >= i + 'a')    s[j] = 1;
                else    s[j] = 0;
            build(1, 1, n);
            for(RG int j = 1; j <= m; j ++) {
                int delta = query(1, 1, n, l[j], r[j]);
                if(x[j] == 0) {
                    if(delta > 0)    modify(1, 1, n, l[j], l[j] + delta - 1, 1);
                }
                else {
                    if(delta > 0)    modify(1, 1, n, r[j] - delta + 1, r[j], 1);
                }
            }
            get(1, 1, n);
            for(RG int j = 1; j <= n; j ++) {
                if(d[j] && !color[j])    color[j] = i;
            }
        }
        for(int i = 1; i <= n; i ++)
            printf("%c", color[i] + 'a');
        return 0;
    }

    Solution

    转移可以说是很难想了QAQ

    列上的限制比行上明显要少,所以定义$dp[i][j]$表示当前扫到了第$i$列,当前没有填数的右区间有$j$个时的方案数。这个右区间指$r[k]$在$1~i$范围内的区间,左区间同理。

    从前往后有当前放数或不放数两个情况,放数还分左区间放和右区间放。详情看代码吧QAQ

    Code

    #include<bits/stdc++.h>
    #define LL long long
    #define mod 998244353
    using namespace std;
    
    int dp[3005][3005];
    int l[3005], r[3005], n, m, x, y;
    
    int main() {
        freopen("matrix.in", "r", stdin);
        freopen("matrix.out", "w", stdout);
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i ++)    scanf("%d%d", &x, &y), l[x] ++, r[y] ++;
        dp[0][0] = 1; int tot = 0, tmp = 0;
        for(int i = 1; i <= m; i ++) {
            tmp ++;
            tot += r[i];
            for(int j = r[i]; j <= tot; j ++)
                dp[i][j] = dp[i - 1][j - r[i]];
            for(int j = 1; j <= tot; j ++)
                dp[i][j - 1] = (dp[i][j - 1] + 1ll * dp[i][j] * j % mod) % mod;
            for(int j = 1; j <= l[i]; j ++) {
                for(int k = 0; k <= tot; k ++)
                    dp[i][k] = 1ll * dp[i][k] * max(0, tmp - (tot - k)) % mod;
                tmp --;
            }
        }
        printf("%d", dp[m][0]);
        return 0;
    }

    Solution

    看到这种位运算就想到$Trie$树啥的,可是怎么建树还有怎么扫都毫无思路啊QAQ

    首先还是从式子入手,对手改变一次的那个式子中,当$x<2^{n-1}$时,原式相当于将$x$左移一位,反之相当于将$x$左移一位再加一,具体写一写就知道了。

    这就相当于在断点的位置,把$x$的最高位取下来放到最后。

    又因为前面总贡献和后面总贡献都是可以预处理出来的前后缀异或和,再加上按位运算,只会影响一位,与其它位无关,所以可以$O(m)$处理出来每个断点的贡献,将所有贡献加入$Trie$数中。

    然后考虑怎么走$x$。因为对手想让结果尽量小,所以他一定会尽量让选择的$x$往和$Trie$数上节点相同的地方走。

    所以$dfs$遍历trie树时,如果两边儿子都有,那么这一层不管怎么选都不会有贡献,如果有一边的儿子,那么就可以获得另一边的贡献。但是遍历还是只能顺着这边遍历下去,遍历与计算贡献无关。

    Code

    #include<bits/stdc++.h>
    using namespace std;
    
    int t[33], tot;
    int n, m;
    int son[6000005][2], tail;
    int pre[100005], las[100005], a[100005];
    
    void add(int x) {
        memset(t, 0, sizeof(t)); tot = 0;
        while(x) {
            t[++tot] = (x & 1);
            x >>= 1;
        }
        int nd = 0;
        for(int i = n; i >= 1; i --) {
            if(!son[nd][t[i]])    son[nd][t[i]] = ++ tail;
            nd = son[nd][t[i]];
        }
    }
    
    int ans1, ans2;
    void dfs(int nd, int val, int depth) {
        if(depth == -1) {
            if(val > ans1)    ans1 = val, ans2 = 1;
            else if(val == ans1)    ans2 ++;
            return ;
        }
        if(son[nd][0] && son[nd][1]) {
            dfs(son[nd][0], val, depth - 1);
            dfs(son[nd][1], val, depth - 1);
        } else if(son[nd][0])    dfs(son[nd][0], val + (1 << depth), depth - 1);
          else if(son[nd][1])    dfs(son[nd][1], val + (1 << depth), depth - 1);
    }
    
    int main() {
        freopen("big.in", "r", stdin);
        freopen("big.out", "w", stdout);
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= m; i ++)    scanf("%d", &a[i]), pre[i] = pre[i - 1] ^ a[i];
        for(int i = m; i >= 1; i --)    las[i] = las[i + 1] ^ a[i];
        for(int i = 0; i <= m; i ++) {
            int now = pre[i] << 1;
            if(1 & (now >> n)) now = (now ^ (1 << n)) | 1;    
            now ^= las[i + 1];
            add(now);
        }
        dfs(0, 0, n - 1);
        printf("%d
    %d", ans1, ans2);
        return 0;
    } 
  • 相关阅读:
    Java中NIO和IO区别和适用场景
    JDK和CGLIB动态代理原理
    java中的Serializable接口的作用
    redis采用序列化方案存对象
    在时间复杂度为O(n)且空间复杂度为O(1)的情况下翻转链表
    给定一个排好序的数组,然后求出丢失的数字
    求字符串里超过字符长度一半的元素
    求你给定两字符串包含的字母数是否完全一致
    动态规划,求数组不相邻数字的最大子串值
    JWT 工具
  • 原文地址:https://www.cnblogs.com/wans-caesar-02111007/p/9872489.html
Copyright © 2011-2022 走看看