zoukankan      html  css  js  c++  java
  • HGOI 20190705 题解

    Problem A 树状数组

    给出数x,一直执行x = x and (x+1)-1 直到 x=0 为止 询问这个数执行运算的次数。

    这个数以二进制的形式表述出 x = s1 & s2 .... s2 & s3 其中s2字段重复n次 &表示连接符号。

    对于$100\%$的数据 $ length(s1),length(s2),length(s3) leq 10^3 ,n leq 10^6$

    Sol : 这道题目就是求x+1中含有二进制1的个数。

    不妨设x=A111...其中A表示一个偶数二进制数,其中后面1的个数可以是任意个。

    x+1 = (A+1)000... 所以 x and (x+1) = (A and (A+1))000... = A000...

    所以x and (x+1) -1 = (A-1)111... 

    可以发现最后的一串1其实是对最后答案没有影响的,所以直接删除即可。

    由于每次会破坏且仅破坏A中的一个1。直接求出A中的1的个数即可。

    比如 : 1111001100 一次只会破坏最后的0,然后把最低的一个1破坏,--> 1111001011->11110010(消掉末尾所有的1)

    这等价于求A=x+1中含有二进制1的个数。

    实现细节: 先求出总的1的个数,然后如果数的末尾有连续的1,那么在总的个数中减去即可。

    复杂度: $O(T imes length(s1+s2+s3) )$

    # pragma GCC optimize(3) 
    # include <bits/stdc++.h>
    # define int long long
    using namespace std;
    const int N=1e5+10;
    char s1[N],s2[N],s3[N];
    int n;
    signed main()
    {
        int T; cin>>T;
        while (T--) {
            scanf("%s%s%s",s1,s2,s3);
            scanf("%lld",&n);
            int len1=strlen(s1),len2=strlen(s2),len3=strlen(s3);
            int tot=0;
            for (int i=0;i<len1;i++) if (s1[i]=='1') tot++;
            for (int i=0;i<len2;i++) if (s2[i]=='1') tot+=n;
            for (int i=0;i<len3;i++) if (s3[i]=='1') tot++;
            tot++;
            for (int i=len3-1;i>=0;i--)
             if (s3[i]=='0') goto End;
             else tot--;
            for (int i=len2-1;i>=0;i--)
             if (s2[i]=='0') goto End;
             else tot--;
            tot-=len2*(n-1);
            for (int i=len1-1;i>=0;i--)
             if (s1[i]=='0') goto End;
             else tot--;
            End:printf("%lld
    ",tot);   
        }
        return 0;
    }
    ftree.cpp

    Problem B 运动会

    m个运动员进行n项比赛,最终成绩为这n项比赛排名相加,现在已知某人的n项比赛的排名$x_i$ 

    考虑等概率情况下,该人的总成绩排名期望值。(排名要求严格)

    对于100%的数据$nleq 100,mleq 1000$

    Sol : 这是一道动态规划的题目。

    定义f[i][j] 表示前i项比赛,排名总和为j的单人期望概率。

    考虑从第i项比赛获得第k名转移而来。 

    转移方程为 : $ f[i][j] = sumlimits_{k=1,k eq x_i}^{m} f[i-1][j-k] $

    由于有m-1个人,对于每一个人其总成绩超过$sumlimits_{i=1}^{n}x_i$ 总期望是 $(m-1) imes sumlimits_{i=1}^{sumlimits_{i=1}^{n}x_i -1} f[n][i] $ 

    所以答案的期望排名就是  $1 + (m-1) imes sumlimits_{i=1}^{sumlimits_{i=1}^{n}x_i -1} f[n][i] $

    然后使用前缀和优化,可以做到$O(mn^2)$ 

    # pragma GCC optimize(3) 
    # include <bits/stdc++.h>
    using namespace std;
    const int N=105,M=1005;
    int n,m,x[N];
    double f[N][N*M];
    double s[N*M];
    double t[N*M];
    int main()
    {
    //  freopen("meeting.in","r",stdin);
    //  freopen("meeting.out","w",stdout);
        scanf("%d%d",&n,&m);
        if (m==1) { puts("1"); return 0;}
        int ret=0;
        for (int i=1;i<=n;i++) scanf("%d",&x[i]),ret+=x[i];
        s[0]=f[0][0]=1;
        for (int i=1;i<=n*m;i++) s[i]=s[i-1]+f[0][i];  
        for (int i=1;i<=n;i++) {
            memset(t,0,sizeof(t));
            for (int j=1;j<=n*m;j++) {
                double sum=s[j-1]-((j-m-1>=0)?s[j-m-1]:0);
                if (j-x[i]>=0&&j-x[i]<=j-1) sum-=f[i-1][j-x[i]];
                sum/=1.0*(m-1);
                f[i][j]+=sum;
                t[j]=t[j-1]+f[i][j];
            }
                memcpy(s,t,sizeof(t));
        }
        double ans=0;   
         for (int j=0;j<ret;j++) ans+=f[n][j];
        printf("%.12lf
    ",1+ans*(m-1));   
        return 0;
    }
    meeting.cpp

    Problem C 区间GCD

    给出一个序列${a_n}$要求维护$m$个操作 查询[l,r]区间GCD个数和在整个序列中的子序列GCD值为k的子序列数目。

    对于100%的数据$n,m leq 10^5 a_i leq 10^9$ 

    Sol : 显然对于求出区间gcd的询问用ST表就可以$O(alpha n log_2 n)$预处理,然后$O(1)$ 求解。

    考虑第二问的一个性质,任何首端固定区间[1,n]的gcd值单调不增而且种类至多为$log_2 n$ 个。

    于是枚举首端位置,然后对于确定的首端,二分不断扩展后端点,找出这相同gcd的$log_2 n$ 子序列gcd,存入map中。

    然后直接用$O(log_2 n log_2 log_2 n)$的代价直接映射即可。

    这样做的复杂度是$O(alpha n log^2_2 n + m log_2 n log_2 log_2 n )$

    #pragma GCC optimize(2)
    # include <bits/stdc++.h>
    # define int long long
    using namespace std;
    const int N=1e5+10;
    int f[N][31],n,m,a[N];
    int LG[N<<1];
    map<int,int>mp;
    int read(){
        int x = 0; int zf = 1; char ch = ' ';
        while (ch != '-' && (ch < '0' || ch > '9')) ch = getchar();
        if (ch == '-') zf = -1, ch = getchar();
        while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar(); return x * zf;
    }
    int gcd(int x,int y)
    {
        if (y==0) return x;
        return gcd(y,x%y);
    }
    void buildST()
    {
        for (int i=1;i<=n;i++) f[i][0]=a[i];
        int t=LG[n]+1;
        for (int j=1;j<t;j++)
         for (int i=1;i<=n-(1<<j)+1;i++)
          f[i][j]=gcd(f[i][j-1],f[i+(1<<(j-1))][j-1]);
    }
    int query(int l,int r)
    {
        int k=LG[r-l+1]; 
        return gcd(f[l][k],f[r-(1<<k)+1][k]);
    }
    void work(int pos)
    {
        int pt=pos;
        while (pt<=n) {
            int g=query(pos,pt);
            int l=pt,r=n,ans;
            while (l<=r) {
                int mid=(l+r)>>1;
                if (query(pos,mid)==g) ans=mid,l=mid+1;
                else r=mid-1;
            }
            mp[g]+=ans-pt+1;
            pt=ans+1;
        }
    }
    signed main()
    {
        int T=read();
        while (T--) {
            mp.clear(); n=read();
            for (int i=0;i<=n*2;i++) LG[i]=log(i)/log(2);
            for (int i=1;i<=n;i++) a[i]=read();
            buildST();
            for (int i=1;i<=n;i++) work(i);
            m=read();
            while (m--) {
                int l=read(),r=read();
                int t=query(l,r);
                printf("%lld %lld
    ",t,mp[t]);
            }   
        }
        return 0;
    }
    gcd.cpp
  • 相关阅读:
    低调做人
    《论语》中发现的问题
    Magic
    雨中游桃花岛
    说完足球说篮球
    转发一个小游戏:看看自己像哪位名人?
    发几个脑筋急转弯题
    Cynthia 终于决定做SOHO
    我家楼上的故事
    上班苦于不能上msn、qq的朋友们有福了
  • 原文地址:https://www.cnblogs.com/ljc20020730/p/11138344.html
Copyright © 2011-2022 走看看