10.5 test solution.

9 篇文章 0 订阅
9 篇文章 0 订阅
本文详细解析了三道编程竞赛题目,包括‘拼不出的数’、‘整除’和‘钻石’。介绍了每道题目的问题描述、输入输出格式,以及针对不同数据规模的解决方案。解决方案中涉及暴力枚举、分块处理和动态规划等算法思想。
摘要由CSDN通过智能技术生成

1.拼不出的数(lost.in/.out/.cpp)

时间限制

1000ms

空间限制

256MB

【问题描述】

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

【输入格式】

第一行一个整数 n
第二行 n 个正整数 ai ,表示集合内的元素。

【输出格式】

一行一个正整数表示答案。

【样例输入】

3
5 1 2

【样例输出】

4

【数据规模和约定】

对于 30% 的数据,满足 n15
对于 60% 的数据,满足 n1000
对于 100% 的数据,满足 n100000,1ai109
保证 ai 互不相同


solution

  • 对于 30% 的数据

    • 2n 暴力枚举所有子集,然后暴力找最小值。
  • 对于 100% 的数据

    • a 数组排序。

    • 考虑每插入一个数之后,可以得到的最大值,也就是插入之前的所有数的和sum

    • 然后插入一个数 x 之后,如果 x>sum+1 ,那就输出 sum+1

    • 这个为什么对呢,很显然yinweiwoyebuzhidao

  • 其实因为数据比较水,所以你只需要30分暴力,判断有没有1或2,剩下的输出所有数的和, 就可以A,亲测

code

  • 因为数据水才AC的code
#include<map>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

template<typename T>
void input(T &x) {
    x=0; T a=1;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar())
        if(c=='-') a=-1;
    for(;c>='0'&&c<='9';c=getchar())
        x=x*10+c-'0';
    x*=a;
    return;
}

#define MAXN 100010

map<ll,bool> g;
ll a[MAXN];

int main() {
    int n;
    input(n);
    ll sum=0;
    g[0]=true;
    ll Min=1;
    for(int i=0;i<n;i++) {
        input(a[i]);
        g[a[i]]=true;
        if(g[Min]==true) Min++;
        sum+=a[i];
    }
    if(g[1]==false) {
        printf("1");
        return 0;
    }
    if(g[2]==false) {
        printf("2");
        return 0;
    }
    if(n<=15) {
        ll cnt;
        for(int s=0;s<1<<n;s++) {
            cnt=0;
            for(int i=0;i<n;i++)
                if(s&(1<<i))
                    cnt+=a[i];
            g[cnt]=true;
        }
        while(g[Min]==true) Min++;
        printf("%lld",Min);
        return 0;
    }
    printf("%lld",sum+1);
    return 0;
}
  • 不知道能不能过比较强的数据的std 据出题人说可以卡
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

int a[100005];

int main() {
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    ll sum=0;
    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;
        } else sum+=a[i];
    }
    printf("%lld\n",sum+1);
    return 0;
}

2.整除(div.in/.out/.cpp)

时间限制

1000ms

空间限制

256MB

【问题描述】

给定整数 n ,问ni的结果有多少个不同的数字。( 1in i 为整数。)
n=5时, 51=5,52=2,53=1,54=1,55=1 ,所以结果为 3 ,共有三个不同的数字。
注意 32 位整数的表示范围。

【输入格式】

一行一个整数 n

【输出格式】

一个整数表示答案。

【样例输入】

5

【样例输出】

3

【数据规模和约定】

对于30%的数据,满足 1n103
对于 60% 的数据,满足 1n1012
对于 100% 的数据,满足 1n1018


solution

  • 对于 30% 的数据,直接暴力枚举判断就好了。

  • 对于 60% 的数据

    • ni 的值有一段区间是完全相等的,然后就可以分块来求,也就是除法分块,统计区间个数就是最后的答案。
  • 对于 100% 的数据

    • 利用上面随便一种方法打一个表找一下规律

    • 如果用 f[n] 来表示 ni 不同的值的个数

    • 通过打表可以发现这个 f[n] 也是有一段区间是相同的,所以再把转折点打一个表

    • 打出来就是 1,2,4,6,9,12,16,20,25,30,36

    • 然后就发现其中一些数是完全平方数,但中间还夹着一个数,把完全平方数展开

    • 数列变成 11,2,22,6,33,12,44,20,55,30,66

    • 然后就很显然啦,中间夹着的数就是与他相邻的完全平方数的平方根的乘积

    • 也就是 11,12,22,23,33,34,44,45,55,56,66

    • 找到这个规律以后再看一下它们和答案的关系

    • 对应的答案依次是 1,2,3,4,5,6,7,8,9,10,11

    • 把完全平方数对应的答案提出来

    • 就是 f[1]=1,f[4]=3,f[9]=5,f[16]=7,f[25]=9,f[36]=11

    • 规律显然是 2n1

    • 然后就得到了这个题目 O(1) 的做法

    • 找出一个数离他最近的两个完全平方数,小的那个为 k1 ,大的那个为 k2 ,中间的那个点就是 k3=k1k2

    • 如果 k1n<k3 ,就输出 2k11

    • 如果 k3n<k2 ,就输出 2k1

    • 否则输出 2k2

    • 然后问题就是怎么找最近的两个完全平方数

    • 其实这个很简单

    • k1nk2k1nk2 ,因为 k1 k2 是两个相邻的完全平方数,所以 k1+1=k2 ,所以 n 肯定是两个相差为 1 的数之间的数

    • 所以n=k1 n=k2

    • 一开始没想到这样,naive的我想把所有的完全平方数打个表,结果打了14.2GB还没打完,直接打满C盘,蓝屏is wonderful。还好最后想出来了。

