AtCoder Beginner Contest 226(E、F补题)

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 1N的排列 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 1N的排列产生的贡献的 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)Cni1x1(x1)!
为了防止出现重复,选择 x x x个人的时候,第一个人要先选剩下的人中编号最小的人,每个环内的最小值一定值单调递增的,所以就是 C n − i − 1 x − 1 C_{n-i-1}^{x-1} Cni1x1,就因为第一个人被选出来了,而 x x x个人会形成 ( x − 1 ) ! (x-1)! (x1)!种循环排列。
对于 n n n个数的循环排列,会形成 ( n − 1 ) ! (n-1)! (n1)!种方案,而不是 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
如果你有任何建议或者批评和补充,请留言指出,不胜感激

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值