洛谷3646 APIO2015 巴厘岛的雕塑 位运算 贪心 dp

版权声明:本文为博主原创文章,可以转载但是必须声明版权。 https://blog.csdn.net/forever_shi/article/details/83586412

题目链接
题意:
有n个雕塑,每个雕塑树都有一个美丽程度,将其分为m组,其中m是介于A~B之间的一个数。每组至少有一个雕塑且所选的雕塑是连续的,每个雕塑一定在某个组中。对于一组,令ai表示该组中雕塑的美丽值之和。合理分配使得ai的按位取或的值最小。
part1:n<=100,1<=A<=B<=n。
part2:n<=2000,A=1,1<=B<=n。 n^2*32

题解:
这篇题解也许不是普适性最高的题解,但是是一篇充分利用了题目的特殊条件的题解。

我们发现题目的数据范围包含两个部分。于是我们考虑数据分治。

对于第一部分,我们先预处理一个前缀和,对于这种涉及位运算的题,一个非常重要的思路就是按位考虑。这个题我们对每一位进行考虑,我们贪心地尽可能让高位的按位或结果是0。然后我们考虑dp,我们设dp[i][j]表示前i个数,分成j组是否可以让当前位是0,我们在转移时枚举一个k(1<=k<=i-1),看dp[i][j]是否能从dp[i][k]转移过来。判断是否能转移过来的条件是当前这一段数的前缀和这一位是0并且这一段前缀和的更高位与已经求出的答案的最高的几位是一样的。最后看一下前n个数中是否有一种划分成A到B段的方法,如果有就这一位填0,没有的话这一位就只能是1。
这样我们就可以n^3*位数来做这一部分了。

对于第二部分,我们发现A=1,也就是没有了下限,分成几组都可以。我们这次还是从高位到低位贪心,但是dp的含义不同了,我们设dp[i]为前i个数最少分成几段才能满足这一位是0的要求,转移方法也是枚举一个j(1<=j<=i-1),看从j+1到i这一段是否能形成一段,判断方法与上一部分判断转移的方法类似。这样我们就可以n^2*位数来做这一部分了。

然后把两部分打一个数据分治就可以了。

代码:

#include <bits/stdc++.h>
using namespace std;

int n,a,b;
int len;
long long dp2[2010],dp1[110][110],s[2010],ans,c[2010];
inline void solve1()
{
    for(int l=len;l>=1;--l)
    {
        memset(dp1,0,sizeof(dp1));
        dp1[0][0]=1;
        for(int i=1;i<=n;++i)
        {
            for(int j=1;j<=i;++j)
            {
                for(int k=j-1;k<=i-1;++k)
                {
                    if(dp1[k][j-1])
                    {
                        long long ji=s[i]-s[k];
                        if((ji&(1ll<<(l-1)))==0&&((ji>>l)|ans)==ans)
                        {
                            dp1[i][j]=1;
                            break;
                        }
                    }									
                }
            }
        }
        int pd=0;
        for(int i=a;i<=b;++i)
        {
            if(dp1[n][i])
            {
                pd=1;
                break;
            }
        }
        ans<<=1;
        if(pd==0)
        ans|=1;
    }	
}
inline void solve2()
{
    for(int l=len;l>=1;--l)
    {
        memset(dp2,0x3f,sizeof(dp2));
        dp2[0]=0;
        for(int i=1;i<=n;++i)
        {
            for(int j=0;j<=i-1;++j)
            {
                long long ji=s[i]-s[j];
                if(((ji>>l)|ans)==ans&&(ji&(1ll<<(l-1)))==0)
                dp2[i]=min(dp2[i],dp2[j]+1);
            }
        }
        ans<<=1;
        if(dp2[n]>b)
        ans|=1;
    }
}
int main()
{
    scanf("%d%d%d",&n,&a,&b);
    for(int i=1;i<=n;++i)
    scanf("%lld",&c[i]);
    for(int i=1;i<=n;++i)
    s[i]=s[i-1]+c[i];
    for(long long i=s[n];i;i>>=1)
    ++len;
    if(a!=1)
    solve1();
    else
    solve2();
    printf("%lld\n",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/forever_shi/article/details/83586412