传送门
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)n−iC(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−(k−1)i)n∗(k−1)n2−ni,上面说至多i行存在1,即剩下n-i行一定没有1,方案数为 ( k − 1 ) n 2 − n i (k-1)^{n^2-ni} (k−1)n2−ni,选定的i行可能存在1,方案数为 ( k i − ( k − 1 ) i ) n (k^i-(k-1)^i)^n (ki−(k−1)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)=(k−1)ni∗(k−1)j(n−i)∗kn2−ni−j(n−i)
因为求的是不合法情况,即行和列都要求反面,需要容斥两次。
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)∗2n−j=∑j=0kS2(k,j)(n−j)!n!∗2n−j
然后就可以直接做了,阶乘部分可以边做边推。
复杂度 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)=26n−∑i=0m−1C(n,i)25n−i,考虑总方案减去不合法的方案。时间复杂度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=0m−1C(n,i)25n−i=∑i=0m−1(C(n−1,i−1)+C(n−1,i))25n−i=25n∑i=0m−1C(n−1,i−1)25−i+25n∑i=0m−1C(n−1,i)25−i=25n−1∑i=0m−2C(n−1,i)25−i+25n∑i=0m−1C(n−1,i)25−i=26S(n−1)−C(n−1,m−1)25n−m
化成这样就可以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;
}