HGOI7.18集训题解

题解

今天做的是当年某市联考的内部资料。所以题面会更加概括的。(出题人写题解的的时候说是水题但….当年大部分都是0分)我也…..


第一题——树状数组(ftree)

【题目描述】

  • T组数据,给出三段二进制 S1,S2,S3 S 1 , S 2 , S 3 和数字 n n 表示一个数
    S=S1+S2....n+S2+S3
  • 并给出树状数组的运算方式 lowbit(S)1 l o w b i t ( S ) − 1 来更新 S S 直至S=1问要进行几次运算。

  • 首先明确 lowbit(x)=x&(x+1) l o w b i t ( x ) = x & ( x + 1 )
  • 而对 x x 的二进制的末尾全部都是1 x+1 x + 1 的末尾就全部都是0了。那么在第一次 lowbit l o w b i t 操作之后,最后的数都变成了0。
  • 再-1,就又在最后位1之后变成了1。
  • 手动模拟下,就可以知道要多少此操作就可以了。本质操作就是除去最后连续的1之外,之前存在的所有1的个数+1
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
void fff(){
    freopen("ftree.in","r",stdin);
    freopen("ftree.out","w",stdout);
}
char s1[1100],s2[1100],s3[1100];
int n,T;
int main(){
    fff();
    scanf("%d",&T);
    while (T--){
        scanf("%s%s%s",s1,s2,s3);
        scanf("%d",&n);
        bool flag1=false,flag2=false,flag3=false;
        int t1=0,t2=0,t3=0,t=0;
        int sum=0;
        int l=strlen(s3);
        for (int i=l-1;i>=0;i--){
            if(s3[i]=='0') flag3=true;
            if(flag3&&s3[i]=='1') t3++;
        }
        sum+=t3;
        l=strlen(s2);
        for (int i=l-1;i>=0;i--){
            if(flag3){
                if(s2[i]=='1') t2++;
            }else{
                if(s2[i]=='0') flag2=true;
                if(flag2&&s2[i]=='1') t2++;
            }
            if(s2[i]=='1') t++;
        }

        if(!flag3&&flag2)sum+=((n-1)*t+t2);
        else if(flag3) sum+= t2*n;
        else if(!flag2) sum=sum;
        l=strlen(s1);
        for (int i=l-1;i>=0;i--){
            if(flag2||flag3){
                if(s1[i]=='1') t1++;
            }else{
                if(s1[i]=='0') flag1=true;
                if(flag1&&s1[i]=='1') t1++;
            }
        }
        sum+=t1;
        sum++;
        printf("%d\n",sum);
    }
}

第二题——运动会(meeting)

【题目描述】

  • 给出一个人在 m m 人当中在n项的各个排名分数,求出他总排名的期望值。
  • 排名为分数小于他的分数的人数+1。

  • 讲道理这种期望题对我来说就是爆零的题。
  • 这是个概率题,那可以利用dp求解。
  • 方程:
    dp[i][j]=j1k=max(jm),k(ja[i])dp[i1][k]m1 d p [ i ] [ j ] = ∑ k = m a x ( j − m ) , k ≠ ( j − a [ i ] ) j − 1 d p [ i − 1 ] [ k ] m − 1

