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

3 篇文章 0 订阅
2 篇文章 0 订阅

这里写图片描述


似乎永远用不完的图报


第一题

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

【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 100001
using 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 1500001
using 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 1000001
using 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]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值