10.1综合强化刷题 Day5

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了10.1综合强化刷题 Day5相关的知识,希望对你有一定的参考价值。

T1 拼不出的数

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 <=109。

考场中没有看到1<=ai<=10^9,认为a一定是在n的范围内的,然后完美的开了三个1000的数组,然后就崩溃了、、

技术分享
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define N 110000
#define LL long long 
using namespace std;
LL n,a[N],tot;
bool vis[N],vist[N];
LL read()
{
    LL x=0,f=1; char ch=getchar();
    while(ch<0||ch>9) ch=getchar();
    while(ch>=0&&ch<=9) x=x*10+ch-0,ch=getchar();
    return x*f;
}
void dfs(int now,int s,int sum)
{
    if(now==s)
    {
        vist[sum]=1;
        return ;
    }
    for(LL i=1;i<=n;i++)
    {
        if(vis[i]) continue;
        vis[i]=1;
        dfs(now+1,s,sum+a[i]);
        vis[i]=0;
    }
}
int main()
{
//    freopen("lost1.in","r",stdin);
//    freopen("lost.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++) a[i]=read(),tot+=a[i];
    for(int i=1;i<=n;i++)
      dfs(0,i,0);
    for(LL i=1;i<=tot+1;i++)
    {
        if(vist[i]) continue;
        printf("%lld",i);
        return 0;
    }
}
考场暴零代码

 一个思路题吧、、(代码很短)

我们读入所有的a以后先对其进行一个sort排序,然后我们在观察我们排序后的a数组,我们可以知道当前面的前缀和加1不等于当前数,那么这个前缀和加1,就是我们要求的最小的不能被拼出的数。

正确性证明??

我们来看一组数 12  14 5  6  21  7  1  2  4 13 我们将这一组数排完序以后将会变成 1  2  4  5  6  7  12  13  14  21

假设我们不存在1这个数,那么我们最初拼出的数为2,那么1一定是不会被频出的最小的数,也就是结果。然后我们看当前数组,1跟2能拼成1 、2、 3我们判断当前前缀和也就是最大能拼出的数+1以后是否大于等于当前数,我们继续往下走3+4=7,现在用这些数我们可以拼出1~7内的所有的数,然后在判断3+4+5+6+7+12+13+14=64这样的话我们最小的不能拼成的数为64+1=65.

技术分享
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define N 100010
#define LL long long  
using namespace std;
int n,a[N];
int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<0||ch>9){if(ch==-)f=-1;ch=getchar();}
    while(ch>=0&&ch<=9) x=x*10+ch-0,ch=getchar();
    return x*f;
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++) a[i]=read();
    sort(a+1,a+1+n); LL sum=0;
    for(int i=1;i<=n;i++)
     if(sum+1<a[i]) {printf("%lld",sum+1); return 0;}
     else sum+=a[i]; 
    printf("%lld",sum+1);
    return 0;
}
AC代码

 

T2 整除

题目描述

给定整数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。

 

输出格式:

 

一行一个整数答案。

 

输入输出样例

输入样例#1:
5
输出样例#1:
3

说明

对于30% 的数据,满足1 <=n <= 10^3。

对于60% 的数据,满足1 <= n <=10^12。

对于100% 的数据,满足1 <=n <=10^18。

 

打表找规律80分

技术分享
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
LL n,ans,tot,sum;
LL read()
{
    LL x=0,f=1; char ch=getchar();
    while(ch<0||ch>9) ch=getchar();
    while(ch>=0&&ch<=9) x=x*10+ch-0,ch=getchar();
    return x*f;
}
int main()
{
    freopen("div.in","r",stdin);
    freopen("div.out","w",stdout);
    tot=1;n=read();
    if(n>=1e17&&n<1e18-1e5) {puts("632455531");return 0;};
    if(n>=1e18-10000) {puts("1999999999");return 0;};
    while(sum<n)
    {
        ans++;
        if(ans%2==0) sum=sum+tot;
        else sum=sum+tot,tot++;
    }  
    if(n==1) ans=1;
    printf("%lld",ans);
    return 0;
}
80分代码

 

 打表找规律,我们打一个表出来以后会发现一个规律,每一个数的结果与根号n有一个比较密切的联系