( a[i] a [ i ] 是当前项目的该人的分数)

  • 虽然题解给出的方程解释的不是很清楚,但大致就是:对于一个人的期望可以先求出他的这个分数的概率。
  • 对于总得分为k,第 i i 个项目的排名得分为j的概率就是在第 i1 i − 1 个项目总得分为 kj k − j 的所有概率之和除以 m1 m − 1 (因为剩下每一个人都有相等的几率来得到这个分数)。然后一层层地乘下去。
  • 转移过程可以利用 O(1) O ( 1 ) 代替 O(n) O ( n ) 由于每一个数都要除以 m1 m − 1 那么可以将其提出,然后求出当前层的所有分数的期望值再来对下一层进行运算。
  • 虽然是个很玄学的过程。但确实得到了正确答案(真的是NOIP????)

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
void fff(){
    freopen("meeting.in","r",stdin);
    freopen("meeting.out","w",stdout);
}
const int MAXN=100005;
int n,m;
int a[105],s=0;
long double f[2][MAXN],sum[2][MAXN];
int main(){
    fff();
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        s+=a[i];// get signal sum
    }
    if(m==1){
        printf("%14lf\n",1.0);
        return 0;
    }


    int t=0;//round 
    f[0][0]=1;//f[t][j]表示当前项目能够拿j分的期望人数 
    sum[0][0]=1;//sum[t][j]表示累加到当前分数的总期望人数 
    for (int i=1;i<=m*n;i++) 
        sum[0][i]=sum[0][i-1]+f[0][i];

    for (int i=1;i<=n;i++){//number
        for (int j=1;j<=n*m;j++){//fenshu
            int l=max(j-m,0),r=j-1;//l表示..r表示上一个分数 
            f[t^1][j]=(sum[t][r]-(l==0?0:sum[t][l-1]))/(m-1);//下一项的得分为j的期望人数是当前项的所有期望人数的递推 
            if(j-a[i]>=0)//减去自己的概率,因为自己在大于这个分数的时候也会被包括到m-1的人当中
                f[t^1][j]-=f[t][j-a[i]]/(m-1);//
        }
        for (int j=0;j<=n*m;j++)
            f[t][j]=0;//滚动置零 
        t^=1;
        sum[t][0]=f[t][0];
        for (int j=1;j<=m*n;j++) 
            sum[t][j]=sum[t][j-1]+f[t][j];//滚动求和 
    }
    long double ans=sum[t][s-1]*(m-1);
    printf("%.14lf\n",(double)ans+1);
}

第三题——区间gcd(gcd)

【题目描述】

  • 给出T组数据。每组为长度为 n n 的序列,对序列进行m此查询,给出区间[l,r],求出 [l,r] [ l , r ] 之间的 gcd g c d 的值并且求出与此区间相等的 gcd g c d 的值相等的区间个数。

  • 首先我们知道,在区间当中 gcd g c d 可以有单调性,即区间越大, gcd g c d 越小。同时 gcd g c d 可以利用st倍增来进行预处理。查询的第一问来进行类似于 O(1) O ( 1 ) 的查询。效率比较高。
  • 而对于第二问,考场上真的没什么思路。dasxxx大佬相处twopointer的算法进行线性查找,但无奈复杂度太高只拿了90分。标程用处了二分(what???)。枚举左端点,将所得的二分答案放到map当中。(我这个直接暴力存结果炸了内存orz)

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <map>
#include <cmath>
#define LL long long
using namespace std;
void fff(){
    freopen("gcd.in","r",stdin);
    freopen("gcd.out","w",stdout);
}
const int MAXN=100002;
int T;
map<int, LL> S;
int n,m,q;
int a[MAXN];
int e[MAXN],cnt[MAXN];
int st[MAXN][20];
int gcd(int a,int b){
    return (b==0)? a:gcd(b,a%b);
}
int query(int l,int r){
    int y=e[r-l+1];
    return gcd(st[l][y],st[r-(1<<y)+1][y]);
}
void work(int x){
    int y=x;
    int cnt=0;
    while (y<=n){
        int k=query(x,y);
        int l=y,r=n,yy;
        while (l<=r){
            int mid=(l+r)>>1;
            if(query(x,mid)==k) yy=mid,l=mid+1;
            else r=mid-1;
        }
        S[k]+=yy-y+1;
        y=yy+1;
    }
}

int main(){
    fff();
    scanf("%d",&T);
    while (T--){
        scanf("%d",&n);
        S.clear();
        e[1]=0;
        int j=0;
        for (int i=2;i<=n;i++){
            if(1<<(j+1)<=i) j++;
            e[i]=j;
        }

        for (int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            st[i][0]=a[i];
        }
        for (j=1;j<=19;j++){
            for (int i=1;i<=n&&(i+(1<<j)-1<=n);i++){
                st[i][j]=gcd(st[i][j-1],st[i+(1<<j)-(1<<(j-1))][j-1]);
            }
        }
        for (int i=1;i<=n;i++){
            work(i);
        }
        scanf("%d",&m);
        for (int i=1;i<=m;i++){
            int l,r;
            scanf("%d%d",&l,&r);
            int k=query(l,r);
            printf("%d %lld\n",k,S[k]);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值