1. 程式人生 > >2017.10.5北京清北綜合強化班DAY5

2017.10.5北京清北綜合強化班DAY5

情況 digi tor 並且 得出 兩個 close while span

拼不出的數
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