T1 游戏设计
link
题意
你要设计所有长度为
K
K
K 的字符串,每个字符是 A,B,C,D
中选择一个,显然共有
4
K
4^K
4K 个不同的字符串,你要给每个字符串都分配一种颜色,不同的字符串可以分配相同的颜色,颜色分配过程是由你来决定的。
首先
B
e
s
s
i
e
Bessie
Bessie 会输入一个长度是
K
K
K 的字符串
S
S
S(
S
S
S 是上面
4
k
4^k
4k 个字符串中的其中一个),然后
B
e
s
s
i
e
Bessie
Bessie 每一步操作是如下两种选择之一:
- 交换当前字符串 S S S 的相邻的两个字符。(第一个字符和最后一个字符不相邻)
- 如果当前字符串 S S S 含有子串 b [ i ] b[i] b[i],那么可以把该子串替换成字符串 c [ i ] c[i] c[i] 。其中 b b b 数组和 c c c 数组都是字符串数组。(输入数据给出)
如果 B e s s i e Bessie Bessie 操作若干次后,使字符串 S S S 能变成字符串 T T T( T ≠ S T \not= S T=S),且 T T T 和 S S S 颜色相同,那么 B e s s i e Bessie Bessie 胜利。你希望无论 B e s s i e Bessie Bessie 输入的字符串 S S S 是什么,都不可能胜利,那么至少需要多少种不同的颜色才能实现。
多测, 1 ≤ g r o u p ≤ 10 1 \le group \le 10 1≤group≤10 , 1 ≤ K ≤ 30 1 \le K \le 30 1≤K≤30 , 1 ≤ N ≤ 50 1 \le N \le 50 1≤N≤50( N N N 表示数组 c c c、 d d d 的长度, c c c、 d d d 长度相等)
思路
考虑将可以互相转换的字符串连边,那么答案就是将整个图缩点后得到的
D
A
G
DAG
DAG 上的最长链。但是图中的点数达
4
K
4^K
4K ,显然不行。
但是有一个明显的结论,如果两个字符串
4
4
4 种字母出现的次数都是一样的,显然可以通过不停交换相邻字符来变得相等。
所以我们可以用一个四元组
(
a
,
b
,
c
,
d
)
(a,b,c,d)
(a,b,c,d) 为点表示 所有由
a
a
a 个A
,
b
b
b 个B
,
c
c
c 个C
,
d
d
d 个D
组成的字符串,点权为
K
!
a
!
b
!
c
!
d
!
\frac{K!}{a!b!c!d!}
a!b!c!d!K!。最多只有
(
30
+
4
−
1
4
−
1
)
=
5456
\binom{30+4-1}{4-1} = 5456
(4−130+4−1)=5456 个点。
将可以转化的四元组互相连边,缩点
+
+
+
D
A
G
DAG
DAG 上最长链。
时间复杂度大概为
O
(
K
3
)
O(K^3)
O(K3) 。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=5460;
ll Get_val(int sa,int sb,int sc,int sd,int n){
__int128 s=1;
for(int i=1;i<=n;i++) s*=i;
for(int i=1;i<=sa;i++) s/=i;
for(int i=1;i<=sb;i++) s/=i;
for(int i=1;i<=sc;i++) s/=i;
for(int i=1;i<=sd;i++) s/=i;
return s;
}
int idx,head[maxn];
struct EDGE{ int v,next; }e[maxn*50];
void Insert(int u,int v){
e[++idx]={v,head[u]};
head[u]=idx;
}
int dfn[maxn],low[maxn],tim,scc_cnt,scc[maxn];
ll sccs[maxn];//!!!longlong
stack<int>stk;
struct NODE{ int sa,sb,sc,sd; ll val; }pl[maxn];
void Tarjan(int u){
dfn[u]=low[u]=++tim;
stk.push(u);
for(int i=head[u];i;i=e[i].next){
int v=e[i].v;
if(!dfn[v]){
Tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(!scc[v]) low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
scc_cnt++;
int p=0;
do{
p=stk.top();
stk.pop();
scc[p]=scc_cnt;
sccs[scc_cnt]+=pl[p].val;
}while(p!=u);
}
}
int du[maxn];
ll f[maxn];
queue<int>q;
void Topo(){
while(!q.empty()) q.pop();
for(int i=1;i<=scc_cnt;i++){
f[i]=sccs[i];
if(!du[i]) q.push(i);
}
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].next){
int v=e[i].v;
du[v]--;
f[v]=max(f[v],f[u]+sccs[v]);
if(!du[v]) q.push(v);
}
}
}
int vl[31][31][31][31];
struct NODE2{ int b[5],c[5]; }a[55];
struct EDGE2{ int x,y; }e2[maxn*50];
string st;
int main(){
int group; cin>>group;
while(group--){
int K,n; cin>>K>>n;
for(int i=1;i<=n;i++){
cin>>st;
for(int j=0;j<st.size();j++)
a[i].b[st[j]-'A']++;
}
for(int i=1;i<=n;i++){
cin>>st;
for(int j=0;j<st.size();j++)
a[i].c[st[j]-'A']++;
}
int tot=0;
for(int i=0;i<=K;i++)
for(int j=0;j<=K-i;j++)
for(int k=0;k<=K-i-j;k++){
int q=K-i-j-k;
pl[++tot]={i,j,k,q,Get_val(i,j,k,q,K)};
vl[i][j][k][q]=tot;
}
int cnt=0;
for(int i=1;i<=tot;i++)
for(int j=1;j<=n;j++){
int na=pl[i].sa-a[j].b[0],nb=pl[i].sb-a[j].b[1],nc=pl[i].sc-a[j].b[2],nd=pl[i].sd-a[j].b[3];
if(na>=0&&nb>=0&&nc>=0&&nd>=0){
na+=a[j].c[0],nb+=a[j].c[1],nc+=a[j].c[2],nd+=a[j].c[3];
if(vl[na][nb][nc][nd]!=i){
Insert(i,vl[na][nb][nc][nd]);
e2[++cnt]={i,vl[na][nb][nc][nd]};
}
}
}
while(!stk.empty()) stk.pop();
for(int i=1;i<=tot;i++)
if(!dfn[i]) Tarjan(i);
idx=0;
for(int i=1;i<=tot;i++)
head[i]=0;
for(int i=1;i<=cnt;i++){
int x=e2[i].x,y=e2[i].y;
if(scc[x]!=scc[y]){
Insert(scc[x],scc[y]);
du[scc[y]]++;
}
}
Topo();
ll ans=0;
for(int i=1;i<=scc_cnt;i++)
ans=max(ans,f[i]);
cout<<ans<<"\n";
for(int i=1;i<=n;i++)
a[i].b[0]=a[i].b[1]=a[i].b[2]=a[i].b[3]=a[i].c[0]=a[i].c[1]=a[i].c[2]=a[i].c[3]=0;
idx=0;
for(int i=1;i<=tot;i++)
dfn[i]=low[i]=head[i]=scc[i]=0;
for(int i=1;i<=scc_cnt;i++)
f[i]=du[i]=sccs[i]=0;
scc_cnt=tim=0;
}
return 0;
}
T2 锦标赛([ARC093F] Dark Horse)
link
题意
有
2
N
2^N
2N 个人,按照满二叉树的形态进行淘汰赛,一开始的排列顺序为所有
(
2
N
)
!
(2^N)!
(2N)! 个排列之一。你是第
1
1
1 个人,已知每一对人之间的实力关系,具体地说:
- 给出 M M M 个人 A 1 ∼ A M A_1 \sim A_M A1∼AM,这 M M M 个人都打得过你。
- 你打得过除了这 M M M 个人之外的所有其他人。
- 对于剩下的情况(你不参与的情况),编号小的人胜利。
问你在所有的 ( 2 N ) ! (2^N)! (2N)! 种情况中,有多少种情况可以取得最终胜利,答案对 10 9 + 7 {10}^9 + 7 109+7 取模。
1 ≤ N ≤ 16 1 \le N \le 16 1≤N≤16, 0 ≤ M ≤ 16 0 \le M \le 16 0≤M≤16, 2 ≤ A i ≤ 2 N 2 \le A_i \le 2^N 2≤Ai≤2N, A i < A i + 1 A_i < A_{i + 1} Ai<Ai+1
思路
首先,知道我的位置不重要,那么就钦定我在1号位置,最后答案乘
2
N
2^N
2N 即可。
我们可以让比赛顺序变成如下图所示:
即,我与区间
[
2
,
2
]
,
[
3
,
4
]
,
[
5
,
8
]
,
.
.
.
.
.
.
,
[
2
n
−
1
+
1
,
2
n
]
[2,2] , [3,4] , [5,8] , ...... , [2^{n-1}+1,2^n]
[2,2],[3,4],[5,8],......,[2n−1+1,2n] 比赛,都要获胜。那么每个区间中的最小值都不能为
A
A
A 中的数。
把题意简化为:将
[
2
,
2
N
]
[2,2^N]
[2,2N] 中的元素分成
N
N
N 个大小为
2
i
2^i
2i (
i
∈
[
0
,
n
−
1
]
i \in [0,n-1]
i∈[0,n−1]) 的集合,并保证每个集合中的最小值都不在
A
A
A 中的方案数。
这样有点复杂,考虑容斥,记
F
(
s
)
F(s)
F(s) 表示
s
s
s 二进制下为
1
1
1 的位置所对应的组里面最小值为
A
A
A 中的元素时的方案数。(设第 G 组:
[
2
G
−
1
,
2
G
+
1
]
[2^G-1,2^{G+1}]
[2G−1,2G+1])那么答案为:
a
n
s
=
2
n
×
(
∑
s
(
−
1
)
p
o
p
c
o
u
n
t
(
s
)
F
(
s
)
)
ans=2^n \times (\sum_{s} (-1)^{popcount(s)}F(s))
ans=2n×(s∑(−1)popcount(s)F(s))
考虑如何求
F
(
s
)
F(s)
F(s) 。(不合法的集合指的是集合里面的最小值是
A
A
A 中的元素)
当我们枚举
A
A
A 中的数并用它来使集合不合法的时候,集合剩下的 个数都要大于这个数,这会有后效性,那么将
A
A
A 中的数从大到小
排序,这样前面考虑过的数一定大于后面被考虑的数,就可以避免后效性。
设
f
i
,
j
f_{i,j}
fi,j 表示考虑到第
i
i
i 个被标记的数,
j
j
j 二进制下为
1
1
1 的集合已经不合法,考虑转移:(假定当前考虑到
A
A
A 中的第
i
i
i 个数
A
i
A_i
Ai )
- 不用 A i A_i Ai 去不合法化任何集合,即 f i , j = f i − 1 , j f_{i,j} = f_{i-1,j} fi,j=fi−1,j 。
- 用 A i A_i Ai 去不合法化大小为 2 k 2^k 2k 的集合,此时需要另选 2 k − 1 2^k −1 2k−1 个数,可供选择的数有 2 N − j − A i 2^N - j - A_i 2N−j−Ai 个 (因为选择的数要大于 A i A_i Ai ,如认为不合法的集合已经填满,则有 j j j 个数也已经用过,所以也不能选这 j j j 个数),并且集合中的数可以随意排列,转移为 f i , j ∣ 2 k = f i , j × ( 2 N − j − A i 2 k − 1 ) × ( 2 k ) ! f_{i,j|2^k} = f_{i,j} \times \binom{2^N-j-A_i}{2^k-1} \times (2^k)! fi,j∣2k=fi,j×(2k−12N−j−Ai)×(2k)!
所以, F ( s ) = f M , s × ( N − 1 − s ) ! F(s) = f_{M,s} \times (N-1-s)! F(s)=fM,s×(N−1−s)! 。(若到最后还有空的集合,就直接将剩下的数乱填即可)
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxm=20,maxn=65540,mod=1e9+7;
ll Pow_(ll x,ll y){
ll s=1;
while(y){
if(y&1) (s*=x)%=mod;
(x*=x)%=mod;
y>>=1;
}
return s;
}
ll fac[maxn],inv_fac[maxn];
ll C(ll m,ll n){
if(m<0||n<0||m<n) return 0ll;//!!!
return fac[m]*inv_fac[n]%mod*inv_fac[m-n]%mod;
}
int Cnt_binary(int x){
int s=0;
while(x){
if(x&1) s++;
x>>=1;
}
return s;
}
int a[maxm];
ll f[maxm][maxn];
int main(){
int n,m; cin>>n>>m; int N=(1<<n);
for(int i=1;i<=m;i++)
cin>>a[i];
sort(a+1,a+m+1,[](int a,int b){ return a>b; });
fac[0]=inv_fac[0]=1;
for(int i=1;i<=N;i++){
fac[i]=fac[i-1]*i%mod;
inv_fac[i]=Pow_(fac[i],mod-2);
}
f[0][0]=1;//f[i][j]:考虑到第 i 个我打不过的人,j 的二进制位为1的集合已经不合法的方案数
for(int i=1;i<=m;i++)
for(int j=0;j<N;j++){
(f[i][j]+=f[i-1][j])%=mod;
for(int k=0;k<n;k++)
if(!((1<<k)&j)) (f[i][j|(1<<k)]+=f[i-1][j]*C(N-j-a[i],(1<<k)-1)%mod*fac[1<<k]%mod)%=mod;
}
ll ans=0;
for(int i=0;i<N;i++){
if(Cnt_binary(i)&1) ans-=f[m][i]*fac[N-1-i]%mod,(ans+=mod)%=mod;
else (ans+=f[m][i]*fac[N-1-i]%mod)%=mod;
}
cout<<ans*Pow_(2,n)%mod;
return 0;
}
总结
不想总结~~