AtCoder Beginner Contest 226(EF补题)
Posted 佐鼬Jun
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AtCoder Beginner Contest 226(EF补题)相关的知识,希望对你有一定的参考价值。
E - Just one
链接: link.
题意:
N N N个点 M M M条无向边,现在给每个边定一个方向,总计 2 M 2^M 2M种方案,但现在规定每个点的出度为 1 1 1,且仅为 1 1 1(有且仅有一条出边),现在问有多少种方案?
思路:
要想将无向图定向,是每个点的出度为
1
1
1,那就需要每个点都有一条出边,此时就可以按照点数和边数的关系来分类讨论。
1.对于一个连通图来说 如果点数
>
>
>边数,要么是不连通图(与定义矛盾),要么就是一颗树,无法实现每个点都有出边,因为点数的边数不对应,一定会有一条边没有出边
2.对于一个连通图来说,如果点数
<
<
<边数,由于要实现每个点都有一条出边,那么可以先使每个点都出去一条边,此时用了一些边,但是还有一些边没有定方向,由于所有点的定了一个出去的方向,再给某个点定一个出边的话,就与题意矛盾,所以这种情况也不行
3.对于一个连通图,如果点数
=
=
=边数,说明这个连通图有一个环,且恰好能使每个点有一个出边。
讨论出来以后,就找每个连通块是否是点数
=
=
=边数,如果是的话,就有两种定方向(向左向右)的方案,对答案的贡献就是
✖
✖
✖ 2
代码和思路是参考官方题解来写的
#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
const int N=2e5+10;
#define ll long long
vector<int> e[N];
int n,m;
bool vis[N];
int x,y;
void dfs(int u)
vis[u]=1;
x++;
y+=e[u].size();
for(int i=0;i<e[u].size();i++)
int j=e[u][i];
if(!vis[j])
dfs(j);
int main()
cin>>n>>m;
for(int i=0;i<m;i++)
int a,b;
scanf("%d%d",&a,&b);
e[a].push_back(b);
e[b].push_back(a);
if(n!=m)
puts("0");
return 0;
ll res=1;
for(int i=1;i<=n;i++)
if(!vis[i])
x=0,y=0;
dfs(i);
if(x*2==y)
res=(ll)res*2%mod;
else
res=0;
break;
cout<<res<<endl;
F - Score of Permutations
链接: link.
题意:
对于一个 1 到 N 1到N 1到N的排列 P P P = ( P 1 , P 2 . . . . P n ) =(P_1,P_2....P_n) =(P1,P2....Pn),对产生一个分数贡献 S ( P ) S(P) S(P), S ( P ) S(P) S(P)的定义为自然排列 P ′ = ( 1 , 2..... N ) P'=(1,2.....N) P′=(1,2.....N)通过置换再次编程自然排列的最小次数。置换的意思就是每个 i ! = P i i!=P_i i!=Pi的人,都会把自己的球给 P i P_i Pi号人,一开始每个人 i i i号人,都会又 i i i号球,然后开始置换。现在问 1 到 N 1到N 1到N的排列产生的贡献的 k k k次方的和。
思路:
样例(2,3,1)解释,就是一开始(1,2,3)然后(3,1,2)再(2,3,1)最后变成(1,2,3),一共3步。
对于一个排列
P
P
P,可以把里面的数字看成
m
m
m个循环,即
m
m
m个环,每一个点
i
i
i指向
P
i
P_i
Pi,就会形成多个循环环,例如
N
=
5
,
P
=
(
2
,
5
,
4
,
3
,
1
)
N=5,P=(2,5,4,3,1)
N=5,P=(2,5,4,3,1),里面就会有两个环
3
−
>
4
−
>
3....
和
1
−
>
2
−
>
5
−
>
1..
3->4->3....和1->2->5->1..
3−>4−>3....和1−>2−>5−>1..长度分别为2和3,最终恢复成自然排列的最小次数是
l
c
m
(
2
,
3
)
=
6
lcm(2,3)=6
lcm(2,3)=6,一个排列会产生的贡献通过打表找规律发现是
l
c
m
(
k
1
,
k
2
.
.
.
.
k
m
)
lcm(k_1,k_2....k_m)
lcm(k1,k2....km)。
然后可以定义
f
(
i
,
j
)
f(i,j)
f(i,j)为
i
i
i个人,所有环的长度的lcm为
j
j
j的方案数
f
(
i
+
x
,
l
c
m
(
j
,
x
)
)
+
=
f
(
i
,
j
)
∗
C
n
−
i
−
1
x
−
1
∗
(
x
−
1
)
!
f(i+x,lcm(j,x))+=f(i,j)*C_n-i-1^x-1*(x-1)!
f(i+x,lcm(j,x))+=f(i,j)∗Cn−i−1x−1∗(x−1)!
为了防止出现重复,选择
x
x
x个人的时候,第一个人要先选剩下的人中编号最小的人,每个环内的最小值一定值单调递增的,所以就是
C
n
−
i
−
1
x
−
1
C_n-i-1^x-1
Cn−i−1x−1,就因为第一个人被选出来了,而
x
x
x个人会形成
(
x
−
1
)
!
(x-1)!
(x−1)!种循环排列。
对于
n
n
n个数的循环排列,会形成
(
n
−
1
)
!
(n-1)!
(n−1)!种方案,而不是
n
!
n!
n!种方案,因为这不是全排列,而是循环排列。
例如圈(1,2,3,4)和(2,3,4,1)都对应着同一个循环环1->2->3->4
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll N = 55, mod = 998244353;
ll n, k;
ll fact[N], infact[N];
map<ll, ll> dp[N];
ll qmi(ll a, ll k)
ll res = 1;
while (k)
if (k & 1) res = (ll)res * a % mod;
k >>= 1;
a = a * a%mod;
return res;
void init()
fact[0]=infact[0]=1;
for(int i=1;i<N;i++)
fact[i]=(ll)fact[i-1]*i%mod;
infact[i]=(ll)infact[i-1]*qmi(i,mod-2)%mod;
ll C(ll a, ll b)
if (a < b) return 0;
return fact[a] * infact[b] % mod * infact[a - b] % mod;
ll gcd(ll a, ll b) return !b ? a : gcd(b, a % b);
ll lcm(ll a, ll b) return a / gcd(a, b) * b;
int main()
init();
cin >> n >> k;
dp[0][1] = 1;
for (int i = 0; i < n; i++)
for (auto it : dp[i])
for (int j = 1; i + j <= n; j++)
dp[i + j][lcm(j, it.first)] = (dp[i + j][lcm(j, it.first)] + it.second * C(n - i - 1, j - 1) %mod * fact[j - 1] % mod) % mod;
ll res = 0;
for (auto it : dp[n])
res = (res + it.second * qmi(it.first, k)) % mod;
cout << res << endl;
To be continued
如果你有任何建议或者批评和补充,请留言指出,不胜感激
以上是关于AtCoder Beginner Contest 226(EF补题)的主要内容,如果未能解决你的问题,请参考以下文章
AtCoder Beginner Contest 115 题解