简单环(状压dp)-----Java题解

 

目录

简单环 

题目描述 

输入描述:

输出描述:

输入

输出

备注:

 思路解析:

代码实现


题目来源:https://ac.nowcoder.com/acm/contest/25022/1022

简单环 

时间限制:C/C++ 2秒,其他语言4秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述 

给定一张n个点m条边的无向图,求出图中所有简单环的数量。(简单环:简单环又称简单回路,图的顶点序列中,除了第一个顶点和最后一个顶点相同外,其余顶点不重复出现的回路叫简单回路。或者说,若通路或回路不重复地包含相同的边,则它是简单的)

输入描述:

第一行三个数n m k (k在输出描述中提到)
接下来m行每行两个数u,v表示u到v之间存在一条边(无重边)

输出描述:

输出k行每行1个整数
第i个数表示长度%k==i-1的简单环的数量(对998244353取模)
(只记录长度大于2的简单环的数量)

示例1

输入

复制

4 6 3
1 2
2 3
3 4
4 1
2 4
1 3

输出

复制

4
3
0

备注:

n<=20
m<=n*(n-1)/2
k<=n

 思路解析:

简单环的定义:

(简单环:简单环又称简单回路,图的顶点序列中,除了第一个顶点和最后一个顶点相同外,其余顶点不重复出现的回路叫简单回路。或者说,若通路或回路不重复地包含相同的边,则它是简单的)

因为他是一个n个点m条边的无向图,要求求简单环的数量,

因为他没有重边,所以他从i节点到j节点的路径长度为一的路径只有一个,所以我们可以记录上一次状态的终点,然后从他转移到当前状态的终点上。dp[i][j]表示在状态i下,j为状态终点的路径方案数。

dp[ s | (1 << (j - 1))][j] = (dp[ s | (1 << (j - 1))][j] + dp[ s | (1 << (j - 1))][i]) % mod;

然后如果当前状态的终点可以走到起点的话他就构成了一个环。这里又因为一个环起点的位置并不影响这个环的结构,所以我们把一个状态中最小(最右)的节点作为起点,并且之后经过的起点一定要比起点大,这样可以避免重复计算。但他一定会重复计算两次这个无法避免

如 中间状态 2 - 3 - 5,我们可以让起点连向2,也可以让起点连向5,所以统计完答案后,一定要除以2的逆元。

代码实现

import java.util.Scanner;
import java.util.Vector;

/**
 * @ProjectName: study3
 * @FileName: Ex22
 * @author:HWJ
 * @Data: 2023/11/8 20:38
 */
public class Ex22 {
    static int mod = 998244353;
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();
        int m = input.nextInt();
        int k = input.nextInt();
        int[] ans = new int[k];
        int[][] dp = new int[1 << n][n + 1];
        int[][] map = new int[n + 1][n + 1];
        int[] num = new int[1 << n];
        for (int i = 0; i < 1 << n; i++) {
            num[i] = calc(i);
        }
        for (int i = 0; i < m; i++) {
            int a = input.nextInt();
            int b = input.nextInt();
            map[a][b] = 1;
            map[b][a] = 1;
        }

        for (int i = 1; i <= n; i++) {
            dp[1 << (i - 1)][i] = 1;
        }

        for (int s = 1; s < (1 << n); s++) {
            int st = -1;
            for (int i = 0; i < n; i++) {
                if ((s & (1 << i)) != 0){
                    st = i + 1; // 在这个状态下 以这个节点作为状态。
                    break;
                }
            }


            for (int i = st; i <= n; i++) {
                if ((s & (1 << (i - 1))) != 0){
                    if (map[i][st] == 1 && num[s] > 2){
//                        System.out.println(s + "  " + num[s] % k);
                        ans[num[s] % k] = (ans[num[s] % k] + dp[s][i]) % mod;
                    }

                    for (int j = st + 1; j <= n; j++) {
                        if (map[i][j] == 1 && (s & (1 << (j - 1))) == 0){
                            int a = s | (1 << (j - 1));
                            dp[a][j] = (dp[a][j] + dp[s][i]) % mod;
                        }
                    }
                }
            }
        }
        for (int i = 0; i < k; i++) {
            System.out.println((ans[i] * quick(2)) % mod);
        }
    }

    public static int calc(int x){
        int ans = 0;
        while(x > 0){
            if ((x & 1) == 1) ans++;
            x >>= 1;
        }
        return ans;
    }

    public static long quick(long x){
        int a = mod - 2;
        long ans = 1;
        for (; a > 0; a >>= 1, x = x * x % mod) {
            if ((a & 1) == 1) ans = (ans * x) % mod;
        }
        return ans;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Studying~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值