2017.10.5北京清北综合强化班DAY5

Posted 小时のblog

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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\\n",sum+1);
            return 0;
        }
        sum+=a[i];
    }
    printf("%lld\\n",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\\n",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\\n",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\\n",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\\n",ans[i]);
    }
     fclose(stdout);
    return 0;
}
AC

 

以上是关于2017.10.5北京清北综合强化班DAY5的主要内容,如果未能解决你的问题,请参考以下文章

2017国庆 清北学堂 北京综合强化班 Day1

北京清北 综合强化班 Day1

北京清北 综合强化班 Day2 T1

北京清北 综合强化班 Day3

2017.10.1北京清北综合强化班DAY1

北京清北 综合强化班 Day4 T1