CF11D A Simple Task

CF11D A Simple Task - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

数据范围容易让人想到状压 dp,状压对象为点集。

现有一条点集为 SS 的路径。目标是路径成环,所以转移时需要路径的开头和结尾两个信息。

可以想到:f(S,u,v)f(S, u, v) 表示从 uuvv 经过的点集为 SS 的路径数量。

但是这样一来,每个长度为 LL 的环会被统计 2L2L 次(每个点为起点,顺时针或逆时针走向)。为了避免重复,在每次统计答案 f(S,u,u)f(S, u, u) 的时候我们需要除以 LL,即 popcnt(S)\operatorname{popcnt}(S)。这里就会涉及到浮点数的计算,所以 ff 必须用浮点类型存,但是浮点类型无法在 250MB 内存储一个大小为 n2×2nn^2 \times 2^n 的数组。

怎么办?考虑优化状态:f(S,u)f(S, u) 表示从 lowbit(S)\operatorname{lowbit}(S)uu 经过的点集为 SS 的路径数量。

这样一来,每个长度为 LL 的环只会被该环中编号最小的节点统计 22 次(顺时针或逆时针走向)。22 是一个常数,因此我们直接统计 ff,最后除以 22 即可。这样以来 ff 被优化成了一个 n×2nn \times 2^n 的整数数组,可以通过。

换句话说,我们只统计满足这个条件的路径:开头是 ss,那么之后经过的点都比 ss 编号要大。

转移是显然的:f(S{v},v)f(S{v},v)+f(S,u)[(u,v)G]f(S \cup \{v\}, v) \gets f(S \cup \{v\}, v) + f(S, u)[(u, v) \in G]

最后,我们发现长度为 22 的“环”也会被统计。注意,长度为 22 的环只会被统计 11 次而不是 22 次,因为顺时针和逆时针走向实际上是一样的。

这里有两种方案:

  • 只统计 popcnt(S)3\operatorname{popcnt}(S) \ge 3f(S,lowbit(S))f(S, \operatorname{lowbit}(S)),设和为 sumsum,则答案为 sum2\dfrac{sum}{2}
  • 都统计,设和为 sumsum,则答案为 summ2\dfrac{sum - m}{2}

第二种实现比较简单。

时间复杂度 O(n2×2n)\mathcal{O}(n^2 \times 2^n)

/*
 * @Author: crab-in-the-northeast 
 * @Date: 2022-10-19 19:45:25 
 * @Last Modified by: crab-in-the-northeast
 * @Last Modified time: 2022-10-19 20:59:00
 */
#include <bits/stdc++.h>
#define int long long
inline int read() {
    int x = 0;
    bool flag = true;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-')
            flag = false;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = (x << 1) + (x << 3) + ch - '0';
        ch = getchar();
    }
    if(flag)
        return x;
    return ~(x - 1);
}

const int maxn = 20;
int f[1LL << maxn][maxn];
bool a[maxn][maxn];

signed main() {
    int n = read(), m = read();
    for (int i = 1; i <= m; ++i) {
        int u = read() - 1, v = read() - 1;
        a[u][v] = a[v][u] = true;
    }

    int N = (1LL << n);
    for (int u = 0; u < n; ++u)
        f[1LL << u][u] = 1;
    
    int ans = 0;

    for (int S = 0; S < N; ++S) {
        int s = (S & (-S));
        for (int u = 0; u < n; ++u) {
            for (int v = 0; v < n; ++v) if (a[u][v]) {
                if ((1LL << v) < s)
                    continue;
                if (S & (1LL << v)) {
                    if ((1LL << v) == s)
                        ans += f[S][u];
                } else
                    f[S | (1LL << v)][v] += f[S][u];
            }
        }
    }

    printf("%lld\n", (ans - m) >> 1LL);
    return 0;
}

posted @   dbxxx  阅读(39)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示