【2017/9/19】【校内胡策题】【noip模拟】【总结】

来源:互联网 发布:lg pd251软件 编辑:程序博客网 时间:2024/05/16 19:26

这里写图片描述


似乎永远用不完的图报


第一题

此题很有复习价值,本题中有几个点重点:

【1】 对于可行问题要往贪心方向思考

【2】 在想贪心方案是需要严格证明,宁可画上一定时间(但要控制再半小时以内)去证明正确性 , 找反例

【3】 在想贪心情况时 , 要落到实处 ,若觉得一个策略不对则需要通过题目特性落到实处看其是否正确;找反例时先感性想想反例出现情况,在去构造对应数据看是否能否定当前结论

在解决此题时 , 由于放入体积与增加体积,而且题目问题是是否可解,可使人想到贪心;

而贪心策略中,由于要是其放得下,所以可以想到先放有贡献(增加体积)的情况 , 然后猜测是贪心顺序,以a排序?b排序?还是b-a?此时发现它最后加完的体积是一个定值 , 而其体积也在不断增加,那么应该是a越大的放到越后面能跟保险的放下,然而是否会有反例,根据其特性:v会应为b - a > 0 而不断增大,显而易见策略正确;

而此题头疼的是b - a < 0 时的策略 : 还是按照上面所述的思路 , a?,b?,b-a?,按照从大到小?从小到大?貌似从正面不好想,但有个性质时确定的:

最后的体积为定值

那么从次特性思考,倒着来想:
最后的状态是:

V + b1 + b2 + b3 + … + bn >= a1 + a2 + a3 + … + an

那么最后一次选择便是选择先将左式减去对于的b , 在不等式成立的情况下在减去对应得a ,由于后面加上的是b - a < 0 , 那么每次减去一组对应的a,b时左式减右式的值会越来越大 , 所以只要保证每次删的b最小,时左式减小的尽量少,那么等式则月容易成立,所以嘛。。。反过来想 , 不就是。。。以b为标准从大到小排序。。。
那么全程的贪心策略则出来了:
对于 a - b > 0 的情况 以a为标准从小到大排序
对于 a - b < 0 的情况 以b为标准从大到小排序

代码如下:

#include<cctype>#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>#define M 100001using namespace std;long long T , n ;long long v;long long a , b;long long qu1_tot , qu2_tot;struct Queue{    long long a , b_a , b;    Queue(){a = b_a = 0;}}qu1[M] , qu2[M]; bool cmp1(const Queue &x , const Queue &y){return x.a == y.a ? x.b_a > y.b_a : x.a < y.a;}bool cmp2(const Queue &x , const Queue &y){return x.b == y.b ? x.a > y.a : x.b > y.b;}long long read(){    long long an = 0;    static char ch = '*';    while(!isdigit(ch))ch = getchar();    while( isdigit(ch))an = an * 10 + ch - '0' , ch = getchar();    return an;}int main(){    T = read();    while(T--){        n = read() , v = read();        bool bre = true;        qu1_tot = 0 , qu2_tot = 0;        memset(qu1 , 0 , sizeof qu1);        memset(qu2 , 0 , sizeof qu2);        for(int i = 1 ; i <= n ; ++i){            a = read() , b = read();            long long b_a = b - a;            if(b - a >= 0)qu1[++qu1_tot].a = a , qu1[qu1_tot].b_a = b_a , qu1[qu1_tot].b = b;            else qu2[++qu2_tot].a = a , qu2[qu2_tot].b_a = b_a , qu2[qu2_tot].b = b;        }        sort(qu1 + 1 , qu1 + qu1_tot + 1 , cmp1);        sort(qu2 + 1 , qu2 + qu2_tot + 1 , cmp2);        for(int i = 1 ; i <= qu1_tot && bre ; ++i){            if(qu1[i].a > v)bre = false;            else v += qu1[i].b_a;        }        if(bre == false){            printf("No\n");            continue;        }        for(int i = 1 ; i <= qu2_tot && bre ; ++i){            if(qu2[i].a > v)bre = false;            else v += qu2[i].b_a;        }if(bre == false){            printf("No\n");            continue;        }        printf("Yes\n");    }}

第二题

这道题由于时500000的上线而且为区间,很容易想到二分加线段树维护,代码如下(由于卡产只能得65分)(复杂度O(n * (log^3))):

