0%

AtCoder Beginner Contest 187题解

A、B、C略

D - Choose Me

设$S$为选择的进行演讲的城镇的集合。那么两个人的选票差为
$$
\sum_{i\in S}(2\times a[i]+b[i])-\sum_{i=1}^na[i]
$$
要使T的选票多于A的选票,即找到一个元素最小的集合$S$,使得该式大于0。

直接按照$2\times a[i]+b[i]$从大到小排序即可。

E - Through Path

每次操作均是对一颗子树或者一颗子树的补集加上一个$x_i$,最后离线询问每一个点的点权。

直接dfs求出dfn,维护子树关系,然后按照dfn差分数组即可。

F - Close Group

这一题比较有意思,当场没有想出来,主要也是没有写过这个类型的题目。

大意是给定一个$n$点$m$边的简单无向图,求出通过删除一定的边后,使得图中的每一个连通分量都是完全图的最小连通分量个数是多少。

解法:首先枚举每一个状态$i$,判断该状态是否构成完全图,如果是,则标记$good[i]=1$。设$dp[i]$为选择了子图$i$后,子图中满足要求的最小连通分量个数。初始化$dp[0]=0$,其它为正无穷。枚举每一个状态,若$good[i]=1$,则$dp[i]=1$;否则,枚举状态的每一个子集j,有方程$dp[i]=min(dp[i],dp[j]+dp[i\hat{}j])$。将状态i用i的子集j和j的补i^j进行转移的原因是:假设存在i是由多个子集共同组合而成是最优的,则这多个子集组合而成的i的子集显然不是最优的,这与假设矛盾。

那么如何枚举每一个子集呢?

对于二进制状态$S$,可以用此方法来进行不重不漏地枚举。

for (int sub = S; sub; sub = (sub - 1) & S) {
    // sub 为 S 的子集
}

假设当前子集$sub=(d_n d_{n-1} \cdots d_k 10\cdots 0)_2$

那么$sub-1=(d_n d_{n-1} \cdots d_k 01\cdots 1)_2$

(sub-1)&S得到的自然就是二进制表示小于sub的最大的子集 。

枚举的时间复杂度$O(3^n)$:
$$
\sum_{i=0}^n\binom{n}{i}\cdot2^i=(1+2)^n
$$

#参考代码

#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1 << 18;
int n, m;
bool adj[20][20], good[maxn];
int dp[maxn];
inline bool judge(int mask) {
    vector<int> temp;
    for(int i = 0; i < n; ++i)
        if(mask >> i & 1) temp.push_back(i);
    for(int i = 0; i < temp.size(); ++i)
        for(int j = i + 1; j < temp.size(); ++j) {
            int u = temp[i], v = temp[j];
            if(!adj[u][v])
                return false;
        }
    return true;
}
int main() {	
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    cin >> n >> m;
    for(int i = 1; i <= m; ++i) {
        int u, v;
        cin >> u >> v;
        u--, v--;
        adj[u][v] = adj[v][u] = 1;
    }
    for(int i = 0; i < (1 << n); ++i)
        good[i] = judge(i);
    memset(dp, 0x3f3f3f3f, sizeof(dp));
    dp[0] = 0;
    for(int i = 1; i < (1 << n); ++i) {
        if(good[i])
            dp[i] = 1;
        else {
            for (int j = i; j; j = (j - 1) & i) 
                dp[i] = min(dp[i], dp[j] + dp[i ^ j]);
        }
    }
    cout << dp[(1 << n) - 1];
    return 0;
}