zoukankan      html  css  js  c++  java
  • bzoj4069 [Apio2015]巴厘岛的雕塑

    4069: [Apio2015]巴厘岛的雕塑

    Time Limit: 10 Sec  Memory Limit: 64 MB
    Submit: 468  Solved: 229
    [Submit][Status][Discuss]

    Description

    印尼巴厘岛的公路上有许多的雕塑,我们来关注它的一条主干道。
    在这条主干道上一共有 N 座雕塑,为方便起见,我们把这些雕塑从 1 到 N 连续地进行标号,其中第 i 座雕塑的年龄是 Yi 年。为了使这条路的环境更加优美,政府想把这些雕塑分成若干组,并通过在组与组之间种上一些树,来吸引更多的游客来巴厘岛。
    下面是将雕塑分组的规则:
    这些雕塑必须被分为恰好 X 组,其中 A< = X< = B,每组必须含有至少一个雕塑,每个雕塑也必须属于且只属于一个组。同一组中的所有雕塑必须位于这条路的连续一段上。
    当雕塑被分好组后,对于每个组,我们首先计算出该组所有雕塑的年龄和。
    计算所有年龄和按位取或的结果。我们这个值把称为这一分组的最终优美度。
    请问政府能得到的最小的最终优美度是多少?
    备注:将两个非负数 P 和 Q 按位取或是这样进行计算的:
    首先把 P 和 Q 转换成二进制。
    设 nP 是 P 的二进制位数,nQ 是 Q 的二进制位数,M 为 nP 和 nQ 中的最大值。P 的二进制表示为 pM−1pM−2…p1p0,Q 的二进制表示为 qM−1qM−2…q1q0,其中 pi 和 qi 分别是 P 和 Q 二进制表示下的第 i 位,第 M−1 位是数的最高位,第 0 位是数的最低位。
    P 与 Q 按位取或后的结果是: (pM−1  OR  qM−1)(pM−2 OR qM−2)…(p1 OR q1)(p0 OR q0)。其中:
    0 OR 0=0
    0 OR 1=1
    1 OR 0=1
    1 OR 1=1

    Input

    输入的第一行包含三个用空格分开的整数 N,A,B。

     
    第二行包含 N 个用空格分开的整数 Y1,Y2,…,YN。
     

    Output

    输出一行一个数,表示最小的最终优美度。

     

    Sample Input

    6 1 3
    8 1 2 1 5 4

    Sample Output

    11

    explanation

    将这些雕塑分为 2 组,(8,1,2) 和 (1,5,4),它们的和是 (11) 和 (10),最终优美度是 (11 OR 10)=11。(不难验证,这也是最终优美度的最小值。)

    HINT

     子任务 1 (9 分)


    1< = N< = 20

    1< = A< = B< = N

    0< = Yi< = 1000000000

    子任务 2 (16 分)

    1< = N< = 50

    1< = A< = B< = min{20,N}

    0< = Yi< = 10

    子任务 3 (21 分)

    1< = N< = 100

    A=1

    1< = B< = N

    0< = Yi< = 20

    子任务 4 (25 分)

    1< = N< = 100

    1< = A< = B< = N

    0< = Yi< = 1000000000

    子任务 5 (29 分)

    1< = N< = 2000

    A=1

    1< = B< = N

    0< = Yi< = 1000000000
    分析:非常好的一道题.
       分段求最值,显然是用dp.一开始我想着用常用的状态f[i][j]表示前i个数分成了j段的答案,那么f[i][j] = min{f[k][j - 1] | (sum[i] - sum[k - 1]),f[k][j]}. O(n^3)的做法. 
       这道题显然不能用上面的方法.这道题的特殊之处在于它的操作是or操作.一般遇到and,or,xor操作求最值就考虑把数变成二进制,一位一位地确定,判断可行性. 对于这道题的做法也是一样了,从高位到低位依次确定,每次确定当前位是否能为0,如果不行就为1,继续判断下一位. 那么状态就变成了f[i][j]表示前i个数分成了j段且当前位以及前位是否都符合条件.这里不单单要使得当前位满足条件,之前确定的位也必须满足条件!
       如何判断前位是否满足条件呢? 如果前位被确定是0,那么sum[i] - sum[k-1]在这一位上就必须是0,位运算|判断一下就好了. 
       最后看f[n][i]是否有一个为1就可以了(A ≤ i ≤ B),时间复杂度是O(n^2)的.
       上面这个算法过不了子问题五.子问题五的特殊点在于A = 1.之前的做法用第二维来保证最后分的段数一定在[A,B]之间.状态很多,但是状态表示的答案很少(只有0/1).现在既然A=1了,就说明不需要考虑下界了,那么满足条件的情况下使得划分的最少段数≤B就可以了.
    令g[i]表示前i个数的可行划分方案的最少段数. 如果[j,i]可以划分成一段,那么g[i] = min{g[i],g[j] + 1}.最后看g[n]是否≤B即可.
       理一下思路:由or操作想到在二进制数上考虑.  由求最值可以想到在二进制上从高位到低位逐位确定判断可行性,想到一个二维dp的方法. 子问题五由A=1可以想到让段数尽量小,调换状态和其表示的答案,其实就是可行性与最优性的转化,只不过第一个dp不能直接用最优性做,而必须要用可行性来做.
       小细节:
       1.位运算不要挤在一起写,优先级容易出问题.
       2.要得到2^i,可以用1<<i,但是当i比较大的时候,要用1LL << i. 一个比较好的做法是直接开一个数组保存.
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    typedef long long ll;
    const ll maxn = 2010,inf = 1e17;
    ll n,A,B,a[maxn],sum[maxn],ans,f[maxn][maxn],g[maxn],len;
    
    void solve1()
    {
        while (len)
        {
            for (ll i = 1; i <= n; i++)
                for (ll j = 1; j <= i; j++)
                    f[i][j] = 0;
            f[0][0] = 1;
            for (ll i = 1; i <= n; i++)
                for (ll j = 1; j <= i; j++)
                    for (ll k = 0; k < i; k++)
                    {
                        if (f[k][j - 1])
                        {
                            ll t = sum[i] - sum[k];
                            ll temp = t & (1LL << (len - 1));
                            if (temp == 0 && (ans | (t >> len)) == ans)
                                f[i][j] = 1;
                        }
                    }
            bool flag = false;
            for (ll i = A; i <= B; i++)
                if (f[n][i])
                {
                    flag = true;
                    break;
                }
            ans *= 2;
            if (!flag)
                ans |= 1;  //这一位就是1了
            len--;
        }
    }
    
    void solve2()
    {
        while (len)
        {
            for (ll i = 1; i <= n; i++)
                g[i] = inf;
            g[0] = 0;
            for (ll i = 1; i <= n; i++)
                for (ll j = 0; j < i; j++)
                {
                    ll t = sum[i] - sum[j];
                    ll temp = t & (1LL << (len - 1));
                    if (temp == 0 && (ans | (t >> len)) == ans)
                        g[i] = min(g[i],g[j] + 1);
                }
            ans *= 2;
            if (g[n] > B)
                ans |= 1;
            len--;
        }
    }
    
    int main()
    {
        scanf("%lld%lld%lld",&n,&A,&B);
        for (ll i = 1; i <= n; i++)
        {
            scanf("%lld",&a[i]);
            sum[i] = sum[i - 1] + a[i];
        }
        ll t = sum[n];
        while (t)
        {
            len++;
            t >>= 1;
        }
        if (A != 1)
            solve1();
        else
            solve2();
        printf("%lld
    ",ans);
    
        return 0;
    }
       
  • 相关阅读:
    Java实现 LeetCode 30 串联所有单词的子串
    Java实现 LeetCode 29 两数相除
    Java实现 LeetCode 29 两数相除
    Java实现 LeetCode 29 两数相除
    Java实现 LeetCode 28 实现strStr()
    Java实现 LeetCode 28 实现strStr()
    Java实现 LeetCode 28 实现strStr()
    Java实现 LeetCode 27 移除元素
    Java实现 LeetCode 27 移除元素
    字符编码终极笔记:ASCII、Unicode、UTF-8、UTF-16、UCS、BOM、Endian
  • 原文地址:https://www.cnblogs.com/zbtrs/p/8495170.html
Copyright © 2011-2022 走看看