我们先打一个10以内的表

n ans 根号n  
1 1 1 1*2-1=1
2 2 1 1*2=2
3 2 1 1*2=2
4 3 2 2*2-1=3
5 3 2 2*2-1=3
6 4 2 2*2=4
7 4 2 2*2=4
8 4 2 2*2=4
9 5 3 2*3-1=5

通过上面的表,我们是不是可以发现这样一个小规律,每一个数的开跟*2或*2-1跟我们的答案有着密切的联系。我们还可以发现当一个数的跟n等于n/跟n,也就是说我们拼出来的两个数是一样的时候我们要对最终答案-1

技术分享
#include<cmath>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
long long n,ans;
long long read()
{
    long long x=0,f=1; char ch=getchar();
    while(ch<0||ch>9){if(ch==-)f=-1; ch=getchar();}
    while(ch>=0&&ch<=9) x=x*10+ch-0,ch=getchar();
    return x*f;
}
int main()
{
    n=read();
    ans=sqrt(n);
    if(ans==n/ans) ans=ans*2-1;
    else ans=ans*2;
    printf("%lld",ans);
}
AC代码

 

 

T3 钻石

题目描述

你有n 个“量子态” 的盒子,每个盒子都可能是一些钱也可能是一个钻石。

现在你知道如果打开第i 个盒子,有pi/100 的概率能获得Vi 的钱,有

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

现在你想知道,自己恰好获得k(0 <= k <=n) 个钻石,并且获得钱数大

于等于m 的概率是多少。

请你对0 <= k <= n 输出n+1 个答案。

答案四舍五?保留3 位?数。

输入输出格式

输入格式:

 

第??两个整数n,m,见题意。

接下来n 行,每行两个整数Vi; Pi。

 

输出格式:

 

输出共n+1 行,表示0 <=k <= n 的答案。

 

输入输出样例

输入样例#1:
2 3
2 50
3 50
输出样例#1:
0.250
0.250
0.000

说明

对于30% 的数据, n <=10。

对于60% 的数据, n <=15

对于100% 的数据,n <= 30; 1 <= Pi <= 99; 1 <= Vi <= 10^7; 1 <=m <=10^7。

 暴力搜索60

我们搜索从每一个盒子中取出钻石还是钱,分别算出其概率,然后我们在所有的盒子都打开以后判断当前这种情况获得的钱数是否大于等于m,如果是的话ans[s]更新,s为钻石数目,我们用一个ans数组统计获得几颗钻石的情况下的概率。

技术分享
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 50
using namespace std;
double ans[N];
int n,m,v[N],p[N];
int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<0||ch>9){if(ch==-)f=-1;ch=getchar();}
    while(ch>=0&&ch<=9) x=x*10+ch-0,ch=getchar();
    return x*f;
}
void dfs(int now,int s,int c,double w)
{
    if(now==n+1)
    {
        if(c>=m) ans[s]+=w; 
        return ;
    }
    dfs(now+1,s+1,c,w*(1.0-p[now]*1.0/100));
    dfs(now+1,s,c+v[now],w*p[now]*1.0/100);
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=n;i++) v[i]=read(),p[i]=read();
    dfs(1,0,0,1.0);
    for(int i=0;i<=n;i++)
     printf("%.3lf\n",ans[i]);
    return 0;
}
60分搜索

 

 正解为双向搜索,meet in the middle

我们通过前面的可以看到暴力搜索的话只能拿到60分,我们要想一个办法对这个搜索进行优化,我们可以先搜后一半,然后在前一半中找出与后一半合并以后能满足条件的状态,

