ShinriiTin/notes/Counting_and_Expectation
计数与期望问题选讲
K Perm Counting
题目大意:
求有多少个\(1\)到\(n\)的排列\(a\),满足\(\forall 1\le i\le n\to|a_i-i|\not=k\).
\(2\le n\le2000,1\le k\le n-1\).
题解:
令\(f[i]\)表示选择\(i\)个数并决定它们的位置,使得这\(i\)个数都满足\(|a_i-i|=k\),即\(a_i\)可以是\(i-k\)或\(i+k\),的方案数。
则由容斥原理可得\(\displaystyle ans=\sum\limits_{i=0}^n f[i]\times(n-i)!\times(-1)^i\).
接下来介绍如何计算\(f[i]\).
将\(1\)到\(n\)按\(\mod{k}\)的余数分组,则不同的组之间相互独立。
对于同组的情况,我们考虑一个dp,令\(g[i][j][mask]\)表示只考虑前\(i\)个数,一共选了\(j\)个数,最右边的两个数是否占用了\(x+k\)位置的状态为\(mask\)的方案数。
则有递归基:
\[g[1][0][0]=1,g[1][1][1]=1\]
以及转移方程:
\[\displaystyle\begin{aligned}g[i][j][0]&=g[i-1][j][0]+g[i-1][j][2]+g[i-1][j-1][0]\\g[i][j][1]&=g[i-1][j-1][0]+g[i-1][j-1][2]\\g[i][j][2]&=g[i-1][j][1]+g[i-1][j][3]+g[i-1][j-1][1]\\g[i][j][3]&=g[i-1][j-1][1]+g[i-1][j-1][3]\end{aligned}\]
令\(getS(i,j)=g[i][j][0]+g[i][j][2]\),则可以做一个分组背包来计算\(f[i]\):
枚举\(\mod{k}\)的余数\(x\),然后枚举这一组选择的数量\(i\),则背包时应该用\(f[i]+=f[i-j]\times getS(siz[x],i)\)更新,其中\(siz[x]\)为该组的大小。
计算\(g\)部分的时间复杂度为\(O(n^2)\),空间复杂度为\(O(n^2)\);计算\(f\)部分的时间复杂度为\(O(n^2)\),空间复杂度为\(O(n)\);计算\(ans\)部分的时间复杂度为\(O(n)\),空间复杂度为\(O(n)\)。
因此,算法总的时间复杂度为\(O(n^2)\),空间复杂度\(O(n^2)\)。
#include <bits/stdc++.h>
const int max_N = 2000 + 21;
const int mod = 924844033;
int n,k,ans,siz[max_N],fac[max_N];
int f[max_N],g[max_N][max_N][4];
int getG(int i,int j){
return (g[i][j][0]+g[i][j][2])%mod;
}
int main(){
scanf("%d%d",&n,&k);
for(int i=0;i<n;++i)++siz[i%k];
g[1][0][0]=1;
g[1][1][1]=1;
for(int i=2;i<=n;++i)
for(int j=0;j<=i;++j){
g[i][j][0]=(g[i-1][j][0]+g[i-1][j][2])%mod;
g[i][j][2]=(g[i-1][j][1]+g[i-1][j][3])%mod;
if(!j)continue;
g[i][j][0]=(g[i][j][0]+g[i-1][j-1][0])%mod;
g[i][j][1]=(g[i-1][j-1][0]+g[i-1][j-1][2])%mod;
g[i][j][2]=(g[i][j][2]+g[i-1][j-1][1])%mod;
g[i][j][3]=(g[i-1][j-1][1]+g[i-1][j-1][3])%mod;
}
f[0]=1;
for(int x=0;x<k;++x)
for(int i=n;i;--i)
for(int j=1;j<=i&&j<=siz[x];++j){
f[i] = (f[i] + 1ll*f[i-j]*getG(siz[x],j)) % mod;
}
fac[0]=1;
for(int i=1;i<=n;++i)fac[i]=1ll*fac[i-1]*i%mod;
for(int i=0;i<=n;++i){
int tmp=1ll*f[i]*fac[n-i]%mod;
if(i&1)ans=(ans-tmp+mod)%mod;
else ans=(ans+tmp)%mod;
}
printf("%d\n",ans);
return 0;
}
Dictionary
题目大意:
给出\(1\le n\le50\)个只有小写英文字母和?组成的字符串\(s_1,\cdots,s_n\),\(1\le|s_i|\le20\).
求有多少种方案,将所有?换成小写英文字母,并且满足\(s_1<s_2<\cdots<s_n\).
题解:
首先通过给长度不够的字符串末尾添加同一个字典序小于’a’的字符的方式将所有串的长度统一,设这个长度为\(m\)。
这样,如果两个串\(s_i\)与\(s_j\)长度为\(k\)的后缀已经决定了大小关系,则它们除去该后缀之外的两个前缀应该完全相同。
这样我们可以考虑从后往前进行dp,从后往前为每个字符串填上小写字母。
令\(f[len][l][r]\)表示\(s_l,\cdots,s_r\)通过后缀\(s[len\cdots m]\)确定了\(s_l<s_{l+1}<\cdots<s_r\)的大小关系的方案数,则有递归基\(f[m+1][i][i]=1\).
这样我们就可以横向地进行dp,而纵向的部分需要另一个dp来辅助。
假设我们当前正在做\(f[len][l][r]\)的dp,令\(g[i][x]\)表示\(s_l,\cdots,s_i\)可以确定大小关系,且\(s_i\)当前位置填的字符为\(x\)的方案数。
则有递归基\(g[l-1][x]=1\),
转移方法为,枚举\(s_r\)当前位可以填的字符\(k\),再枚举\(i\)满足\(s_i,\cdots,s_r\)都可以填字符\(k\),则有\(\displaystyle g[r][k]=\sum_i\sum_{j<k}g[i-1][j]\times f[len+1][i][r]\)。
做完\(g\)之后,当前位置的\(f\)也得到了:\(f[len][l][r]=\sum_k g[r][k]\).
通过处理\(g\)的前缀和,算法总的时间复杂度为\(O(n^2m\sigma)\),空间复杂度为\(O(n^2m+n\sigma)\),\(\sigma\)为字符集大小。
#include <bits/stdc++.h>
const int max_N = 55;
const int mod = 1e9 + 7;
int n,m,a[max_N][max_N];
char s[max_N];
int f[max_N][max_N][max_N];
int g[max_N][29];
int main(){
scanf("%d", &n);
for(int i=1;i<=n;++i){
scanf("%s", s + 1);
int len = strlen(s + 1);
for(int j=1;j<=len;++j)
if(s[j] == '?')
a[i][j] = -1;
else
a[i][j] = s[j] - 'a' + 1;
m = std::max(m, len);
}
for(int i=1;i<=n;++i)f[m+1][i][i]=1;
for(int len=m;len;--len)
for(int l=1;l<=n;++l){
memset(g,0,sizeof(g));
for(int i=0;i<=27;++i)g[l-1][i]=1;
for(int r=l;r<=n;++r){
int lo,hi;
if(~a[r][len])lo=hi=a[r][len]+1;
else lo=2,hi=27;
for(int k=lo;k<=hi;++k)
for(int i=r;i>=l;--i){
if(~a[i][len]&&a[i][len]!=k-1)break;
g[r][k]=(g[r][k]+1ll*g[i-1][k-1]*f[len+1][i][r])%mod;
}
for(int i=1;i<=27;++i)g[r][i]=(g[r][i]+g[r][i-1])%mod;
f[len][l][r]=g[r][27];
}
}
printf("%d\n", f[1][1][n]);
return 0;
}
Leftmost Ball
题目大意:
有\(n\le2000\)种颜色的球,每种颜色都有\(k\le2000\)个,现在将它们放在一行任意排列,然后将每种颜色最左边的球染色成\(0\)号颜色(与\(n\)种颜色都不同),问最终有多少种不同的局面。
题解:
考虑如何一个最终局面是否合法。最终局面合法,当且仅当,将出现的颜色按除了\(0\)号颜色之外的每种颜色最左边的球的顺序重新编号,第\(i\)种颜色的球左边至少有\(i\)个白球。
因此可以用\(f[i][j]\)表示仅考虑有\(i\)个\(0\)号球,\(j\)种不同颜色的球的不同局面数量,则答案为\(f[n][n]\times n!\)。
显然\(f[0][0]=1\),\(f[i][j]\)则需要考虑当前最左边的球放\(0\)号球还是放\(j\)种颜色中编号最小的颜色的球。
如果放\(0\)号球(要求\(i>0\)),则有\(f[i-1][j]\)种方案;
如果不放\(0\)号球(要求\(j>i\)),则需要先将该种颜色的其余\(k-2\)个球的位置决定好,然后转化为子问题\(f[i][j-1]\),共\(\displaystyle\binom{i+j(k-1)-1}{k-2}\times f[i][j-1]\)种方案;
时空复杂度\(O(n(n+k))\),注意要特判\(k=1\),此时显然只有一种方案。
#include <bits/stdc++.h>
const int max_N = 2e3 + 21;
const int mod = 1e9 + 7;
int n,k,f[max_N][max_N];
int fac[max_N*max_N],_fac[max_N*max_N],inv[max_N*max_N];
inline void init(int n){
inv[1]=1;
for(int i=2;i<=n;++i){
inv[i]=1ll*(mod/i)*inv[mod%i]%mod;
if(inv[i])inv[i]=mod-inv[i];
}
fac[0]=_fac[0]=1;
for(int i=1;i<=n;++i){
fac[i]=1ll*fac[i-1]*i%mod;
_fac[i]=1ll*_fac[i-1]*inv[i]%mod;
}
}
inline int C(int n,int m){
return 1ll*fac[n]*_fac[m]%mod*_fac[n-m]%mod;
}
int calc(int i,int j){
if(~f[i][j])return f[i][j];
int&res=f[i][j];
res=0;
if(i)res=calc(i-1,j);
if(j>i)
res=(res+1ll*C(i+j*(k-1)-1,k-2)*calc(i,j-1))%mod;
return res;
}
int main(){
scanf("%d%d",&n,&k);
if(k==1)return puts("1"),0;
init(n*k);
for(int i=0;i<=n;++i)
for(int j=0;j<=n;++j)
f[i][j]=-1;
f[0][0]=1;
int ans=1ll*calc(n,n)*fac[n]%mod;
printf("%d\n",ans);
return 0;
}
BBQ Hard
题目大意:
有\(n\le2\times10^5\)包物品,第\(i\)包物品中有\(A\)类物品\(1\le A_i\le2000\)个,\(B\)类物品\(1\le B_i\le2000\)个,同类的物品个体之间不存在差异。
现在要从中取出两包物品,将其中的物品都取出,再将其中的物品按某种顺序排列,问有多少种不同的方案。
两种方案不同当前仅当选择的两包物品不同,或者其中物品的排列顺序不同。
题解:
如果确定选择第\(i\)包和第\(j\)包物品,则不同的方案共有\(\displaystyle \frac{(A_i+A_j+B_i+B_j)!}{(A_i+A_j)!(B_i+B_j)!}\).
这个几何意义可以是从只能向上和向右走的网格中从\((-A_i,-B_i)\)走到\((A_j,B_j)\)的方案数。
因此可以用dp
求出从\((-A_1,-B_1),\cdots,(-A_n,-B_n)\)中任意位置出发走到\((A_i,B_i)\)的方案数求和,再减去从\((-A_i,-B_i)\)走到\((A_i,B_i)\)的方案数之和,最后再除以\(2\)消序即可。
时空复杂度\(O(n+m^2)\),其中\(m\)为坐标范围。
#include <bits/stdc++.h>
const int max_N = 2e5 + 21;
const int max_M = 2e3 + 21;
const int mod = 1e9 + 7;
int n,A[max_N],B[max_N],dp[max_M<<1][max_M<<1],ans;
int fac[max_M<<2],_fac[max_M<<1],inv[max_M<<1];
int main(){
inv[1]=1;
for(int i=2;i<=4000;++i){$O(n\log^2{n}$
inv[i]=1ll*(mod/i)*inv[mod%i]%mod;
if(inv[i])inv[i]=mod-inv[i];
assert(1ll*i*inv[i]%mod==1);
}
fac[0]=_fac[0]=1;
for(int i=1;i<=8000;++i){
fac[i]=1ll*fac[i-1]*i%mod;
}
for(int i=1;i<=4000;++i){
_fac[i]=1ll*_fac[i-1]*inv[i]%mod;
}
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d%d",A+i,B+i);
++dp[2000-A[i]][2000-B[i]];
}
for(int i=0;i<=4000;++i)
for(int j=0;j<=4000;++j){
dp[i+1][j]=(dp[i+1][j]+dp[i][j])%mod;
dp[i][j+1]=(dp[i][j+1]+dp[i][j])%mod;
}
for(int i=1;i<=n;++i){
ans=(ans+dp[2000+A[i]][2000+B[i]])%mod;
int tmp=1ll*fac[A[i]*2+B[i]*2]*_fac[A[i]*2]%mod*_fac[B[i]*2]%mod;
ans=(ans-tmp+mod)%mod;
}
ans=1ll*ans*inv[2]%mod;
printf("%d\n",ans);
return 0;
}
其他题暂时都找不到提交的地方,后面找到了或者和队友讨论过了再放
Dancing IOI Team
占个坑,感觉会做了,写完和暴力拍一拍,没问题放上来。
题目大意:
给定两个长度为\(n\)的数列\(\{a_n\}\)和\(\{b_n\}\),以及一个正整数\(k\)。
其中\(1\le k\le n\le100\),\(a_1\ge a_2\ge\cdots\ge a_n\),\(0\le a_i,b_i\le1000\),\(a_i\)两两不同,\(b_i\)两两不同。
问有多少\(1\)到\(n\)的排列\(p\),满足\(\displaystyle\min\limits_{i\le k}a_i+b_{p_i}\ge\max\limits_{j>k}a_j+b_{p_j}\),对\(10^9+7\)取模。
题解:
令\(f(t)\)表示满足下面三个条件的排列数:
\(\forall i\le k\to a_i+b_{p_i}\ge t\)
\(\forall j>k\to a_j+b_{p_j}\le t\)
\(\exists i\le k\land a_i+b_{p_i}=t\)
则\(ans=\sum\limits_t f(t)\).
对于上述的第三个条件,我们可以简单地用一个容斥将其去掉。
令\(g(A,B)\)表示满足下面两个条件的排列数:
\(\forall i\le k\to a_i+b_{p_i}\ge A\)
\(\forall j>k\to a_j+b_{p_j}\le B\)
则\(f(t)=g(t,t)-g(t+1,t)\).
接下来考虑如何求\(g(A,B)\)的值。
这其实是一个二分图完备匹配计数问题:
\(a,b\)分别是二分图的左右部,\(i\le k\)的\(a_i\)可以与不小于\(A-a_i\)的\(b\)匹配,\(i>k\)的\(a_i\)可以与不大于\(B-a_i\)的\(b\)匹配。
因此我们将\(i\le k\)的\(A-a_i\),\(i>k\)的\(B-a_i\),以及\(b_j\),放在一起按大小排序(如果值相同,认为\(A-a_i\)最小,\(B-a_i\)最大)。
对于排序后的数组,我们从小到大扫描一次,同时用dp计算方案数。
令\(dp[i][j]\)表示扫描到了第\(i\)个位置,与\(i\le k\)的\(a_i\)匹配的\(b\)的数量为\(j\)的方案数。
显然递归基为\(f[0][0]=1\)。
转移方式视当前位置的元素类型而定,记\(A-a_i\)类型为\(0\),\(b\)类型为\(1\),\(B-a_i\)类型为\(2\):
如果当前元素为类型\(0\),则只需简单的拷贝上一个位置处的dp值
如果当前元素为类型\(1\),那么可能有两种转移:
- 第一种,当前元素不与类型\(0\)匹配,将其加入未分配组中,这种转移对\(j\)没有要求,
dp[i][j]=dp[i-1][j]
;
- 第一种,当前元素不与类型\(0\)匹配,将其加入未分配组中,这种转移对\(j\)没有要求,
- 第二种,当前元素与类型\(0\)匹配,要求\(j\)不为零且不大于之前扫描到的类型\(0\)数量\(cnt_0\),
dp[i][j]+=dp[i-1][j-1]*(cnt_0-j+1)
;
- 第二种,当前元素与类型\(0\)匹配,要求\(j\)不为零且不大于之前扫描到的类型\(0\)数量\(cnt_0\),
如果当前元素为类型\(2\),那么分两种情况(令\(cnt_1\)为之前扫描到的类型\(1\)的数量,\(cnt_2\)为之前扫描到的类型\(2\)的数量,则未分配的类型\(1\)有\(cnt_1-j\)个):
- 第一种情况,若\(cnt_1-j<cnt_2\),则当前的类型\(2\)多于未分配的类型\(1\),无法达到完备匹配,
dp[i][j]=0
;
- 第一种情况,若\(cnt_1-j<cnt_2\),则当前的类型\(2\)多于未分配的类型\(1\),无法达到完备匹配,
- 否则,第二种情况,
dp[i][j]=dp[i][j]*(cnt_1-cnt_2-j+1)
;
- 否则,第二种情况,
转移完毕之后,\(g(A,B)=dp[n<<1][k]\)。
计算一次\(g\),时间复杂度为\(O(n^2)\),空间复杂度为\(O(n^2)\)。
若\(t\)最多有\(m\)种取值,则总的时间复杂度为\(O(n^2m)\),空间复杂度为\(O(n^2)\)。
本题中\(m\le2001\)。
#include <bits/stdc++.h>
const int max_N = 100 + 21;
const int mod = 1e9 + 7;
int n,k,a[max_N],b[max_N],min_a,max_a,min_b,max_b;
std::pair<int,int>x[max_N<<1];
//second: 0 -> A - ai, 1 -> bi, 2 -> B - ai
int dp[max_N<<1][max_N];
int g(int A,int B){
for(int i=1;i<=k;++i)x[i]={A-a[i],0};
for(int i=k+1;i<=n;++i)x[i]={B-a[i],2};
for(int i=1;i<=n;++i)x[i+n]={b[i],1};
std::sort(x+1,x+1+2*n);
int cnt[3]={0,0,0};
memset(dp,0,sizeof(dp));
dp[0][0]=1;
for(int i=1;i<=(n<<1);++i){
++cnt[x[i].second];
for(int j=0;j<=cnt[0]&&j<=cnt[1];++j){
if(x[i].second==2){
if(cnt[1]-j>=cnt[2])
dp[i][j]=1ll*dp[i-1][j]*(cnt[1]-cnt[2]-j+1)%mod;
continue;
}
dp[i][j]=dp[i-1][j];
if(x[i].second==1&&j){
dp[i][j]=(dp[i][j]+1ll*dp[i-1][j-1]*(cnt[0]-j+1))%mod;
}
}
}
return dp[n<<1][k];
}
int f(int t){
return (g(t,t)-g(t+1,t)+mod)%mod;
}
int T[max_N],tot;
int main(){
max_a=max_b=0,min_a=min_b=1000;
scanf("%d%d", &n, &k);
for(int i=1;i<=n;++i)scanf("%d", a + i);
for(int i=1;i<=n;++i)scanf("%d", b + i);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
T[++tot] = a[i] + b[j];
std::sort(T+1,T+1+tot);
tot=std::unique(T+1,T+1+tot)-T-1;
int ans = 0;
for(int i=1;i<=tot;++i)ans=(ans+f(T[i]))%mod;
printf("%d\n", ans);
return 0;
}
Izhevsk Training Camp (Simplified)
占个坑,感觉会做了,写完和暴力拍一拍,没问题放上来。
题目大意:
给出三个\(1\)到\(n\)的排列\(a,b,c\),问有多少二元组\((x,y)\)满足\(a_x<a_y,b_x<b_y,c_x<c_y\)。
\(2\le n\le5\times10^6\).
题解:
令满足\(a_x<a_y,b_x<b_y,c_x<c_y\)的二元组数量为\(A\),满足\(a_x<a_y,b_x<b_y,c_x>c_y\)或\(a_x<a_y,b_x>b_y,c_x<c_y\)或\(a_x>a_y,b_x<b_y,c_x>c_y\)的二元组数量为\(B\),则\(A+B=\binom{n}{2}\).
令满足\(a_x<a_y,b_x<b_y\)的二元组数量为\(u\),满足\(b_x<b_y,c_x<c_y\)的二元组数量为\(v\),满足\(a_x<a_y,c_x<c_y\)的二元组数量为\(w\),则\(3A+B=u+v+w\).
因此我们可以做三次二维偏序,然后求得\(\displaystyle A=\frac{u+v+w-\binom{n}{2}}{2}\),时间复杂度\(O(n\log{n})\),空间复杂度\(O(n)\)。
#include <bits/stdc++.h>
template<class T>inline void read(T&x){
char c; while(!isdigit(c=getchar()));
for(x=c-48;isdigit(c=getchar());x=x*10+c-48);
}
const int max_N = 5e6 + 21;
using ll = long long;
int n, a[max_N], b[max_N], c[max_N];
int bit[3][max_N], q[max_N];
int main(){
read(n);
for(int i=1;i<=n;++i) read(a[i]);
for(int i=1;i<=n;++i) read(b[i]);
for(int i=1;i<=n;++i) read(c[i]);
ll ans = 0;
for(int i=1;i<=n;++i) q[i] = i;
std::sort(q+1,q+1+n,[&](int x,int y){return a[x]<a[y];});
for(int i=1;i<=n;++i){
int x = q[i];
for(int j=b[x];j;j-=j&-j) ans += bit[1][j];
for(int j=c[x];j;j-=j&-j) ans += bit[2][j];
for(int j=b[x];j<=n;j+=j&-j) ++ bit[1][j];
for(int j=c[x];j<=n;j+=j&-j) ++ bit[2][j];
}
std::sort(q+1,q+1+n,[&](int x,int y){return b[x]<b[y];});
for(int i=1;i<=n;++i){
int x = q[i];
for(int j=c[x];j;j-=j&-j) ans += bit[0][j];
for(int j=c[x];j<=n;j+=j&-j) ++ bit[0][j];
}
ans = (ans - 1ll*n*(n-1)/2) >> 1;
printf("%lld\n", ans);
return 0;
}
Mixed Drinks
由于某些原因暂时不公开题解