#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>#define M 1500001using namespace std;int n;long long gcd(long long a , long long b){return b > 0 ? gcd(b , a%b) : a;}long long v_num[M] , v_gcd[M];int l[M] , r[M];long long num[M];long long tot , ans , set[M];struct val{    long long gcd , num;    val(){gcd = 1 , num = (1 << 31) - 1;}    val(long long x , long long y){gcd = x , num = y;}};long long read(){    long long an = 0;    static char ch = '*';    while(!isdigit(ch))ch = getchar();    while( isdigit(ch))an = an * 10 + ch - '0' , ch = getchar();    return an;}void build(int l_ ,  int r_ , int v){    if(l_ == r_){        v_num[v] = v_gcd[v] = num[l_];        return;    }    l[v] = l_ , r[v] = r_;    int m_ = (l_ + r_) >> 1;    build(l_ , m_ , v << 1);    build(m_ + 1 , r_ , v << 1 | 1);    v_gcd[v] = gcd(v_gcd[v << 1] , v_gcd[v << 1 | 1]);    v_num[v] = min(v_num[v << 1] , v_num[v << 1 | 1]);//  printf("[%d %d %I64d %I64d]\n",l_,r_,v_num[v] , v_gcd[v]);}val query(int l_ , int r_ , int qu_l , int qu_r , int v){    if(l_ >= qu_l && r_ <= qu_r)return val(v_gcd[v] , v_num[v]);    int m_ = (l_ + r_) >> 1;    val a , b , ret;    if(qu_l <= m_ && qu_r > m_){        a = query(l_ , m_ , qu_l , qu_r , v << 1);        b = query(m_ + 1 , r_ , qu_l , qu_r , v << 1 | 1);        ret.gcd = gcd(a.gcd , b.gcd);        ret.num = min(a.num , b.num);    }    else if(qu_l <= m_)ret = query(l_ , m_ , qu_l , qu_r , v << 1);    else if(qu_r >  m_)ret = query(m_ + 1 , r_ , qu_l , qu_r , v << 1 | 1);    return ret;}bool check(int lon){    for(int i = 1 ; i <=n - lon + 1 ; ++i){        val x = query(1 , n , i , i + lon - 1 , 1);        if(x.gcd == x.num)return true;    }    return false;}void find(int lon){    ans = lon * 1LL;    for(int i = 1 ; i <=n - lon + 1 ; ++i){        val x = query(1 , n , i , i + lon - 1 , 1);            if(x.gcd == x.num)  set[++tot] = i;    }}int main(){    freopen("point.in","r",stdin);    scanf("%d",&n);    for(register int i = 1 ; i <= n ; ++i){        num[i] = read();        //scanf("%I64d",&num[i]);        if(num[i] == 1){            printf("1 %d\n1",n - 1);            return 0;        }    }     build(1 , n , 1);    int L = 1 , R = n;    while(L + 1 < R){        int M_ = (L + R) >> 1;        check(M_) ? L = M_ : R = M_;    }    if(check(R))find(R);    else find(L);    printf("%I64d %I64d\n" , tot , ans - 1);    for(int i = 1 ; i <= tot ; ++i)printf("%I64d ",set[i]);} 

然而其实有更好的方法,由于时连续的区间,所以更具其【连续性】,那么每次维护一个以当前为ak而且是该区间最后一个数的满足条件的区间的长度和以当前为ak而且是该区间第一个数的满足条件的区间长;再来O(n)枚举即可

但是有一点需要注意,就是判重 , (一个合理区间里有多个相同的数)

例子:

4

1 1 1 1

代码如下:(复杂度O(n))

#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>#define M 1000001using namespace std;int n , ans = 0  , top , vis[M];long long qu[M];int pre[M] , nex[M];long long  num[M];int main(){    scanf("%d",&n);      for(int i = 1 ; i <= n ; ++i)scanf("%I64d",&num[i]);    for(int i = 1 ; i <= n ; ++i){        int set = i ; pre[i] = i;        while(set && num[set] % num[i] == 0)set = pre[set] == set ? set - 1 : pre[set];        pre[i] = set + 1;    }    for(int i = n ; i >= 1 ; --i){        int set = i ; nex[i] = i;        while(set < n + 1 && num[set] % num[i] == 0)set = nex[set] == set ? set + 1 : nex[set];        nex[i] = set - 1;    }    for(int i = 1 ; i <= n ; i++){        if(ans < nex[i] - pre[i]){            ans = nex[i] - pre[i];            memset(vis , 0 , sizeof vis);            top = 0;        }        if(ans == nex[i] - pre[i])            if(!vis[pre[i]]){                vis[pre[i]] = 1;                qu[++top] = pre[i];            }    }    printf("%d %d\n",top , ans);    for(int i = 1 ; i <= top ; i++)printf("%I64d ",qu[i]);}