满足的条件就是钱数>=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<vector>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define N 50
using namespace std;
double p[N],ans[N];
int n,m,x,a,b,v[N];
vector<pair<int,double> >vec[N];
int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<0||ch>9){if(ch==-)f=-1;ch=getchar();}
    while(ch>=0&&ch<=9) x=x*10+ch-0,ch=getchar();
    return  x*f;
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=n;i++) v[i]=read(),x=read(),p[i]=x/100.;
    a=(n+1)/2;b=n-a;//由于我们要折半处理,因此我们现将其盒子分成两半 
    for(int i=0;i<=n;i++)
        vec[i].clear();//可有可无、、 
    //对于每一种状态我们可以用个三元组表示{a,b,c}表示状态的钻石个数为a,钱数为b,概率为c 
    for(int st=0;st<1<<b;st++)//我们先处理后一半盒子 
    {
        double np=1; 
        int s=0,money=0;//赋初值 
        for(int i=0;i<b;i++)//这个与前面构成一个大枚举,当前盒子为钻石还是钱 
        {
            if((st>>i)&1)//当前盒子中为钱 
             money+=v[n-i],np*=p[n-i];//钱的总数增加,概率相乘 
            else
             s++,np*=(1-p[n-i]);//钻石数+1 
        }
        vec[s].push_back(make_pair(money,np));//我们将我们处理出来的所有情况储存在vec中 
    }
    //我们处理了后一半的所有状态 ,将其存在vec数组中 
    //接下来的这个地方我们对我们处理出来的每一个数做一个分块处理,钻石数为0的放在一起,钻石数为1的放在一起,钻石数为2的放在一起、、、、并且对于每一块做概率的前缀和 
    for(int i=0;i<=n;i++)
    {
        sort(vec[i].begin(),vec[i].end());//我们将最小的可能不满足条件的放在前面,那么我们找出第一个满足条前的位置的时候,我们就可以知道在这个位置往前的一定都不满足条件 
        for(int j=1;j<vec[i].size();j++)
         vec[i][j].second+=vec[i][j-1].second;  
    }
    //这个地方我们可以知道a*b+a*c+a*d=a*(b+c+d)而这个地方的(b+c+d)为后半段所有满足条件的概率总和,那么我们在这个地方就可以预处理一个前缀和
    for(int st=0;st<1<<a;st++)//我们再来根据搜出来的后半段的状态来找前半段的状态 
    {
        double np=1;
        int s=0,money=0;
        for(int i=0;i<a;i++)
        {
            if((st>>i)&1)
             money+=v[i+1],np*=p[i+1];
            else 
             s++,np*=(1-p[i+1]);
        }
        //找能与搜出的前半段的状态合并起来以后能满足条件的后半段 
        //钻石数为s+i的这种状态可以由我们当前搜到的状态(存在了s颗钻石)我们可以再加上i颗钻石以后的钱数就可以满足条件,枚举这个可以使他满足条件的i
        for(int i=0;i<=b;i++)//后一半一共有b个位置,总共就有b+1个状态,我们按照钻石的个数为一个状态 
        {
            int l=m-money;//我们当前搜到的前半段的钱数已经是money了,那么我们还剩下m-money的钱数就可以满足条件 
            vector<pair<int,double> >::iterator it=lower_bound(vec[i].begin(),vec[i].end(),make_pair(l,1.0));//获得第一个满足条件的位置 
            //我们找出第一个满足条件的位置 用it来记录 
            //我们可以认为vec为一个数组 
            double tmp=vec[i].back().second;//现将tmp初始为整个数组的值 
               if(it!=vec[i].begin())//我们判断当期位置是否为数组的第一个位置,如果是的话就说明数组中的每一种状态都满足条件,总概率即为tmp 
              it--,tmp-=it->second; //如果不是的话,那么it为第一个满足条件的位置,it-1就是第一个不满足条件的位置,我们在减去it-1这一部分的前缀和,那么剩下的就是所有的满足条件的概率 
            ans[s+i]+=tmp*np;//搜到当前钻石数的概率为前半段的概率*后半段的概率 
        }
    }
    for(int i=0;i<=n;i++)
     printf("%.3lf\n",ans[i]);
    return 0;
}
AC代码

 

 

 

以上是关于10.1综合强化刷题 Day5的主要内容,如果未能解决你的问题,请参考以下文章

10.1综合强化刷题 Day3 afternoon

10.1综合强化刷题 Day4

10.1综合强化刷题 Day6

10.1综合强化刷题 Day2 afternoon

10.1综合强化刷题 Day2 morning

北京清北 综合强化班 Day5