zoukankan      html  css  js  c++  java
  • 2017.10.5北京清北综合强化班DAY5

    拼不出的数
    lost.in/.out/.cpp
    【问题描述】
    3 个元素的集合{5, 1,2} 的所有子集的和分别是0,1, 2, 3, 5, 6, 7, 8。发
    现最小的不能由该集合子集拼出的数字是4。
    现在给你一个n 个元素的集合,问你最小的不能由该集合子集拼出的
    数字是多少。
    注意32 位数字表示范围。

    【输入格式】
    第一行一个个整数n。
    第二行n 个正整数ai,表示集合内的元素。
    【输出格式】
    一行一个个整数答案。

    【样例输入】
    3
    5 1 2
    【样例输出】
    4
    【数据规模和约定】
    对于30% 的数据,满足n <=15。
    对于60% 的数据,满足n <= 1000。
    对于100% 的数据,满足n <= 100000; 1 <= ai <= 10^9。

    题解:排序+前缀和

    sum表示当前前缀和

    如果当前加入的数大于前缀和+1,那么输出前缀和+1,否则继续。

    因为需要表示连续的整数,那么相邻的数最多只能差1.

    如果排序后k前面的数字之和<k-1,那么k-1这个数就无法表示。

    再详细的说就是 

    现在能表示出[0,0]这个区间,那么对于排序后接下来的数k,如果k>1,那么1

    这个数就永远也拼不出来。那么对于之前能拼出的区间为[0,x],加上k之后能拼出

    的数至少为[k,x+k],必须要求[0,x]这个区间的右端点和[k,k+x]的左端点连续才能把所有

    数都拼出来,也就是k<=x+1。

    代码:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define LL long long
    using namespace std;
    
    int n,a[100008];
    LL sum;
    
    int main(){
        scanf("%d",&n);
        for(int i=1;i<=n;i++)scanf("%d",&a[i]);
        sort(a+1,a+n+1);
        for(int i=1;i<=n;i++){
            if(a[i]>sum+1){
                printf("%lld
    ",sum+1);
                return 0;
            }
            sum+=a[i];
        }
        printf("%lld
    ",sum+1);
        return 0;
    }
    AC

    整除
    div.in/.out/.cpp
    【问题描述】

    给定整数n,问[n/i],的结果有多少不同的数字。(1<=i<=n),i为正整数。

    比如n=5时,[5/1]=5,[5/2]=2,[5/3]=1,[5/4]=1,[5/5]=1,所以结果共有三个

    不同的数字。

    注意32位整数的表示范围。

    【输入格式】

    一行一个整数n

    【输出格式】

    一行一个整数答案

    【样例输入】

    5

    【样例输出】

    3

    【数据规模与约定】

    对于30% 的数据,满足1 <=n <= 10^3
    对于60% 的数据,满足1 <= n <= 10^12
    对于100% 的数据,满足1 <= n <= 10^18

    题解:

    发现一段区间的数是连续的,想办法跳过去。

    时间复杂度根号n 因为至多有根号n个数

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #define LL long long
    using namespace std;
    
    LL n,ans;
    
    int main(){
        scanf("%lld",&n);
        for(register LL i=1;i<=n;i++){
            LL a=n/i;
            LL b=n/a;
            i=b;
            ans++;
        }
        printf("%lld",ans);
        return 0;
    }
    60

    正解:

    找规律

    对于n=7

    i ret

    1 7

    2 3

    -------

    3 2

    ...

    7 1

     发现在横线上方有两个答案,下方也有两个.把重复的答案去掉就成了

    1 7

    2 3

    ------

    3 2

    7 1

    发现 1--7和7--1,2--3和3--2是对应的.相当于根号7作为一个分界线.

    那么答案会是(根号n)*2么?

    再看个例子9

    1 9

    2 4

    3 3

    4 2

    ....

    9 1

    答案是5,而不是sqrt(9)*2=6(这里的sqrt都是下取整).这是因为3多数了一次.

    那么是不是对于完全平方数答案就要-1呢?对拍发现不是这样的.

    对于10

    1 10

    2  5

    3 3

    4 2

    5 2

    6 1

    ...

    10 1

    发现答案是5,不是sqrt(10)*2-1.这是为什么呢?这是因为10/sqrt(10)=sqrt(3),这里的3又多数了一次.

    所以对于[N/[N]]=[N],答案都要减1.就可以做到O(1)得出答案. 这是同学给我讲的好详细哒orz..

    也可以打表找规律

    #include<iostream>
    #include<cmath>
    #include<cstdio>
    #define LL long long
    using namespace std;
    LL n;
    int main(){
        scanf("%lld",&n);
        LL k=sqrt(n),ans=k*2;
        if(k*k<=n&&k*(k+1)>n)ans--;
        printf("%lld
    ",ans);
        return 0;
    }
    AC

    std的做法是二分。

     对于[n/i]假设它的值是

    100 70 60 50 20 19 18 17 16 15 14 1 1 1 1 

    那么相邻两项的差值为[n/i]-[n/i-1],如果按浮点数比较,

    [n/i]-[n/i-1]<=1,那么1--[n/i]这段区间的所有数都存在,

    对于[n/i]和[n/i+1]的差大于1,对于不同的i存在不同的[n/i],

    对于i越大,差值越小。//我也不太明白这个做法。

     我又认真看了看...下面是我的理解...

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cassert>
    using namespace std;
    
    typedef long long LL;
    LL n;
    bool check(LL x){ // n <= x*(x+1)
        if(x*1.*(x+1)>1e18) return true;
        if(n <= x *(x+1)) return true;
        return false;
    }
    int main(){ 
        freopen("div.in","r",stdin);
        freopen("div.out","w",stdout);
        scanf("%lld",&n);
        if(n==1){
            puts("1");
        }else if(n==2){
            puts("2");
        }else{
            LL L = 1,R=n-1;
            while(R-L>1){
                LL mid = (L+R)/2;
                if(check(mid)) R=mid;
                else L=mid; 
            }
            // assert(check(R));
            printf("%lld
    ",L+(n/R));
        }
        
        return 0;
    }
    AC

    钻石diamond.in/.out/.cpp

    【问题描述】
    你有n 个“量子态” 的盒子,每个盒子里可能是一些钱也可能是一个钻
    石。
    现在你知道如果打开第i 个盒子,有Pi/100 的概率能获得Vi 的钱,有

    1 -Pi/100 的概率能获得一个钻石。

    现在你想知道,如果恰好获得k(0<= k<= n) 个钻石,并且获得钱数大
    于等于m 的概率是多少。
    请你对0 <= k<= n 输出n+1 个答案。
    答案四舍五入保留3 位小数。
    【输入格式】
    第一行两个整数n,m,见题意。
    接下来n 行,每行两个整数Vi; Pi。
    【输出格式】
    输出共n+1 行,表示0<= k<= n 的答案。
    【样例输入】
    2 3
    2 50
    3 50
    【样例输出】
    0.250
    0.250
    0.000

    题目大意:有n个盒子,打开时有pi的概率是钱,有1-pi的概率是钻石,求当

    钻石的个数为0-n时并且钱的个数大于等于m时的概率

    题解:

    搜索60分

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #define LL long long
    using namespace std;
    
    int n,m;
    double ans[35];
    struct BOX{
        int v,p;
    }b[35];
    
    inline int read(){
        char ch=getchar();int x=0,f=1;
        for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
        for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
        return x*f;
    }
    
    void dfs(int x,LL sumz,int sumq,double w){
        if(x==n+1){
            if(sumq>=m)ans[sumz]+=w;
            return;
        }
        dfs(x+1,sumz+1,sumq,w*(1.0-b[x].p*1.0/100));
        dfs(x+1,sumz,sumq+b[x].v,w*b[x].p*1.0/100);
    }
    
    int main(){
        freopen("diamond.in","r",stdin);
        freopen("diamond.out","w",stdout);
        n=read();m=read();
        /*n个盒子 m钱数大于m*/
        for(int i=1;i<=n;i++){
            b[i].v=read();b[i].p=read();
        }
         /*pi/100的概率获得钱*/
        dfs(1,0,0,1.0);
        /*目前看第1个盒子,钻石数和钱数为0
        当前情况出现的概率为0.0 
        */
        for(int i=0;i<=n;i++)
         printf("%.3lf
    ",ans[i]);
         fclose(stdin);fclose(stdout);
        return 0;
    }
    60

    正解

    一直以为是dp,dp应该也可过。正解是双向搜索 meet in the middle

    我们可以把盒子分成两半 1--n/2和n/2+1--n,搜索出后一半的情况,在前一半的状态中

    找出两半合并后满足条件的状态,满足的条件就是钱数>=n。对于每一种状态我们可以用

    一个三元组表示{a,b,c}表示状态的钻石个数为a,钱数为b,概率为c。

    对于这样一组样例

    2 50

    3 50

    --------

    4 50

    5 50

    那么前一半的状态用三元组表示为

    {0,5,0.25},{1,3,0.25},{1,2,0.25},{1,3,0.25};

    好,我们知道这样表示了。代码实现的主要过程就是,我们搜索后一半的状态,

    找前一半有多少符合的。

    例如,现在我们已经搜出后一半的所有三元组了。

    前一半的某个状态为{cnt,money,nowp},那么我们至少需要的钱就是L=m-money,

    那就需要找后一半状态里钱数大于等于L的,可以二分找。对于后一半的所有状态,按钻石数分块,

    意思是,钻石数为0的放在一起,为1的放在一起...,并且对于每一块做概率的前缀和。找出每一块里

    钱数大于等于L的那个状态,就可以用前缀和求出钱数大于等于L状态的概率的总和tmp。那么钻石

    数为p时最答案的贡献就是,在后一半找到的概率和tmp,和前一半的现在搜到的状态的概率nowp的乘积。

    代码:

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<vector>
    using namespace std;
    int tt;
    int n,m;
    int v[35];
    double p[35];
    double ans[35];
    vector<pair<int,double> > sta[35];
    int main(){
         freopen("diamond.in","r",stdin);
         freopen("diamond.out","w",stdout);
        scanf("%d%d",&n,&m);
        for(int i=1,x;i<=n;i++){
            scanf("%d%d",&v[i],&x);
            p[i]=x/100.;
        }
        for(int i=0;i<=n;i++){
            sta[i].clear();
        }
        int an=(n/2.5)+1;
        int bn=n-an;
        for(int st=0;st<1<<bn;st++){
            double nowp=1;
            int cnt=0,money=0;
            for(int i=0;i<bn;i++){
                if((st>>i)&1){
                    money+=v[n-i];
                    nowp*=p[n-i];
                }else{
                    cnt++;
                    nowp*=(1-p[n-i]);
                }
            }
            sta[cnt].push_back(make_pair(money,nowp));
        }
        for(int i=0;i<=n;i++){
            sort(sta[i].begin(),sta[i].end());
            for(int j=1;j<sta[i].size();j++){
                sta[i][j].second+=sta[i][j-1].second;
            }
        }
        for(int st=0;st<1<<an;st++){
            double nowp=1;
            int cnt=0,money=0;
            for(int i=0;i<an;i++){
                if((st>>i)&1){
                    money+=v[i+1];
                    nowp*=p[i+1];
                }else{
                    cnt++;
                    nowp*=(1-p[i+1]);
                }
            }
            for(int i=0;i<=bn;i++){
                // now d =cnt+i
                int L = m-money;
                vector<pair<int,double> >::iterator it = lower_bound(sta[i].begin(),sta[i].end(),make_pair(L,-1.));
                double tmp = sta[i].back().second;
                if(it!= sta[i].begin()){
                    it--;
                    tmp-=it->second;
                }
                ans[cnt+i] += tmp*nowp;
            }
        }
        for(int i=0;i<=n;i++){
            printf("%.3f
    ",ans[i]);
        }
         fclose(stdout);
        return 0;
    }
    AC
  • 相关阅读:
    图像和流媒体 -- 帧率、分辨率、码流的概念和关系(转)
    Linux设备驱动(转)
    STM32开发 -- 4G模块开发详解(转)
    Linux下EC20实现ppp拨号(转)
    使用机智云APP控制战舰V3 (转)
    USB Host读取U盘成功
    FreeModbus在STM32上移植(转)
    处理分页操作
    HBase参数配置及说明(转)
    学习JNDI
  • 原文地址:https://www.cnblogs.com/zzyh/p/7629844.html
Copyright © 2011-2022 走看看