• [APIO2015]巴厘岛的雕塑 贪心+DP+特殊数据优化


    写了好久。。。。

    刚刚调了一个小时各种对拍,,,,最后发现是多写了一个等号,,,,内心拒绝

    表示一开始看真的是各种懵逼啊

    在偷听到某位大佬说的从高位开始贪心后发现可做

    首先考虑小数据(因为可以乱搞)

    所以先从高位开始枚举位数:

    for(int k=all; k ;k--)

    ans表示当前答案,f[i][j]表示dp到第i为,分为j段,是否可以满足第k位为0

    ans初始为0,每次DP完就修改ans,

    所以枚举i和j,然后枚举上一段的结尾l,

    由于是or,所以之前已经有的是消不掉的,

    于是有    if(!f[l][j-1]) continue;

    这样就可以保证之前的都已经满足了,所以只要考虑当前段就好了

    sum[i]表示到i的前缀和。

    先获取当前段大小now=sum[i] - sum[l]

    所以如果now的第k位为0,并且之前的高位都不与当前ans冲突,就可以转移了

    因为如果高位与ans冲突,而前面已经最优了,所以如果冲突只能是不那么优,

    于是后面再优都补不回来了。其实这也是从高位贪心的意义所在

    最后如果在f[n][A ~ B]中有任意一个为true的话,ans就不用修改(因为默认已经是0了)

    而如果一个都没有,那么就将ans的当前位变成1,

    那么考虑如何判断,

    首先运算符肯定只能用or,不然是会遗漏信息的(也许可以用其他的吧,但是我没想到)

    考虑这样两个二进制串:

    now :101100 1011

    ans : 101100 0000

    中间的空格代表把第k位和后面隔开,

    通过一些二进制的基本性质(其实也是所有进制的基本性质)可以发现,

    如果前面都是相同的话,因为ans的小于k的位数都还不确定,都是0,

    所以最大相差也就是2^(k-1),

    反之如果前面有冲突,那么由于冲突在前面,相差一定会超过2^(k-1),

    所以直接判断now和ans的差就可以了?

    不是!

    因为now是中间过程,所以now是可能小于ans的,

    所以如果是这样的情况的话:

    now : 100110 1011

    ans :  101100 0000

    这样的话now < ans,所以now - ans 一定小于 2^(k-1),-1是因为第k位对应的是2^(k-1),几次方是从0开始数的

    但是这样是不合法的,之所以这样会被误判为合法,是因为它之前的那个粗体0帮后面的粗体1抵消掉了。

    因此为了这样的0不会对真正不合法的1造成干扰,

    我们只需要计算(now | ans) - ans < 2^(k-1)是否成立即可,

    因为这样的话,所有这样的0都会变为1,于是这时要再有不合法的1出现就无法被抵消了。

    于是小数据就解决了,但是考虑到最大的点n <= 2000,这样的复杂度肯定是过不去的

    但是观察发现,对于最大的那几个点,满足A == 1,

    而之前的f数组之所以要开二维,还要枚举段数,就是因为只有枚举了所有情况才能保证不漏掉A~B之间的答案,

    因为段数小不一定更优,不然可能会找到小于A的答案,

    但是因为A == 1,就不可能会找到小于A的答案了,这时也满足段数小的一定更优,

    所以我们直接令g[i]表示dp到i,使第k位为0的最小段数,

    然后转移时判断条件和上面一样即可,注意初始化g[0]=0,其余为极大值

    下面放代码:

    #include<bits/stdc++.h>
    using namespace std;
    #define R register int
    #define AC 2100
    #define getchar() *o++
    #define LL long long
    char READ[5000100],*o=READ;
    int n,A,B,all;
    int s[AC],g[AC];
    LL sum[AC];//g[i]是适用于没有下限的情况
    LL ans;
    bool f[AC][250];
    
    inline int read()
    {
        int x=0;char c=getchar();
        while(c > '9' || c < '0') c=getchar();
        while(c >= '0' && c <= '9') x=x*10+c-'0',c=getchar();
        return x;
    }
    
    void work1()//f[i][j]代表前i位分成j段,第k位能否在保证前k位都不变且不考虑后面all-k位的情况下是否可能与当前ans相同(此时第k位默认为0)
    {//这样的话相当于给了一个判断依据ans,于是就可以提高复杂度以解决问题
        LL now;//获取最高位数
        bool done=false;
        all=log2(sum[n]) + 1;
        for(R k=all; k ;k--)//枚举位数(从高位开始贪心)
        {
            memset(f,0,sizeof(f));
            f[0][0]=true;
            done=false;//error!!!每次都要重置
            for(R i=1;i<=n;i++)//枚举到了哪里
            {
                int be=min(i,B);
                for(R j=1;j<=be;j++)//枚举分的段数
                {
                    for(R l=j-1;l<i;l++)//枚举上一段的结尾,error!!!上一段啊怎么可以等于。。。。
                    {
                        if(!f[l][j-1]) continue;//因为是|,所以前面有1不能消掉,所以前面不满足就不能取了
                        now=sum[i] - sum[l];//获取区间和
                        if(((now | ans) - ans) < (1LL<<(k-1)))//error!!!注意longlong 
                        {//如果相差小于当前位,代表不一样的都在后面,就没关系了
                            f[i][j]=true;
                            break;
                        }
                    }
                    if(i == n)
                    {
                        if(j >= A && j <= B && f[i][j])
                        {
                            done=true;
                            break;//如果找到了就无需再找了
                        }
                    }
                }
            }
            if(!done) ans+=1LL<<(k-1);//因为1已经有一位了,所以只要移动k-1位就可以了(其实是因为本来就只要判断2^(k-1),因为第一位对应的是2^0)
        }
        printf("%lld
    ",ans);
    }
    //g[i]代表满足这位为0最少需要的段数
    void work2()
    {
        LL now;
        all=log2(sum[n]) + 1;
        for(R k=all; k ;k--)
        {
            memset(g,127,sizeof(g));
            g[0]=0;
            for(R i=1;i<=n;i++)//枚举树
            {
                for(R j=0;j<i;j++)//枚举上一段的结尾,,,懒得特判1了,就从0开始吧
                {
                    now=sum[i] - sum[j];
                    if(((now | ans) - ans) < (1LL<<(k-1))) g[i]=min(g[i],g[j]+1);//error!!!注意longlong 
                }//因为要求最小的,所以就不能跳过了
            }
         /*   printf("!!!%d:
    ",k);
            for(int i=1;i<=n;i++) printf("%d ",g[i]);
            printf("
    ");*/
            if(g[n] > B) ans+=1LL<<(k-1);//error!!!貌似在这里也要加LL吧 
        }
        printf("%lld
    ",ans);
    }
    
    void pre()
    {
        n=read(),A=read(),B=read();
        for(R i=1;i<=n;i++) s[i]=read(),sum[i]=sum[i-1] + (LL)s[i];
    }
    
    int main()
    {
        freopen("in.in","r",stdin);
    //    freopen("sculpture.in","r",stdin);
    //    freopen("sculpture.out","w",stdout);
        fread(READ,1,5000000,stdin);    
        pre();
        if(A == 1) work2();//特殊点特殊处理
        else work1();
        fclose(stdin);
    //    fclose(stdout);
        return 0;
    }
  • 相关阅读:
    My.NET.ClassLibrary.Note.1
    DesignPattern.Ch12.Facade.Node
    DesignPattern.Ch10.Template Method.Note
    Oracle.sql.Note
    C#.Static Field And Static Constructor.Autocriticism
    DesignPattern.Ch4.Open Closed Principle.Note
    关于n!被整除的问题【算法实现】
    令人费解的编译错误:error C2144: syntax error : 'double' should be preceded by ';' 和 error C3646: ';' : unknown override specifier
    诡异难解决的错误:Windows已在xxx.exe中触发一个断点
    Linux(Ubuntu10.04 )下libxml2的安装以及使用示例
  • 原文地址:https://www.cnblogs.com/ww3113306/p/9027042.html
Copyright © 2020-2023  润新知