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

    Problem A 树状数组

      给出下列$C++$代码:

      

      设区间加操作$modify(l,r)$为调用两次$update(r,1)$和$update(l-1,-1)$

      设$f(l,r)$表示在初始$cnt[i]$全部是$0$的情况下进行$modify(l,r)$操作后,cnt数组中含有非$0$元素的个数。

      给出$T$组询问,输出$sumlimits_{i=1}^{n} sumlimits_{j=i}^{n} f(i,j)$的值。

      对于 $100\%$的数据满足,$Tleq 10^4 , n leq 10^{18}$

    Solution :

       上来就做如此恶心的数位$DP$,不过也好,复习了一下数位$DP$。

       观察到$f(l,r)$答案的构成是$l$的二进制个数+$r$的二进制个数 - $l,r$二进制表示形成字符串的lcp的$1$的个数。

       对$n$二进制拆分,从高位到低位依次考虑,设$f[i][op1][op2][j]$当前考虑到第$i$位,当前$r$对$n$是否有限制(op1),当前$l$是否对$r$有限制(op2)。

      而$j$这一维状态,分两次$dp$考虑。

      第一次,我们要求出所有数对$(0 leq l< r leq n)$中两个数的二进制$1$的个数的和。

        所以,容易的设$j$为更高位$1$的数目,而整个dp的值表示方案数,总和就是方案数$ imes j$的和

      第二次,我们要求出所有数对$(0 leq l< r leq n)$中两个数二进制串的$lcp$的$1$的个数

        所以,容易的设$j$为更高位公共$1$的个数,同时整个$dp$的值表示方案数,总和就是方案数$ imes j$的和

      按照普通的数位$dp$转移即可,需要注意一些细节,这里就不再赘述一些沙雕错误了,代码用了循环展开,可读性极差。

      复杂度是$O(T {log_2}^2 n)$

    # pragma GCC optimize(3,"Ofast") 
    # include<bits/stdc++.h>
    # define Rint register int  
    using namespace std;
    const int mo=1e9+7;
    long long n;
    int a[105];
    int f[64][2][2][2*64];
    inline void pls(int &a, int b) {
        a = (a + b >= mo ? a + b - mo : a + b);
    }
    signed main(){
        int T; scanf("%d",&T);
        while (T--) {
            scanf("%lld",&n);a[0]=0; while (n) { a[++a[0]]=n&1; n>>=1;}
            f[a[0]][0][1][0]=1, f[a[0]][1][0][1]=1, f[a[0]][1][1][2]=1;
            int HJCAK = a[0] << 1;
            for (Rint i=a[0];i>=2;i--)
                for (Rint op1=0;op1<=1;op1++){
                    Rint op2 = 0;
                    for (Rint j=0;j<=HJCAK;j++) {
                        pls(f[i-1][(op1)&&!a[i-1]][op2][j],f[i][op1][op2][j]);
                        if (op1 && a[i-1]) pls(f[i-1][1][op2][j+2],f[i][op1][op2][j]);
                        if (!op1) pls(f[i-1][0][op2][j+2],f[i][op1][op2][j]);
                        if (!op2) pls(f[i-1][op1&&!a[i-1]][0][j+1],f[i][op1][op2][j]);
                        if (op1 && a[i-1]) pls(f[i-1][1][0][j+1],f[i][op1][op2][j]);
                        if (!op1) pls(f[i-1][0][0][j+1],f[i][op1][op2][j]);
                    }
                    op2 = 1;
                    for (Rint j=0;j<=HJCAK;j++) {
                        pls(f[i-1][(op1)&&!a[i-1]][op2][j],f[i][op1][op2][j]);
                        if (op1 && a[i-1]) pls(f[i-1][1][op2][j+2],f[i][op1][op2][j]);
                        if (!op1) pls(f[i-1][0][op2][j+2],f[i][op1][op2][j]);
                        if (!op2) pls(f[i-1][op1&&!a[i-1]][0][j+1],f[i][op1][op2][j]);
                        if (op1 && a[i-1]) pls(f[i-1][1][0][j+1],f[i][op1][op2][j]);
                        if (!op1) pls(f[i-1][0][0][j+1],f[i][op1][op2][j]);
                    }
                }
            int res1=0;
            for (Rint op1=0;op1<=1;op1++)
                for (Rint j=1;j<=HJCAK;j++)
                    pls(res1,1ll*f[1][op1][0][j]*j%mo);
            memset(f,0,sizeof(f));
            f[a[0]][0][1][0]=1, f[a[0]][1][0][0]=1, f[a[0]][1][1][1]=1;
            for (Rint i=a[0];i>=2;i--) 
                for (Rint op1=0;op1<=1;op1++){
                    Rint op2 = 0;
                    for (Rint j=0;j<=a[0];j++) {
                        pls(f[i-1][op1&&!a[i-1]][op2][j],f[i][op1][op2][j]);
                        if (op1 && a[i-1]) pls(f[i-1][1][op2][j+op2],f[i][op1][op2][j]);
                        if (!op1) pls(f[i-1][0][op2][j+op2],f[i][op1][op2][j]);
                        if (!op2) pls(f[i-1][op1&&!a[i-1]][0][j],f[i][op1][op2][j]);
                        if (op1 && a[i-1]) pls(f[i-1][1][0][j],f[i][op1][op2][j]);
                        if (!op1) pls(f[i-1][0][0][j],f[i][op1][op2][j]);
                    }
                    op2 = 1;
                    for (Rint j=0;j<=a[0];j++) {
                        pls(f[i-1][op1&&!a[i-1]][op2][j],f[i][op1][op2][j]);
                        if (op1 && a[i-1]) pls(f[i-1][1][op2][j+op2],f[i][op1][op2][j]);
                        if (!op1) pls(f[i-1][0][op2][j+op2],f[i][op1][op2][j]);
                        if (!op2) pls(f[i-1][op1&&!a[i-1]][0][j],f[i][op1][op2][j]);
                        if (op1 && a[i-1]) pls(f[i-1][1][0][j],f[i][op1][op2][j]);
                        if (!op1) pls(f[i-1][0][0][j],f[i][op1][op2][j]);
                    }
                }
            int res2=0;
            for (Rint op1=0;op1<=1;op1++)
                for (Rint j=1;j<=a[0];j++)
                    pls(res2,1ll*f[1][op1][0][j]*j%mo);
            res2 <<= 1; res2 >= mo && (res2 -= mo);
            printf("%d
    ",(res1-res2+mo)%mo);
            if(T) memset(f,0,sizeof(f));
        }
        return 0;
    }
    bit.cpp

    Problem B 雇佣妹抖  

      给出初始$n$个数$a_i$,支持下列两种操作,共$m$个操作:

      1. 给出一个$b$,输出大于等于$b$的所有数,构成了多少个极大联通子段

      2.给出一个$p,k$,将$a_{p} = k$的单点修改。

       对于$100\%$的数据满足$1 leq n,m leq 2 imes 10^5$

    Solution : 

      这道题目让我们想到了氨基酸和肽链。肽链数 $=$ 氨基酸数 $-$ 肽键数。(过于形象引起不适~)

      考虑答案是如何构成的,大于等于$b$的个数,减去相邻两个都大于等于$b$的个数。

      可以将单点和相邻数的$min$分开维护,离散化以后用树状数组或者直接写平衡树都行。

      时间复杂度为$O(mlog_2n)$

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    const int maxn = 400010;
    int n, qq, B, c, d, d1, T, num;
    int a[maxn], b[maxn], t[maxn], tr[maxn], q[maxn][3];
    int lowbit(int x) { return x & (-x); }
    void add1(int x, int y) {
        for (int i = x; i <= num; i += lowbit(i)) t[i] += y;
    }
    void add2(int x, int y) {
        for (int i = x; i <= num; i += lowbit(i)) tr[i] += y;
    }
    int find1(int x) {
        int ans = 0;
        for (int i = x; i >= 1; i -= lowbit(i)) ans += t[i];
        return ans;
    }
    int find2(int x) {
        int ans = 0;
        for (int i = x; i >= 1; i -= lowbit(i)) ans += tr[i];
        return ans;
    }
    int main() {
        scanf("%d%d", &n, &qq);
        for (int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
            b[i] = a[i];
        }
        int cnt = n;
        for (int i = 1; i <= qq; i++) {
            scanf("%d", &T);
            if (T == 1)
                scanf("%d", &q[i][1]);
            else {
                scanf("%d%d", &q[i][0], &q[i][1]);
            }
            b[++cnt] = q[i][1];
        }
        sort(b + 1, b + cnt + 1);
        num = unique(b + 1, b + cnt + 1) - b - 1;
        for (int i = 1; i <= n; i++) {
            a[i] = lower_bound(b + 1, b + num + 1, a[i]) - b;
            add1(a[i], 1);
        }
        for (int i = 1; i < n; i++) add2(min(a[i], a[i + 1]), 1);
        for (int i = 1; i <= qq; i++) {
            if (q[i][0] == 0) {
                int B1 = lower_bound(b + 1, b + 1 + num, q[i][1]) - b;
                int ans = n - find1(B1 - 1) - (n - 1 - find2(B1 - 1));
                printf("%d
    ", ans);
            } else {
                c = q[i][0];
                d = lower_bound(b + 1, b + num + 1, q[i][1]) - b;
                add1(a[c], -1);
    
                if (c > 1)
                    add2(min(a[c], a[c - 1]), -1), add2(min(d, a[c - 1]), 1);
                if (c < n)
                    add2(min(a[c], a[c + 1]), -1), add2(min(d, a[c + 1]), 1);
    
                a[c] = d;
                add1(a[c], 1);
            }
        }
        return 0;
    }
    maid.cpp

    Problem C 建造

      有$n$幢房屋需要规划建设,高度恰好是$[1,n]$的一个排列,需要建设在$[1,X]$的数轴的整点上。

      设第$i$幢房屋位置为$pos_i$,高度为$h_i$,第$j$幢房屋的位置为$pos_j$,高度为$h_j$。

      一个合法的建设方案,需要有任意$|pos_i - pos_j| leq max{h_i,h_j}$恒成立。

      两个建设方案不同,当且仅当至少有$1$个位置的建设情况不同。

      在模质数$p$的意义下输出答案。

      对于$100\%$的数据满足$1 leq n leq 100 , 1 leq X leq 10^5 , 10^8 leq p leq 10^9$

    Solution : 

      显然不能直接在数轴上直接$DP$,任意的限制只要按照排列顺序就可以转化为相邻的限制。

      设$d_1 = pos_1 - 1, d_i = pos_i - pos_{i-1} (igeq 2)$

       于是限制可以转化为$d_i geq max(h_i , h_{i-1})$且$sumlimits_{i=1}^{n} d_i  + tmp = X$,

      且$d_1,...,d_n , tmp$都是非负整数。

      如果知道$S = sumlimits_{i=2}^{n} max(h_{i-1},h_{i})$, 那么容易使用插板法求出合法答案数目$inom{X-1-s+n}{n}$

      现在只要求出排列$n$个元素,获得$S$的方案数即可,答案就是方案数乘以组合数即可。

      设$f[i][j][k]$表示从低到高考虑到第$i$个建筑,当前已知的$S$的值至少是$j$,还有$k$个空位来插入更大的值。

      考虑定义$f[1][0][0] = 1$,刷表法求出所有答案。

      考虑将$i+1$放在已知序列的首位,考虑两种可能,

      • 首位和次位之间之间还要插入更大的数。
      • 首位和次位之后不会插入数。

      考虑将$i+1$放在已知序列的末尾,考虑两种可能。

      • 次末尾和末尾之间还要插入更大的数。
      • 次末尾和末尾值之间不会插入更大的数。

      考虑将$i+1$放在一般位置,考虑三种可能。

      • 前面和后面两个位置都会插入更大的数。
      • 前面和后面都不会插入更大的数。
      • 前面和后面其中只有1个位置会插入更大的数。

      时间复杂度就是$O(n^4+X)$,需要使用滚动数组+卡常$AC$本题。    

    # pragma GCC optimize(3,"Ofast")
    # include <bits/stdc++.h>
    # define Rint register int
    # define int long long
    using namespace std;
    const int N=1e6+10;
    const int M=105;
    int n,m,mo;
    int fac[N],inv[N],f[2][M*M][M];
    inline int Pow(int x,int n,int mo) {
        int ans = 1;
        while (n) {
            if (n&1) ans=ans*x%mo;
            x=x*x%mo;
            n>>=1;
        }
        return ans % mo;
    }
    inline int C(int n,int m) {
        return (m>n)?(0):(fac[n]*inv[m]%mo*inv[n-m]%mo);
    }
    inline void pls(int &a, int b) {
        a = (a + b >= mo ? a + b - mo : a + b);
    }
    signed main()
    {
        cin>>n>>m>>mo;
        fac[0]=1; for (Rint i=1;i<=n+m;i++) fac[i]=fac[i-1]*i%mo;
        inv[0]=1; for (Rint i=1;i<=n+m;i++) inv[i]=Pow(fac[i],mo-2,mo);
        int p;
        f[p=1][0][0]=1;
        for (Rint i=1;i<n;i++) {
            for (Rint j=0;j<=n*n;j++)
                for (Rint k=0;k<=i;k++) {
                    pls(f[p^1][j][k+1],f[p][j][k]);
                    pls(f[p^1][j+i+1][k],f[p][j][k]);
                    pls(f[p^1][j][k+1],f[p][j][k]);
                    pls(f[p^1][j+i+1][k],f[p][j][k]);
                    if (k!=0) pls(f[p^1][j+2*(i+1)][k-1],f[p][j][k]*k%mo);
                    pls(f[p^1][j][k+1],f[p][j][k]*k%mo);
                    pls(f[p^1][j+i+1][k],2*k*f[p][j][k]%mo);
            }
            memset(f[p],0,sizeof(f[p]));
            p^=1;
        }
        int ans = 0;
        for (Rint i=0;i<=n*n;i++) {
            pls(ans,f[p][i][0]*C(m-1-i+n,n)%mo);
        }
        cout<<ans<<'
    ';
        return 0;
    }
    build.cpp

     

  • 相关阅读:
    使用.Net Core 2.2创建windows服务
    Ubuntu 18.04 安装部署Net Core、Nginx全过程
    Task的在主线程处理异常信息的Helper类
    行转列的处理。
    netcore 下加密遇到的问题
    关于安装angular-cli环境报错的问题
    64位的windows服务安装问题
    EF.Mysql在codefirst模式下调用存储过程,和再DbFirst模式下的调用
    阿里云消息队列的C#使用http接口发送消息实例
    [转载]EF或LINQ 查询时使用IN并且根据列表自定义排序方法
  • 原文地址:https://www.cnblogs.com/ljc20020730/p/11815239.html
Copyright © 2011-2022 走看看