题目链接
题目解法
考虑把几个写法缝合
对于
S
u
b
t
a
s
k
1
,
2
Subtask\;1,2
Subtask1,2
因为
n
n
n 比较小,所以考虑指数级做法
可以发现如果每次多刷一个颜色的话转移较简单
所以列
d
p
dp
dp 方程为
d
p
[
i
]
[
S
]
dp[i][S]
dp[i][S] 表示刷了
i
i
i 个不同的颜色,每个颜色至少染了一个点,现在状态为
S
S
S 的点被染过了
转移就是按照
3
n
3^n
3n 的子集转移就可以了
需要预处理状态是否可以放在同一个颜色里
最后统计答案时只要枚举在
k
k
k 个颜色中选
i
i
i 个不同的颜色即可
时间复杂度
O
(
n
∗
3
n
)
O(n*3^n)
O(n∗3n)
struct SUBTASK12{
int inv[20],dp[20][1<<15],okS[1<<15];
bool link[20][20];
int C(int a,int b){
int res=1;
for(int i=0;i<b;i++) res=(LL)res*(a-i)%P;
return (LL)res*inv[b]%P;
}
void work(){
inv[0]=1;
for(int i=1;i<=n;i++) inv[i]=(LL)inv[i-1]*qmi(i,P-2)%P;
for(int i=1,x,y;i<=m;i++) x=read(),y=read(),x--,y--,link[x][y]=link[y][x]=1;
int full=(1<<n)-1;
for(int S=0;S<=full;S++)
for(int i=0;i<n;i++){
if(!(S>>i&1)) continue;
for(int j=0;j<n;j++){
if(i==j||!(S>>j&1)) continue;
if(link[i][j]) okS[S]=1;
}
}
dp[0][0]=1;
for(int i=0;i<n;i++)
for(int S=0;S<=full;S++){
if(!dp[i][S]) continue;
int buS=full^S;
for(int T=buS;T;T=(T-1)&buS) if(!okS[T]) (dp[i+1][S|T]+=dp[i][S])%=P;
}
while(t--){
int k=read(),ans=0;
for(int i=1;i<=n;i++) ans=(ans+(LL)dp[i][full]*C(k,i)%P)%P;
printf("%d\n",ans);
}
}
}sub12;
对于
S
u
b
t
a
s
k
3
Subtask\;3
Subtask3
m
m
m 的范围比较小,所以考虑对边容斥
即容斥一些边集必须不满足两头颜色相同
具体可以用并查集维护添加边集之后块的个数
struct SUBTASK3{
int a[25],b[25],fa[100100],cnt[100100];
int get_father(int x){
return x==fa[x]?x:get_father(fa[x]);
}
void dfs(int dep,int c,int op){
if(dep>m){ cnt[c]+=op;return;}
dfs(dep+1,c,op);
int p=get_father(a[dep]),q=get_father(b[dep]);
if(p==q) dfs(dep+1,c,-op);
else fa[p]=q,dfs(dep+1,c-1,-op),fa[p]=p;
}
void work(){
for(int i=1;i<=m;i++) a[i]=read(),b[i]=read();
for(int i=1;i<=n;i++) fa[i]=i;
dfs(1,n,1);
while(t--){
int k=read(),ans=0;
for(int i=1,j=1;i<=n;i++) j=(LL)j*k%P,ans=((ans+(LL)cnt[i]*j%P)%P+P)%P;
printf("%d\n",ans);
}
}
}sub3;
对于
S
u
b
t
a
s
k
4
Subtask\;4
Subtask4
可以发现,这个图一定是由若干个简单环组成的
考虑对每个环分开做,即询问有
n
n
n 个点的圆,相邻颜色不同的方案数
令此方案数为
f
n
f_n
fn
考虑分类
c
o
l
1
col_1
col1 和
c
o
l
3
col_3
col3
当
c
o
l
1
=
c
o
l
3
col_1=col_3
col1=col3 时,
c
o
l
2
col_2
col2 有
k
−
1
k-1
k−1 种颜色选择,
c
o
l
3
,
.
.
.
,
c
o
l
n
col_3,...,col_n
col3,...,coln 恰好构成一个
n
−
2
n-2
n−2 个点的子问题,这种情况的方案数为
(
k
−
1
)
f
n
−
2
(k-1)f_{n-2}
(k−1)fn−2
当
c
o
l
1
≠
c
o
l
3
col_1\neq col_3
col1=col3 时,
c
o
l
2
col_2
col2 有
k
−
2
k-2
k−2 种颜色选择,
c
o
l
1
,
c
o
l
3
,
.
.
.
c
o
l
n
col_1,col_3,...col_n
col1,col3,...coln 恰好构成
n
−
1
n-1
n−1 个点的子问题,这种情况的方案数为
(
k
−
2
)
f
n
−
1
(k-2)f_{n-1}
(k−2)fn−1
所以
f
n
=
(
k
−
1
)
f
n
−
2
+
(
k
−
2
)
f
n
−
1
f_n=(k-1)f_{n-2}+(k-2)f_{n-1}
fn=(k−1)fn−2+(k−2)fn−1
可以求出通项公式:
f
n
=
(
k
−
1
)
n
+
(
−
1
)
n
∗
(
k
−
1
)
f_n=(k-1)^n+(-1)^n*(k-1)
fn=(k−1)n+(−1)n∗(k−1)
考虑最多有
n
\sqrt n
n 个本质不同的环(即点数不同的环),所以每个询问只要求这
n
\sqrt n
n 个环的方案数就可以了
时间复杂度
O
(
k
n
l
o
g
n
)
O(k\sqrt nlogn)
O(knlogn)
struct SUBTASK4{
int fa[100100],cnt[100100],tot[100100],pos[100100];
int get_father(int x){
return x==fa[x]?x:fa[x]=get_father(fa[x]);
}
void work(){
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1,a,b;i<=m;i++) a=read(),b=read(),fa[get_father(a)]=get_father(b);
for(int i=1;i<=n;i++) cnt[get_father(i)]++;
for(int i=1;i<=n;i++) tot[cnt[i]]++;
int siz=0;
for(int i=1;i<=n;i++) if(tot[i]) pos[++siz]=i;
while(t--){
int k=read(),ans=1;
for(int i=1;i<=siz;i++)
ans=(LL)ans*qmi((qmi(k-1,pos[i])+(k-1)*(pos[i]&1?-1:1))%P,tot[pos[i]])%P;
printf("%d\n",(ans+P)%P);
}
}
}sub4;