code

  • O(1) 虐std的code
#include<cmath>
#include<cstdio>
#include<iostream>
using namespace std;
typedef long long ll;

template<typename T>
void input(T &x) {
    x=0; T a=1;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar())
        if(c=='-') a=-1;
    for(;c>='0'&&c<='9';c=getchar())
        x=x*10+c-'0';
    x*=a;
    return;
}

int main() {
    freopen("div.in","r",stdin);
    freopen("div.out","w",stdout);
    ll n;
    input(n);
    ll k1=floor(sqrt(n)*1.00),k2=ceil(sqrt(n)*1.00),k3=k1*k2;
    if(k1*k1<=n&&n<k3) cout<<2*k1-1;
    else if(k3<=n&&n<k2*k2) cout<<2*k1;
    else cout<<2*k2-1;
    fclose(stdin);
    fclose(stdout);
    return 0;
}

3.钻石(diamond.in/.out/.cpp)

时间限制

1000ms

空间限制

256MB

【问题描述】

你有 n 个“量子态” 的盒子,每个盒子里可能是一些钱也可能是一个钻石。
现在你知道如果打开第 i 个盒子,有 Pi100 的概率能获得 Vi 的钱,有 1Pi100 的概率能获得一个钻石。
现在你想知道,自己恰好获得 k(0kn) 个钻石,并且获得钱数大于等于 m 的概率是多少。
请你对0kn输出 n+1 个答案。
答案四舍五入保留 3 位小数。

【输入格式】

第一行两个整数 n,m,见题意。
接下来 n 行,每行两个整数 Vi,Pi

【输出格式】

输出共 n+1 行,表示 0kn 的答案。

【样例输入】

2 3
2 50
3 50

【样例输出】

0.250
0.250
0.000

【数据规模和约定】

对于 30% 的数据, n10
对于 60% 的数据, n15
对于 100% 的数据, n30,1Pi99,1Vi107,1m107


solution

  • std的思路

  • 主要思想是meet in the middle

  • 暴力枚举每个物品选还是不选,是 2n ,明显不能A

  • 但是我们可以搜两次,一次搜 2n ,那样复杂度就是 22n/2 ,这个复杂度就可以A了

  • 然后就是怎么搜了

  • 首先要知道怎么枚举子集,我的做法是压成二进制串来做,关于这个推荐这篇blog

  • 会了这个以后就好了, 每次搜 2n/2 ,最后合并就好了。

  • 表示以上代码写不出来,看来还要提高码力。

  • 所以我们来写dp吧

  • f[i][j] 表示前 i 个盒子获得 j 颗钻石的概率

  • 考虑第 i 个盒子拿不拿钻石

  • 如果拿那就有 f[i][j]+=f[i1][j1](100p[i])/100.0

  • 如果不拿那就有 f[i][j]+=f[i1][j]p[i]/100.0

  • 先这样跑一遍求出的是没有限制的时候的概率,然后再用dfs减去获得的钱不满足条件的概率

  • 感觉dp比什么meet in the middle不知道好到哪里去

code

  • 自己写的dp
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAXN 40

int n,m;
double f[MAXN][MAXN];
int v[MAXN],p[MAXN];

void dfs(int cnt,double P,int money,int diamond){
    if(money>=m)return;
    if(cnt==n+1){
        if(money<m)f[n][diamond]-=P;
        return;
    }
    dfs(cnt+1,P*p[cnt]/100.0,money+v[cnt],diamond);
    dfs(cnt+1,P*(100-p[cnt])/100.0,money,diamond+1);
    return;
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&v[i],&p[i]);
    f[1][0]=p[1]/100.0;
    f[1][1]=(100-p[1])/100.0;
    for(int i=2;i<=n;i++)
        for(int j=0;j<=i;j++){
            if(j) f[i][j]+=f[i-1][j-1]*(100-p[i])/100.0;
            f[i][j]+=f[i-1][j]*(p[i]/100.0);
        }
    dfs(1,1,0,0);
    for(int i=0;i<=n;i++)
        printf("%.3lf\n",f[n][i]);
    return 0;
}
  • std的meet in the middle
#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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值