CF11D A Simple Task

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

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

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

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

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

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

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

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

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

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

这里有两种方案:

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

第二种实现比较简单。

时间复杂度 \(\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 @ 2022-10-19 21:03  dbxxx  阅读(38)  评论(0编辑  收藏  举报