zoukankan      html  css  js  c++  java
  • [ARC089D] ColoringBalls

    ARC089D - ColoringBalls

    题目大意

    有一排 \(n\) 个球,一开始都是白色的。

    然后有 \(m\) 次操作,用一个字符串表示,若第 \(i\) 个字符为r/b则表示可以选择一段区间染成 红色/蓝色。

    不能直接把一个白球染成蓝色。

    求最终不同的球染色情况的个数 \(\mod 10^9+7\)


    分析

    下文视 \(n,m\)同 阶。

    分析最终态,总可以描述为若干连续的红蓝段,中间由白色分开。

    如果确定了每一个红蓝段的长度和交错情况,然后再在 \(n\) 个位置里分配就能得到方案数,因此可以先忽略每一段的长度来进行考虑。

    对于每一个红蓝段,可以分成两类:

    1. 只出现了红色,称这样的段为单一段。

    2. 出现了蓝色,不一定出现红色,称这样的段为复合段。

      考虑这样的段如何构造得到:

      1. 先染了一次红色
      2. 然后染了一次蓝色
      3. 接下来的每一次操作,无论染红色还是蓝色,均可以使得交替次数\(+2\)

      而最终得到的段,交替段的两端的红色段可以为空。

    为了包含到所有情况,并且不算重,构造方案时必然需要贪心地考虑

    可以先枚举有 \(a\) 个复合段,\(b\) 个单一段。

    贪心地将前 \(a+b\)r 操作用以新建一个段,\(a\)个复合段对应着前 \(a\)r

    然后再为每一个复合段贪心地匹配后面的第一个 b 操作,记作 \((pos_i,match_i),i\in[1,a]\)

    那么对于剩下的 r/b,每一个操作均可以对于 \(a\) 个中的某一些进行,称这样的r/b为自由的。

    对于每一个自由的b,设其位置为\(j\),其能操作的段 \((pos_i,match_i)\) 必然满足 \(pos_i<j\)

    对于每一个自由的r,设其位置为\(j\),其能操作的段 \((pos_i,match_i)\) 必然满足 \(match_i<j\)

    对于 \(a\) 个复合段,容易求得它们各自能得到的自由操作数量,记作 \(c_i\),显然 \(c_i\ge c_{i+1}\)

    设每一个段被操作了 \(b_i\) 次,满足条件的方案只需要满足\(c_i\ge \sum_{j=i} b_j\)

    而我们只需考虑 \(b_i\ge b_{i+1}\) 的方案,因为这样的方案更容易被满足。

    其次还需要考虑这 \(a+b\)个段之间的相对顺序:

    1. \(b\)个单一段等价,无顺序;
    2. \(a\) 个复合段根据 \(b_i\) 分类, \(b_i\) 相同的段之间等价,需要在方案中除掉个数的阶乘。
    3. 最终还需要将 \(a,b\) 两部分归并。

    考虑倒着处理 \(dp_{i,j,k}\) 表示考虑了 \([i,a]\) 这些复合段,\(b_i=j\)\(\sum_{l=j} b_l=k\)

    每次枚举一个 \(b_i\) 相同的段进行转移,即

    \[dp_{i,j,k}\leftarrow \sum_{d=1} \frac{1}{d!}\sum_{l=0}^{j-1} dp_{i+d,l,k+jd} \cdot [\ \forall p\in[0,d), k-jp\leq c_{i+p}] \]

    可以在 \(j\) 这一维上前缀和优化,剩余的部分转移复杂度的一个上界为 \(O(n^3\ln n)\)

    最终还需要将 \(a,b\) 两部分归并,并且将 \(n\) 个位置分配到每一个段中。

    如果最终的方案额外加入了 \(i\) 个自由操作,则每一个段可以两类。

    1. 可以为空的段:
      • 每一个复合段两端的红色段;
      • 序列两端的白色段。
    2. 不能为空的段:
      • 每一个复合段中间的段;
      • 每一个单一段;
      • 每一个交替段之间的白色段。

    这是一个简单的插板问题,可以用组合数在 \(O(1)\) 时间求解。

    而需要枚举 \(O(n^2)\)\(a,b\) ,每次需要\(O(n^3\ln n)\)\(\text{dp}\),故总复杂度为 \(O(n^5\ln n)\)

    实际上这个上界非常松,可以随意通过。

    #include<bits/stdc++.h>
    using namespace std;
    
    typedef long long ll;
    typedef unsigned long long ull;
    typedef double db;
    typedef pair <int,int> Pii;
    #define reg register
    #define mp make_pair
    #define pb push_back
    #define Mod1(x) ((x>=P)&&(x-=P))
    #define Mod2(x) ((x<0)&&(x+=P))
    #define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
    #define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
    template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
    template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }
    
    char IO;
    template <class T=int> T rd(){
        T s=0; int f=0;
        while(!isdigit(IO=getchar())) f|=IO=='-';
        do s=(s<<1)+(s<<3)+(IO^'0');
        while(isdigit(IO=getchar()));
        return f?-s:s;
    }
    
    const int N=72,INF=1e9+10,P=1e9+7;
    
    int n,m,ans;
    char s[N];
    int Com[N*4][N*4],J[N],I[N];
    ll qpow(ll x,ll k=P-2) {
        ll res=1;
        for(;k;k>>=1,x=x*x%P) if(k&1) res=res*x%P;
        return res;
    }
    int R[N],mk[N],C[N],pos[N],c;
    // R[i] 表示第i个复合段匹配的第一个b
    // pos 表示每一个a出现的位置
    // C[i] 表示每一个复合r之后的自由点个数
    void Add(int &a,int b){
        a+=b,Mod1(a);
    }
    // 枚举 a 个复合段,b个单一段
    void Calc(int a,int b) {
        if(!a && !b) { ans++; return; }
        rep(i,1,a) if(R[i]>m) return;
        rep(i,1,m) mk[i]=0;
        rep(i,1,a) mk[R[i]]=1;
        rep(i,1,a+1) C[i]=0;
        // 为自由的b找到能放置的第一个复合点
        rep(i,1,m) if(s[i]=='b' && !mk[i]) {
            drep(j,a,1) if(pos[j]<i) {
                C[j]++;
                break;
            }
        }
        // 为自由的r找到能放置的第一个复合点
        rep(i,a+b+1,c) {
            drep(j,a,1) if(R[j]<pos[i]) {
                C[j]++;
                break;
            }
        }
        static int dp[N][N][N];
        // dp[i][j]表示上一个放了i个,一共放了j个,处理掉分母的count[i]!
        // 每次枚举一段,且都放了k个
        dp[a+1][0][0]=1;
        drep(i,a-1,1) C[i]+=C[i+1];
        drep(i,a,1) {
            rep(j,0,C[i]) rep(k,0,C[i]) dp[i][j][k]=0;
            rep(k,1,C[i]) {
                int up=C[i];
                rep(j,i,a) {
                    up=min(up-k,C[j]-k);
                    if(up<0) break;
                    rep(d,0,min(up,C[j+1])) Add(dp[i][k][d+k*(j-i+1)],1ll*dp[j+1][min(k-1,C[j+1])][d]*I[j-i+1]%P);
                }
            }
            dp[i][0][0]=I[a-i+1];
            rep(j,1,C[i]) rep(k,0,C[i]) Add(dp[i][j][k],dp[i][j-1][k]);
        }
        rep(i,0,C[1]) if(dp[1][C[1]][i]) {
            // 计算可以为0的段数以及不可以为0的段数
            int c1=2+a*2; // 两端的黑段,每一个非单一段两端的红段
            int c2=a+b-1+a+i*2+b; // 中间的每一个黑段,每一个非单一段除了红段以外的段,每一个单一段
            if(c2>n) continue;
            ans=(ans+1ll*Com[n+c1-1][c1+c2-1]%P*J[a]%P*Com[a+b][a]%P*dp[1][C[1]][i])%P;
        }
    }
    
    int main() {
        rep(i,0,N*4-1) rep(j,*Com[i]=1,i) Com[i][j]=Com[i-1][j]+Com[i-1][j-1],Mod1(Com[i][j]);
        rep(i,*J=1,N-1) J[i]=1ll*J[i-1]*i%P;
        I[N-1]=qpow(J[N-1]);
        drep(i,N-1,1) I[i-1]=1ll*I[i]*i%P;
        n=rd(),m=rd();
        scanf("%s",s+1);
        int j=1;
        rep(i,1,m) if(s[i]=='r') {
            pos[++c]=i;
            cmax(j,i),j++;
            while(j<=m && s[j]!='b') j++;
            R[c]=j;
        }
        rep(a,0,c) rep(b,0,c-a) if((a+b)*2-1<=n) {
            Calc(a,b);
        }
        printf("%d\n",ans);
    }
    
  • 相关阅读:
    Jenkins+ant循环执行jmeter文件
    Jmeter接口模版说明
    jenkins与远程服务器配置SSH免密登录
    xcode developer tools简介
    MySQL之模糊查询
    MySQL排名函数
    openblas下载安装与使用
    CVX安装使用
    AMD包下载及使用
    Python及相应软件安装
  • 原文地址:https://www.cnblogs.com/chasedeath/p/15566813.html
Copyright © 2011-2022 走看看