ABC321 题解(C,F,G)
C 321-like Searcher
题意:一个数字串,如果从高位到低位严格递减则被称为“321串”(特例:不包括0和1),问第k大的“321串”是什么。
解答:只要从0-9中选定了不同的k个数,由于要满足严格递减,那么相应的“321串”也就唯一确定。所以“321串”总个数为 2 10 − 2 = 1022 2^{10}-2=1022 210−2=1022个。枚举不同选择方法即可。
F #(subset sum = K) with Add and Erase
题意:给定一个集合,往集合中不断增删元素,问每次操作后和为K的子集的数量。
解答:如果只有增加元素,这是一道简单的DP。令 d p i dp_i dpi表示和为 i i i的子集数量,每次增加元素 k k k,则转移方程为 d p i = d p i + d p i − k dp_i=dp_i+dp_{i-k} dpi=dpi+dpi−k,每次倒序遍历。删除元素就是增加元素的逆,转移方程 d p i = d p i − d p i − k dp_i=dp_i-dp_{i-k} dpi=dpi−dpi−k,注意每次正序遍历,为什么是这样?
我们可以画出状态空间转移的图示:
如图所示,一个元素回溯转移的过程会发现它实际上是三角元素和方块元素的和,由于我们想要消去+2的操作,方块元素是我们不希望转移到它身上的,我们想要减去方块元素,而方块元素会转移到 d p i − 2 dp_{i-2} dpi−2上,所以我们只要让 d p i = d p i − d p i − k dp_i=dp_i-dp_{i-k} dpi=dpi−dpi−k就可以了,注意一定要正序遍历 i i i。
G Electric Circuit
题意:N个元件,每个元件上面都有一些A口和B口,保证所有元件总共的A口和B口数目相同,均为M,现在随意用M根电线连接任意A口和B口(同一个元件上A口和B口也可以相连),问连完后形成的连通分量个数的数学期望。
解答:对N个元件的子集 S ⊆ N S\subseteq N S⊆N,定义 g ( S ) g(S) g(S)是让S内元件自成一个连通分量的连接方案数。
易知 g ( S ) = 0 g(S)=0 g(S)=0若S为空集或S内元件A口数和B口数不相同(这样的话S内元件一定得连向外部,不能自成一个连通分量),否则设S内A口数量为K, g ( S ) = k ! − ∏ { s 1 , s 2 , . . . , s t } g ( s i ) ( t > 1 ) g(S)=k!-\prod_{\{s_1,s_2,...,s_t\}}g(s_i)(t>1) g(S)=k!−∏{s1,s2,...,st}g(si)(t>1)其中 { s 1 , s 2 , . . . , s t } \{s_1,s_2,...,s_t\} {s1,s2,...,st}是S的一个划分,如何快速的算出 g ( S ) g(S) g(S)?我们可以通过固定S中一个元素,枚举它所在的连通分量算出来。用数位DP,从1遍历到 2 N 2^N 2N,我们可以依次算出 g ( S ) g(S) g(S),每算出一个 g ( S ) g(S) g(S),可以看出它对所有方案的总连通分量个数的贡献为 g ( S ) ∗ ( M − k ) ! g(S)*(M-k)! g(S)∗(M−k)!,这样当然会有重复计数,但是n个连通分量的连接方案就会计数n次,正是我们想要的。
最后将所有方案总连通分量数除以 M ! M! M!得到答案
代码(模版省略):
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
int main(){
int n,m;
cin>>n>>m;
vector<int> red(n,0),blue(n,0);
vector<int> rcnt(1<<n,0),bcnt(1<<n,0);
for(int i=0;i<m;i++){
int r;
cin>>r;
r--;
red[r]++;
}
for(int i=0;i<m;i++){
int b;
cin>>b;
b--;
blue[b]++;
}
for(int i=0;i<(1<<n);i++){
for(int j=0;j<n;j++){
if(i>>j&1){
rcnt[i]+=red[j];
bcnt[i]+=blue[j]; //记录每一个元件子集S有多少A口和B口
}
}
}
vector<Z> fac(m+1,0);
fac[0]=1;
for(int i=1;i<=m;i++){
fac[i]=fac[i-1]*i;
}
vector<Z> dp(1<<n,0),f(1<<n,0),g(1<<n,0);
Z ans=0;
for(int bit=1;bit<(1<<n);bit++){
if(rcnt[bit]!=bcnt[bit]) continue;
f[bit]=g[bit]=fac[rcnt[bit]];
for(int sub=(bit-1)&bit;sub>bit-sub;sub=(sub-1)&bit){ //枚举最高位在哪个连通分量中
g[bit]-=g[sub]*f[bit-sub];
}
ans+=g[bit]*fac[m-rcnt[bit]];
}
ans/=fac[m];
cout<<ans<<'\n';
return 0;
}
时间复杂度的分析有一点困难,易知是最后的二层for循环贡献了主要的复杂度,而最内层代码执行次数为 ( n 1 ) 2 0 + ( n 2 ) 2 1 + . . . + ( n n ) 2 n − 1 = ( 1 + 2 ) n 2 = 3 n 2 \binom{n}{1}2^0+\binom{n}{2}2^1+...+\binom{n}{n}2^{n-1}=\frac{(1+2)^n}{2}=\frac{3^n}{2} (1n)20+(2n)21+...+(nn)2n−1=2(1+2)n=23n所以复杂度为 O ( 3 N ) O(3^N) O(3N),考虑到 N ≤ 17 N\leq 17 N≤17,这样的复杂度是可以接受的。