[背包DP] UOJ #181. 【UR #12】密码锁

Solution S o l u t i o n

因为这是一张竞赛图,所以把强连通分量缩起来以后回事一条链的情况。
那么强连通分量的数量就是相当于缩起来以后的点的数量。
对于图 G(V,E) G ( V , E ) 来说,这样的点集 SV S ⊆ V ,满足两个点集的边是这样的关系 eE^={(u,v)|uS,vVS} e ∈ E ^ = { ( u , v ) | u ∈ S , v ∈ V − S }
考虑 m m 条特殊的边对答案的贡献。原来点集 S S 的贡献是 12|S|(n|S|) 1 2 | S | ( n − | S | ) 。那么现在一条特殊的边相当于有 2p 2 p 的贡献。
就只需要考虑 m m 条边的情况,计算出强连通分量大小 x x 后乘上 12x(nx) 1 2 x ( n − x ) 就好了。
可以每个联通块里枚举哪些点 uS u ∈ S ,把这些边的贡献记录下来,背包起来就好了。

#include 
using namespace std;

const int MOD = 998244353;
const int N = 50;
const int INV = 796898467;
typedef long long ll;

vector<int> G[N];
vector<int> cc;
int n, m, x, y, z, gcnt, clc;
int vis[N], head[N], id[N];
int from[N], to[N], key[N];
int sum[N][N], dp[N][N];

inline void add(int &x, int a) {
    x = (x + a >= MOD) ? x + a - MOD : x + a;
}
inline int pwr(int a, int b) {
    b = (b % (MOD - 1) + MOD - 1) % (MOD - 1);
    int c = 1;
    while (b) {
        if (b & 1) c = (ll)c * a % MOD;
        b >>= 1; a = (ll)a * a % MOD;
    }
    return c;
}
inline void addEdge(int from, int to) {
    G[from].emplace_back(to);
    G[to].emplace_back(from);
}
inline void dfs(int u) {
    vis[u] = clc;
    id[u] = cc.size();
    cc.emplace_back(u);
    for (int to: G[u])
        if (!vis[to]) dfs(to);
}

int main(void) {
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        cin >> x >> y >> z;
        z = (ll)z * INV % MOD;
        addEdge(x, y);
        from[i] = x;
        to[i] = y;
        key[i] = z;
    }
    for (int i = 1; i <= n; i++)
        if (!vis[i]) {
            cc.clear();
            ++clc; dfs(i);
            for (int s = 0; s < (1 << cc.size()); s++) {
                int res = 1;
                for (int i = 1; i <= m; i++) {
                    int u = from[i], v = to[i];
                    if (vis[u] != clc) continue;
                    if ((s >> id[u] & 1) == (s >> id[v] & 1)) continue;
                    if (s >> id[u] & 1) res = 2ll * res * key[i] % MOD;
                    else res = 2ll * res * (MOD + 1 - key[i]) % MOD;
                }
                add(sum[clc][__builtin_popcount(s)], res);
            }
        }
    dp[0][0] = 1;
    for (int i = 0; i < clc; i++)
        for (int j = 0; j < n; j++)
            if (dp[i][j])
                for (int k = 0; k + j < n; k++)
                    add(dp[i + 1][k + j], (ll)dp[i][j] * sum[i + 1][k] % MOD);
    int ans = 0;
    for (int i = 0; i <= n; i++)
        add(ans, (ll)dp[clc][i] * pwr(2, -i * (n - i)) % MOD);
    ans = (ll)ans * pwr(10000, n * n - n) % MOD;
    cout << ans << endl;
    return 0;
}

你可能感兴趣的:(图论,动态规划)