zoukankan      html  css  js  c++  java
  • 【高手训练】数位DP系列(暂完)

    【高手训练】数位(dp)学习笔记

    <前言>

    也就是不停讲题写题,没讲什么实质性数位(dp),甚至只有后面三题有点点味道。

    前面就是用到了数位的相关知识而已。

    <正文>

    【高手训练】【动态规划】最大值

    题目大意

    多组数据,每组几个操作(opt)与n个数,(opt)可能是(and、xor、or),求任意两数(opt)运算后的最大值。

    Solution

    显然我们得分操作进行。


    (Xor)

    (opt=xor)时,你会发现这题十分熟悉。

    The XOR Largest Pair(随手网上找的OJ)

    我们发现对于一个0/1,我们需要找到和它相对的方向走

    比如当一位为1,我们要有最大代价,就应该找0与之形成贡献。

    然而对于找不到相对值的位置,没有办法,只能找相同数了

    直接一个(Trie)树板子套上去就行了。

    (mathrm{Code:})

    struct trie
    {
        int tr[N << 1][2];
        int cnt = 0;
        inline void inc(int x)
        {
            int now = 0;
            for(int i = 30; i >= 0; --i)
            {
                int t = x >> i & 1;
                if(!tr[now][t])tr[now][t] = ++cnt;
                now = tr[now][t];
            }
        }
        inline int ask(int x)
        {
            int now = 0, ans = 0;
            for(int i = 30; i >= 0; --i)
            {
                int t = x >> i & 1;
                if(tr[now][t ^ 1])
                    ans += 1 << i, now = tr[now][t ^ 1];
                else now = tr[now][t];
            }
            return ans;
        }
        inline void clear()
        {
            memset(tr, 0, sizeof(tr));
            cnt = 0;
        }
    } tr;
    

    (And)

    (直接念题解:)

    从高位到低位贪心。毕竟and的限制性还是比较强的。

    • 对于某一位,如果在没被删除的数中1的个数超过2个,那么上下那些不合法的数随便删,因为就算那些数接下来的1再多,造成的贡献也不及这一次保留((2^{i-1}+2^{i-2}...+2^0=2^i-1<2^i))。

      然后累计当前二进制位的贡献((ans+=(1<<i)))。

    • 如果当前位是1的数的个数不到两个,那么删掉只会造成贡献变小,则不删,直接考虑下一位。

    (mathrm{Code:})

    int vis[N] = {};
    for(int i = 30; i >= 0; --i){
        int cnt = 0, op = 0;
        for(int j = 1; j <= n; ++j)
            if(!vis[j]){
                ++op;
                cnt += a[j] >> i & 1;
            }
        if(op <= 2)break;
        if(cnt > 1){
             for(int j = 1; j <= n; ++j)
                if(!vis[j] && ((a[j] >> i & 1 ) == 0))
                    vis[j] = 1;
        }
    }
    int ans = (1 << 30) - 1;
    for(int i = 1; i <= n; ++i)
        if(!vis[i])ans &= a[i];
    

    (Or)

    or比较烦,因为限制少,答案难求,但我们依然从高位到低位贪心。

    考虑每次都尽量取优。枚举某个数的不同位。

    • 若当前枚举的位为1,那么另一数可以是0或1。
    • 若当前枚举的位位0,那么另一数尽量取1。

    我们对于每个数,找到满足最优条件(如上规则)的情况下,最小的一个值,记为(val)。可以理解为:当某一位可选可不选的时候,让它为0.

    这么搞出来的(val)一定是某个可行解的子集。

    • 处理子集,设(F[x])表示是否存在一个(a_i)满足(x∈a_i)

    • 当某一位(i)满足存在某个(a_i)使(val|2^i∈a_i),则第(i)位可以为1.这个相当于存在这么一种方案使第i位为1.

      贡献加上。

    最后拼出的答案即为所求。

    状压(dp)(mathrm{Code:})

    memset(f, 0, sizeof(f));
    for(int i = 1; i <= n; ++i)f[a[i]] = 1;
    for(int i = (1 << 20) - 1; i >= 0; --i)
        for(int j = 0; j <= 20; ++j)
            f[i] |= f[i | (1 << j)];
    

    Code

    完整代码:

    #include<bits/stdc++.h>
    #define N 200010
    #define int long long
    
    int n, a[N] = {};
    int k;
    
    inline int read()
    {
        int s = 0, w = 1;
        char c = getchar();
        while((c < '0' || c > '9') && c != '-')
            c = getchar();
        if(c == '-')w = -1, c = getchar();
        while(c <= '9' && c >= '0')
            s = (s << 3) + (s << 1) + c - '0', c = getchar();
        return s * w;
    }
    void write(int x)
    {
        if(x > 9)write(x / 10);
        putchar(x % 10 + 48);
    }
                        //IO
    int f[1 << 21] = {};
    struct trie
    {
        int tr[N << 1][2];
        int cnt = 0;
        inline void inc(int x)
        {
            int now = 0;
            for(int i = 30; i >= 0; --i)
            {
                int t = x >> i & 1;
                if(!tr[now][t])tr[now][t] = ++cnt;
                now = tr[now][t];
            }
        }
        inline int ask(int x)
        {
            int now = 0, ans = 0;
            for(int i = 30; i >= 0; --i)
            {
                int t = x >> i & 1;
                if(tr[now][t ^ 1])
                    ans += 1 << i, now = tr[now][t ^ 1];
                else now = tr[now][t];
            }
            return ans;
        }
        inline void clear()
        {
            memset(tr, 0, sizeof(tr));
            cnt = 0;
        }
    } tr;
                  //trie树
    
    void work()
    {
        n = read();
        k = read();
        for(int i = 1; i <= n; ++i)
            a[i] = read();
        if(k == 1)
        {
            int vis[N] = {};
            for(int i = 30; i >= 0; --i)
            {
                int cnt = 0, op = 0;
                for(int j = 1; j <= n; ++j)
                    if(!vis[j])
                    {
                        ++op;
                        cnt += a[j] >> i & 1;
                    }
                if(op <= 2)break;
                if(cnt > 1)
                {
                    for(int j = 1; j <= n; ++j)
                        if(!vis[j] && ((a[j] >> i & 1 ) == 0))
                            vis[j] = 1;
                }
            }
            int ans = (1 << 30) - 1;
            for(int i = 1; i <= n; ++i)
                if(!vis[i])ans &= a[i];
            write(ans);
            putchar(10);
            return ;
        }
        if(k == 2)
        {
            tr.clear();
            for(int i = 1; i <= n; ++i)
                tr.inc(a[i]);
            int ans = 0;
            for(int i = 1; i <= n; ++i)
                ans = std::max(ans, tr.ask(a[i]));
            write(ans);
            putchar(10);
            return ;
        }
        if(k == 3)
        {
            memset(f, 0, sizeof(f));
            for(int i = 1; i <= n; ++i)f[a[i]] = 1;
            for(int i = (1 << 20) - 1; i >= 0; --i)
                for(int j = 0; j <= 20; ++j)
                    f[i] |= f[i | (1 << j)];
            int sum = 0, val = 0;
            for(int i = 1; i <= n; ++i)
            {
                val = 0;
                for(int j = 20; j >= 0; --j)
                    if(!((a[i] >> j) & 1) && f[val | (1 << j)])
                        val |= 1 << j;
                sum = std::max(sum, a[i] | val);
            }
            write(sum);
            putchar(10);
        }
    }
    main()
    {
        freopen("maxium.in", "r", stdin);
        freopen("maxium.out", "w", stdout);
        int T = read();
        while(T--)
            work();
        return 0;
    }
    

    【高手训练】【动态规划】寻找整数

    题目大意

    给定整数(m,k),求出正整数(n)使得(n+1,n+2,...,2n)中恰好有个(m)数在二进制下恰好有(k)(1)。有多组数据。

    Solution

    这种题一上来就没头没脑的,该怎么做?

    稍微手推几组相邻答案,尝试寻找关联。

    • (Num(x))为区间([x+1,2x])之间的二进制数中1的个数为(k)的个数
    • 我们通过观察发现(Num(x))具有单调性。

    感性理解:

    image-20200525182531104

    而我们通过前缀和的方式求解(Num(n))

    • (S(x))([1,x])中二进制位(k)的个数。
    • (Num(x)=S(2x)-S(x))

    (S(x))

    • 从高位到低位考虑,每个小于(等于)x的数。钦定当前位为1,则前面几位都和(x+1)一样.那么对于后面的位置可以计算方案。
    • 记录剩下位数(n)和前面1的个数(m),方案为(mathrm{C(k-m,n)})

    对于答案

    • 二分极值,然后相减。
    • 特判:(m=0)(2^k-1)(恰好(k)(1)
    • (m=1且k=1)时特判(-1)(无数组解)

    (mathrm{Code:})

    #include <bits/stdc++.h>
    #define int long long
    using namespace std;
    int n, m;
    
    int read() {
        int s = 0, w = 1;
        char c = getchar();
        while ((c < '0' || c > '9') && c != '-') c = getchar();
        if (c == '-') w = -1, c = getchar();
        while (c <= '9' && c >= '0')
            s = (s << 3) + (s << 1) + c - '0', c = getchar();
        return s * w;
    }
    void write(int x) {
        if (x > 9) write(x / 10);
        putchar(x % 10 + 48);
    }
    int c[85][85];
    inline int ask(int x) {
        int ans = 0, r = 0;
        ++x;
        for (int i = 63; ~i; --i)
            if (x >> i & 1LL) {
                if (m >= r) ans += c[i][m - r];
                ++r;
            }
        return ans;
    }
    
    inline bool check(int x) { return ask(x << 1) - ask(x) >= n; }
    
    int find() {
        int l = 0, r = 2e18, mid, ans = 0;
        while (l <= r) {
            mid = (l + r) >> 1;
            if (check(mid))
                ans = mid, r = mid - 1;
            else
                l = mid + 1;
        }
        return ans;
    }
    
    void work(void) {
        n = read();
        m = read();
        if (n == 0) {
            write(1);
            putchar(32);
            write((1LL << m - 1) - 1);
            putchar(10);
            return void();
        }
        if (n == 1 && m == 1) {
            puts("1 -1");
            return void();
        }
        int k1 = find();
        ++n;
        int k = find();
        write(k1);
        putchar(32);
        write(k - k1);
        putchar(10);
        return void();
    }
    void pre(void) {
        for (int i = 0; i <= 64; ++i) c[i][0] = 1;
        for (int i = 1; i <= 64; ++i)
            for (int j = 1; j <= i; ++j) c[i][j] = c[i - 1][j - 1] + c[i - 1][j];
    }
    main() {
        freopen("num.in", "r", stdin);
        freopen("num.out", "w", stdout);
        pre();
        int T = read();
        while (T--) work();
        return 0;
    }
    

    【高手训练】【动态规划】好数字

    题目大意

    一个数字被称为好数字需满足下列条件:

    ①它有个(2 imes n)数位,(n)是正整数(允许有前导(0))。

    ②构成它的每个数字都在给定的数字集合(S)中。

    ③它前(n)位之和与后(n)位之和相等或者它奇数位之和与偶数位之和相等

    例如,对于(n=2)(S={1,2}),合法的好数字有(8)个:

    (1111)(1122)(1212)(1221)(2112)(2121)(2211)(2222)

    已知,求合法的好数字个数(mod 999983)

    Solution

    我们可以通过一些操作得出一些奇怪结论。

    • (前n位=后n位)(奇数位=偶数位) == (前n位=后n位) + (奇数位=偶数位) - (前n位=后n位)(奇数位=偶数位)

    • (f(n,m))为用给定数字集拼出(m)的方案数,可以用01背包(mb)预处理。

    • 我们发现两者本质其实相同,答案都为

    • [sum_{i=1}^{max}f^2(n,i) ]

      (不想手打了浪费时间)

      image-20200525210302388

    然后就tm瞎算。

    (mathrm{Code:})

    #include <bits/stdc++.h>
    #define N 1010
    #define mod 999983
    #define int long long
    using namespace std;
    int n, m;
    
    inline int add(int a, int b) { return a + b >= mod ? a + b - mod : a + b; }
    inline int del(int a, int b) { return a - b <= 0 ? a - b + mod : a - b; }
    
    int read() {
        int s = 0, w = 1;
        char c = getchar();
        while ((c < '0' || c > '9') && c != '-') c = getchar();
        if (c == '-') w = -1, c = getchar();
        while (c <= '9' && c >= '0')
            s = (s << 3) + (s << 1) + c - '0', c = getchar();
        return s * w;
    }
    void write(int x) {
        if (x > 9) write(x / 10);
        putchar(x % 10 + 48);
    }
    int a[11] = {}, cnt = 0;
    int f[N][N * 10] = {};
    void work() {
        n        = read();
        int maxn = 0;
        char c   = getchar();
        while (c < '0' || c > '9') c = getchar();
        while (c <= '9' && c >= '0') a[++cnt] = c - '0', c = getchar();
        for (int i = 1; i <= cnt; ++i) maxn = max(maxn, a[i]);
        for (int i = 1; i <= cnt; ++i) f[1][a[i]] = 1;
        for (int i = 2; i <= n; ++i)
            for (int j = 0; j <= i * maxn; ++j)
                for (int k = 1; k <= cnt; ++k)
                    if (j >= a[k]) f[i][j] = add(f[i][j], f[i - 1][j - a[k]]);
        int ans = 0;
        for (int i = 0; i <= n * maxn; ++i)
            f[n][i] ? ans = add(ans, 2 * f[n][i] % mod * f[n][i] % mod) : 0;
        int mid = n >> 1, res = n - mid, s1 = 0, s2 = 0;
        for (int i = 0; i <= mid * maxn; ++i)
            f[mid][i] ? s1 = add(s1, f[mid][i] * f[mid][i] % mod) : 0;
        for (int i = 0; i <= res * maxn; ++i)
            f[res][i] ? s2 = add(s2, f[res][i] * f[res][i] % mod) : 0;
        ans = del(ans, s1 ? 1LL * s1 * s2 % mod : s2);
        write(ans);
        putchar(10);
    }
    main() {
        freopen("number.in", "r", stdin);
        freopen("number.out", "w", stdout);
        work();
        return 0;
    }
    

    <后记>

    后面的题都没写了

    虽然真正的数位(dp)都在后面,但是我写不动了。

    鸽了吧。

  • 相关阅读:
    vue.js 第二课
    vue.js学习(第一课)
    2016-11-14看张大神的微博总结
    这几天的工作总结:
    调了一天的兼容总结下
    鸭式辩论
    prototype 原型
    前端ps常用的小技巧
    Android的开始之相对布局
    Android的开始之线性布局
  • 原文地址:https://www.cnblogs.com/yywxdgy/p/12957221.html
Copyright © 2011-2022 走看看