• 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
  • 相关阅读:
    How To Run Docker in Docker Container [3 Easy Methods]
    design patterns of refactoring guru
    MathJax A JavaScript display engine for mathematics that works in all browsers.
    SoC the root design principle
    Kubernetes plugin for Jenkins
    Inversion of Control
    Python Metaclasses
    JNLP the foundametal of distributed computing of Jenkins
    C# 表达式树Expression
    ML .NET 电影评论情绪分析
  • 原文地址:https://www.cnblogs.com/zzyh/p/7629844.html
Copyright © 2020-2023  润新知