组队练习数学-04

传送门
CF466D
CF1666F
CF1228E
CF932E
CF666C

A. cf466D

题意:长n的数组a,每次选定区间[l,r],使得区间内的数字都加1,但是每个点作为l,r最多各一次,问最小多少次使得数组全变成h

Solution from lnn:

1.可以发现相邻两个数字最多差1

2.定义dp[i]为前i个数符合条件的方案数,每个点需要被覆盖的次数:las = h-a[i-1] , now = h-a[i]

当las+1 == now时,延续之前的线段并在i点新启一个线段,dp[i] = dp[i-1]

当las-1 == now时,可以选一个区间停在i-1,dp[i] = dp[i-1]*las

当las == now时,可以直接延续,也可以停一个再新启,dp[i] = dp[i-1]*(las + 1)

3.时间复杂度:O(N)

void solve(){
    for(ll i = 1 ; i <= n ; ++i){
        if(a[i] > h){
            cout << 0 << endl ;
            return;
        }
    }
    if(a[n]+1 < h){
        cout << 0 << endl ;
        return;
    }
    a[0] = h;
    dp[0] = 1;
    for(ll i = 1 ; i <= n ; ++i){
        ll las = h - a[i-1];
        ll now = h - a[i];
        if( !(las-1 <= now && now <= las+1) ){
            cout << 0 << endl ;
            return;
        }
        if(now == las){
            dp[i] = (dp[i-1]*(1 + las))%mod;
        }else if(now < las){
            dp[i] = dp[i-1]*las%mod;
        }else if(now > las){
            dp[i] = dp[i-1];
        }
    }
    cout << dp[n] << endl ;
}

B.cf1666F

