1. 程式人生 > >洛谷3646 APIO2015 巴厘島的雕塑 位運算 貪心 dp

洛谷3646 APIO2015 巴厘島的雕塑 位運算 貪心 dp

題目連結
題意:
有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;
}