zoukankan      html  css  js  c++  java
  • AGC012

    原题链接

    题意简述

    沙漠中有n(n2×105)个排成一条直线的绿洲,一头储水量为V(V2×105)的骆驼。
    骆驼有两个操作:

    • 走到距离在V以内的一个绿洲。
    • 飞到任意一个绿洲,但V减少一半。V=0时不能飞。

    问骆驼依次从每个绿洲出发,能否一次性遍历所有绿洲。

    分析

    首先预处理出 V=V0 时哪些绿洲之间是可以随便走的,对于每个V0扫一遍即可。时间复杂度为O(nlog2V)

    每飞一次相当于下一层。题目转化成钦定第一条线段,然后从每一层选一条线段,问能否覆盖整个区间。
    万万没想到,这道题居然是状压DP!!!

    s中的后起第i位为1表示从第i层选出了一条线段。
    f1[s]表示状态s时从1起向右最远能延伸到哪,f2[s]表示状态s时从n起向左最远能延伸到哪。
    f1[s]=max(f1[s],upFind(f1[s0]))
    f2[s]=min(f2[s],lowFind(f2[s0]1))
    意义是从s0加上一条线段能延伸到哪。这个转移方程和我的写法有关,具体看代码。
    时间复杂度O(log2Vlog2n2log2V+1)=O(Vlog2nlog2V)

    检查答案时,对于第一层的每一条线段,寻找是否存在s,使得f1[s],f2[Us1]和该线段覆盖整个区间。表示用状态s这些线段尽可能扩展左半部分,用剩下的线段(不包括第一层)尽可能扩展右半部分,再加上第一层的这条线段。
    最大时间复杂度O(n2log2V)=O(nV),GG ╮(╯﹏╰)╭
    但实际上,第一层的线段条数是不能超过log2V+1的。因为飞log2V+1次后V0=0,并且下一层的条数比上一层只多不少,要是第一层就超过log2V+1那么不可能遍历所有绿洲。
    所以最大时间复杂度为O(Vlog2V)

    总时间复杂度最大为O(nlog2V+Vlog2nlog2V)

    实现

    a[i][j]记录第i层的第j条线段的右端点。特别地,a[i][0]记录第i层线段的条数。
    upFind(x)找出第一个严格大于x的右端点。这个右端点所在的区间一定能延伸当前的f1
    lowFind(x1)找出第一个严格小于x-1的右端点。这个右端点的下一个区间一定能延伸当前的f2。如果写lowFind(x),要是找到x-1的话,说明有一段以x-1为右端点的区间以及一段以x为左端点的区间。这时候明明可以加入前者,实际上却加入了后者,导致问题。

    代码

    //Camel and Oases
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    typedef long long lint;
    int const N=2e5+10;
    int const S=1<<19;
    int n,V;
    lint d[N];
    int logV,a[25][N];
    int U,f1[S],f2[S];
    int upFind(int a[],int x)
    {
        int L=1,R=a[0];
        while(L<R-1)
        {
            int mid=(L+R)>>1;
            if(a[mid]<=x) L=mid+1;
            if(a[mid]>x) R=mid;
        }
        if(a[L]>x) return a[L];
        else return a[R];
    }
    int lowFind(int a[],int x)
    {
        int L=1,R=a[0];
        while(L<R-1)
        {
            int mid=(L+R)>>1;
            if(a[mid]<x) L=mid;
            if(a[mid]>=x) R=mid-1;
        }
        if(a[R]<x) return a[R]+1;
        else return a[L]+1;
    }
    int main()
    {
        scanf("%d%d",&n,&V);
        logV=0;
        while((1<<logV)<=V) logV++;
        logV++;
        for(int i=1;i<=n;i++) scanf("%lld",&d[i]),d[i-1]=d[i]-d[i-1];
        d[n]=0;
        for(int i=1;i<=logV;i++)
        {
            a[i][0]=1;
            for(int j=1;j<=n;j++)
            {
                a[i][a[i][0]]=j;
                if(d[j]>(V>>(i-1))) a[i][0]++;
            }
        }
        if(a[1][0]>logV)
        {
            for(int i=1;i<=n;i++) printf("Impossible
    ");
            return 0;
        }
        U=(1<<logV)-1;
        for(int s=0;s<=U;s++) f1[s]=0,f2[s]=n+1;
        for(int s=0;s<=U;s+=2)
            for(int i=2;i<=logV;i++)
            {
                int s0=1<<(i-1);
                if(s&s0) continue;
                f1[s|s0]=max(f1[s|s0],upFind(a[i],f1[s]));
                f2[s|s0]=min(f2[s|s0],lowFind(a[i],f2[s]-1));
            }
        for(int i=1;i<=a[1][0];i++)
        {
            bool f=false;
            int fr=a[1][i-1]+1,to=a[1][i];
            if(i==1) fr=1;
            for(int s=0;s<=U&&!f;s+=2)
                if(fr<=f1[s]+1 && f2[U-s-1]-1<=to) f=true;
            if(f) for(int j=fr;j<=to;j++) printf("Possible
    ");
            else for(int j=fr;j<=to;j++) printf("Impossible
    ");
        }
        return 0;
    }

    注意

    • 因为最多飞log2V+1次,所以全集状态U=2log2V+11,体现在题目中为219,而不是218
    • a[i][0]做他用,有些地方写的可能会麻烦一些。
  • 相关阅读:
    C语言关键字register、extern、static、一些总结,及项目中使用的心得
    c语言,文件操作总结
    《Redis内存数据库》Redis内存数据库技术总结
    《Redis内存数据库》Redis环境搭建(Linux)
    《Linux 操作系统》Linux的常用命令操作大全
    《Java练习题》Java编程题合集(全)
    《Java练习题》Java习题集一
    《Java基础知识》Java技术总结
    《Java基础知识》Java数据类型以及变量的定义
    《Java 底层原理》Jvm 类的加载原理
  • 原文地址:https://www.cnblogs.com/VisJiao/p/8485770.html
Copyright © 2011-2022 走看看