题意:给定一组数字a,n<=5e3,重新排列,满足以下两个条件,求方案数
1.b1b3…>bn−1<bn (偶数位置作为波峰
2.b2<b4<b6<…<bn (偶数位置递增

Solution from lnn:

1.可以发现最大的两个数字只能放在最后两个偶数位置,顺着想,发现应该按照a从大到小考虑,
那么数字只能从后往前放置,偶数只能一个一个贴着放,奇数可以放在已经放置的偶数之间

2.dp[i][j]表示>=i的数全都放置完毕了,并且放置了j个偶数
cnt[i]表示数字i出现了几次,suf[i]为cnt的后缀
如果i-1全放置在奇数位置,设剩余res个奇数位置
dp[i-1][j] = dp[i][j] * res * (res-1) * (res-2) * … * (res - cnt[i-1]+1)

如果i-1放置在偶数位置,偶数递增,最多放置一个
dp[i-1][j+1] = cnt[i-1] * dp[i][j] * res * … * (res - cnt[i-1]+2)

奇数放置是有重复的,最后的答案dp[1][n/2]要除以所有的cnt[i]!
所以偶数的转移多乘一个cnt[i-1],统一去重

3.时间复杂度:O(N^2)

void solve(){
    dp[tot+1][0] = 1;
    for(ll i = tot+1 ; i >= 2 ; --i){               /// >= i 的数已经处理完毕
        for(ll j = 0 ; j <= nnn/2 ; ++j){           /// 已经占用j个偶数
            ll odd = (j == nnn/2 ? nnn/2 : max(0ll , j-1) );
            odd -= (suf[i] - j);
            ///全放在奇数位置
            if(a[i-1] <= odd){
                ll jie = fac[odd] * inv[odd-a[i-1]]%mod;
                dp[i-1][j] = (dp[i-1][j] + dp[i][j] * jie%mod )%mod;
            }
            if(j < nnn/2 && a[i-1]-1 <= odd){
                ll jie = fac[odd] * inv[odd-a[i-1]+1]%mod;
                dp[i-1][j+1] = (dp[i-1][j+1] + a[i-1] * dp[i][j]%mod * jie%mod)%mod;
            }
        }
    }
    ll ans = dp[1][nnn/2];
    for(ll i = 1 ; i <= tot ; ++i){
        ans = ans * inv[a[i]]%mod;
    }
    cout << (ans%mod + mod)%mod << endl ;
}

C. cf1228E

题意:一个n*n的方阵,每个位置可以填1~k,求多少种填法,使得每行每列至少有1一个位置有1.(n<=250,k<=1e9)

Solution from y:容斥

法一:

设f(i)表示至多i行存在1,且每列都有1。

设g(i)表示恰好i行存在1,且每列有1.

a n s = g ( n ) = ∑ i = 0 n ( − 1 ) n − i C ( n , i ) f ( i ) ans = g(n) =\sum_{i=0}^n (-1)^{n-i}C(n,i)f(i) ans=g(n)=i=0n(1)niC(n,i)f(i)

f ( i ) = ( k i − ( k − 1 ) i ) n ∗ ( k − 1 ) n 2 − n i f(i) = (k^i-(k-1)^i)^n * (k-1)^{n^2-ni} f(i)=(ki(k1)i)n(k1)n2ni,上面说至多i行存在1,即剩下n-i行一定没有1,方案数为 ( k − 1 ) n 2 − n i (k-1)^{n^2-ni} (k1)n2ni,选定的i行可能存在1,方案数为 ( k i − ( k − 1 ) i ) n (k^i-(k-1)^i)^n (ki(k1)i)n,表示用1k的数任意填的方案-用2k的数任意填的方案数。

因为算至多的时候可以将每列都有1的方案算出来,所以容斥1次即可。

#define int long long
const int N=1e3+5;
const int mod=1e9+7;
const double eps=1e-8;
int n,m,k;
int fac[N],ni_f[N];
ll qsm(int a,int b){
    ll ans=1,tmp=a;
    while( b ) {
        if( b&1 ) ans = ans * tmp%mod;
        tmp = tmp * tmp%mod;
        b>>=1;
    }
    return ans;
}
void ini(){
    int maxn = 1e3;
    fac[0]=1;
    _for(i,1,maxn) fac[i] =  (fac[i-1] * i)%mod;
    ni_f[maxn] = qsm(fac[maxn],mod-2);
    _rep(i,maxn-1,0) ni_f[i] = ni_f[i+1] * (i+1)%mod;
}
ll C(int n,int m){
    if( m==n || m==0 ) return 1;
    if( n < m ) return 0;
    return fac[n] * ni_f[n-m]%mod * ni_f[m]%mod;
}
signed main(){
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
#endif  
    IOS;
    ini();
    cin>>n>>k;
    int ans=0;
    _for(i,1,n){
        int flag = (n-i)&1?-1:1;
        int t = (qsm(k,i) - qsm(k-1,i) + mod )%mod;
        int yu = n*n - n*i;
        ans = (ans + mod + flag * C(n,i) * qsm(t,n)%mod * qsm(k-1,yu)%mod )%mod;
    }
    cout<<ans<<endl;
    AC; 
}

法二:

考虑求不合法的方案。

设f(i)表示钦定i行没有1,列都有1的方案数。

g(i)表示恰好i行没有1,列都有1的方案数。

a n s = g ( 0 ) = ∑ i = 0 n ( − 1 ) i C ( n , i ) f ( i ) ans =g(0)= \sum_{i=0}^n(-1)^iC(n,i)f(i) ans=g(0)=i=0n(1)iC(n,i)f(i)

再去求f(i),再用一次容斥

设h(i,j)表示在i行没有1的情况下,钦定j行没有1

则$ f(i) = \sum_{j=0}n(-1)jC(n,j)h(i,j)$

h ( i , j ) = ( k − 1 ) n i ∗ ( k − 1 ) j ( n − i ) ∗ k n 2 − n i − j ( n − i ) h(i,j) = (k-1)^{ni}*(k-1)^{j(n-i)}*k^{n^2-ni-j(n-i)} h(i,j)=(k1)ni(k1)j(ni)kn2nij(ni)

因为求的是不合法情况,即行和列都要求反面,需要容斥两次。

D . cf932E

题意:求 ∑ i = 1 n i k C ( n , i ) , 其中 n < = 1 e 9 , k < = 5000 \sum_{i=1}^ni^kC(n,i),其中n<=1e9,k<=5000 i=1nikC(n,i),其中n<=1e9,k<=5000

Solution from y:第二类斯特林数

注意到k的范围比较小,考虑枚举k。

从组合数学的意义 i k i^k ik表示i个有区别的盒子,k个不同球放置到盒子的方案数。

最多有k个盒子不是空盒子,考虑枚举非空盒子。

枚举非空盒子,然后将k个球放置到盒子中,这里有第二类斯特林数,由于第二类斯特林数的盒子是无区别的所以还要乘上阶乘,代表给盒子编号。

∑ i = 1 n i k C ( n , i ) = ∑ i = 1 n C ( n , i ) ∑ j = 0 k S 2 ( k , j ) j ! C ( i , j ) \sum_{i=1}^ni^kC(n,i)=\sum_{i=1}^nC(n,i)\sum_{j=0}^kS2(k,j)j!C(i,j) i=1nikC(n,i)=i=1nC(n,i)j=0kS2(k,j)j!C(i,j)

原始式子的意义是n个有别的盒子,选i个放k个球的方案数。转化成,n个有别的盒子,选i个,再指定j个盒子非空,放球的方案数。

然后就是推式子了。

上式 = ∑ j = 0 k S 2 ( k , j ) j ! ∑ i = 0 n C ( n , i ) C ( i , j ) = ∑ j = 0 k S 2 ( k , j ) j ! C ( n , j ) ∗ 2 n − j = ∑ j = 0 k S 2 ( k , j ) n ! ( n − j ) ! ∗ 2 n − j 上式=\sum_{j=0}^kS2(k,j)j!\sum_{i=0}^nC(n,i)C(i,j)\\=\sum_{j=0}^kS2(k,j)j!C(n,j)*2^{n-j}\\=\sum_{j=0}^kS2(k,j)\frac{n!}{(n-j)!}*2^{n-j} 上式=j=0kS2(k,j)j!i=0nC(n,i)C(i,j)=j=0kS2(k,j)j!C(n,j)2nj=j=0kS2(k,j)(nj)!n!2nj

然后就可以直接做了,阶乘部分可以边做边推。

复杂度 O ( k 2 + k l o g n ) O(k^2+klogn) O(k2+klogn),其中k方是预处理第二类斯特林数。当然也可以O(klogk)用生成函数预处理出来,不过这里k比较小,没必要。

#define int long long
const int N=5e3+5;
const int mod=1e9+7;
const double eps=1e-8;
int n,m,k;
int S2[N][N],b[N];
int fac[N],ni_f[N];
ll qsm(int a,int b){
    ll ans=1,tmp=a;
    while( b ) {
        if( b&1 ) ans = ans * tmp%mod;
        tmp = tmp * tmp%mod;
        b>>=1;
    }
    return ans;
}
void ini(){
    int maxn = 5e3;
    fac[0]=1;
    b[0]=1;
    _for(i,1,maxn) fac[i] =  (fac[i-1] * i)%mod,b[i] = b[i-1]*2%mod;
    ni_f[maxn] = qsm(fac[maxn],mod-2);
    _rep(i,maxn-1,0) ni_f[i] = ni_f[i+1] * (i+1)%mod;
    S2[0][0]=1;
    _for(i,1,maxn){
        _for(j,1,i){
            S2[i][j] = (S2[i-1][j-1] + S2[i-1][j]*j%mod)%mod;
        }
    }
}
ll C(int n,int m){
    if( m==n || m==0 ) return 1;
    if( n < m ) return 0;
    return fac[n] * ni_f[n-m]%mod * ni_f[m]%mod;
}
signed main(){
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
#endif  
    IOS;
    ini();
    cin>>n>>k;
    int ans =0;
    int base = 1;
    int nn = n;
    int ma = min(k,n);
    _for(i,0,ma){
        int t = S2[k][i]*base%mod*qsm(2,n-i)%mod;
        ans = (ans + t)%mod;
        base = base * nn%mod;
        nn--;
    }
    cout<<ans<<endl;
    AC; 
}

  • List item

E .cf666C

题意:两种操作,操作一,给定字符串s,操作二,给定n,问有多少个长为n的字符串t,使得s是t的子序列。

Solution from y: 根号分治

首先考虑暴力dp处理每个询问, d p [ i ] [ j ] dp[i][j] dp[i][j]表示模式串s已经匹配了前i个字符后最长子序列长度为j的方案数。

考虑匹配串t加入一个新字符ch,有转移方程

d p [ i ] [ j ] − > d p [ i + 1 ] [ j + 1 ] ( c h = s [ i + 1 ] ) dp[i][j] -> dp[i+1][j+1] (ch = s[i+1]) dp[i][j]>dp[i+1][j+1](ch=s[i+1])

25 d p [ i ] [ j ] − > d p [ i + 1 ] [ j ] ( c h ! = s [ i + 1 ] ) 25dp[i][j] - >dp[i+1][j](ch!=s[i+1]) 25dp[i][j]>dp[i+1][j](ch!=s[i+1])

发现dp式子与s的内容无关,只与|s|有关。

不妨把s视为只有与a组成的长度为m的字符串。那么只要考虑匹配串中a的数量即可。

设m = |s| , n = |t|

a n s ( n , m ) = 2 6 n − ∑ i = 0 m − 1 C ( n , i ) 2 5 n − i ans(n,m) =26^n-\sum_{i=0}^{m-1}C(n,i)25^{n-i} ans(n,m)=26ni=0m1C(n,i)25ni,考虑总方案减去不合法的方案。时间复杂度O(m)

考虑根号分治,当 m < = n m<=\sqrt{n} m<=n 时直接算这个式子。

$ m > \sqrt{n} 时,不同的 m 最多只有根号级别个。考虑把式子的瓶颈转移到 n 上。因为 a n s ( n , m ) 最多有 时,不同的m最多只有根号级别个。考虑把式子的瓶颈转移到n上。因为ans(n,m)最多有 时,不同的m最多只有根号级别个。考虑把式子的瓶颈转移到n上。因为ans(n,m)最多有n\sqrt{n}$个结果,求的时候记忆化一下即可。

S ( n ) = ∑ i = 0 m − 1 C ( n , i ) 2 5 n − i = ∑ i = 0 m − 1 ( C ( n − 1 , i − 1 ) + C ( n − 1 , i ) ) 2 5 n − i = 2 5 n ∑ i = 0 m − 1 C ( n − 1 , i − 1 ) 2 5 − i + 2 5 n ∑ i = 0 m − 1 C ( n − 1 , i ) 2 5 − i = 2 5 n − 1 ∑ i = 0 m − 2 C ( n − 1 , i ) 2 5 − i + 2 5 n ∑ i = 0 m − 1 C ( n − 1 , i ) 2 5 − i = 26 S ( n − 1 ) − C ( n − 1 , m − 1 ) 2 5 n − m S(n) = \sum_{i=0}^{m-1}C(n,i)25^{n-i}\\=\sum_{i=0}^{m-1}(C(n-1,i-1)+C(n-1,i))25^{n-i}\\=25^n\sum_{i=0}^{m-1}C(n-1,i-1)25^{-i}+25^n\sum_{i=0}^{m-1}C(n-1,i)25^{-i}\\=25^{n-1}\sum_{i=0}^{m-2}C(n-1,i)25^{-i}+25^n\sum_{i=0}^{m-1}C(n-1,i)25^{-i}\\=26S(n-1)-C(n-1,m-1)25^{n-m} S(n)=i=0m1C(n,i)25ni=i=0m1(C(n1,i1)+C(n1,i))25ni=25ni=0m1C(n1,i1)25i+25ni=0m1C(n1,i)25i=25n1i=0m2C(n1,i)25i+25ni=0m1C(n1,i)25i=26S(n1)C(n1,m1)25nm

化成这样就可以O(n)求了。然后对于根号个m都这样求,所以是 O ( n n ) O(n\sqrt{n}) O(nn )的复杂度。

#define int long long
const int N=1e5+5;
const int mod=1e9+7;
const double eps=1e-8;
int n,m,k;
string s;
int fac[N],ni_f[N],base[N];
map<int,int> f[N];
ll qsm(int a,int b){
    ll ans=1,tmp=a;
    while( b ) {
        if( b&1 ) ans = ans * tmp%mod;
        tmp = tmp * tmp%mod;
        b>>=1;
    }
    return ans;
}
void ini(){
    int maxn = 1e5;
    fac[0]=base[0]=1;
    _for(i,1,maxn){
         fac[i] =  (fac[i-1] * i)%mod;
         base[i] = base[i-1]*25%mod;
    }
    ni_f[maxn] = qsm(fac[maxn],mod-2);
    _rep(i,maxn-1,0) ni_f[i] = ni_f[i+1] * (i+1)%mod;
}
ll C(int n,int m){
    if( m==n || m==0 ) return 1;
    if( n < m ) return 0;
    return fac[n] * ni_f[n-m]%mod * ni_f[m]%mod;
}
int solve1(string s){
    m = s.size();
    int ans = qsm(26,n);
    _for(i,0,min(n,m-1)){
        ans = (ans +mod - C(n,i)*base[n-i]%mod)%mod;
    }
    return ans;
}
int cal(int n,int m){
    m = s.size();
    if( n < m ) return 0;
    if( f[n].count(m) ) return f[n][m];
    if( n == m ){
        int ans = 0;
        _for(i,0,m-1){
            ans = (ans + C(n,i)*base[n-i]%mod)%mod;
        }
        return ans;
    }
    if( n==1 ){
        int t = 0;
        if( m>1 ) t = qsm(25,mod-2);
        return 25*( 1 + t)%mod; 
    }
    return f[n][m] = (26*cal(n-1,m)%mod +mod - C(n-1,min(n-1,m-1))*base[n-m]%mod)%mod;
}
signed main(){
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
#endif  
    IOS;
    ini();
    int q;cin>>q;
    cin>>s;
    while( q-- ){
        int op;cin>>op;
        if( op==1 ) cin>>s;
        else{
            cin>>n;
            int ans = 0;
            if( n < s.size() ){
                cout<<0<<endl;
                continue;
            }
            if( s.size() <= 1000 ) ans = solve1(s);
            else{
                int t = cal(n,(int)s.size());
                 ans = (qsm(26,n) - t +mod) %mod;
            }
            cout<<ans<<endl;
        }
    }
    AC; 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值