标签: 容斥

  • noi.ac 277 游戏

    见注释。

    另递推求逆元特别慢,效率要求高的地方不要用。

    // #include <iostream>
    #pragma GCC optimize("O3")
    #include <cstdio>
    #include <utility>
    using int_t = long long int;
    // using std::cin;
    // using std::cout;
    // using std::endl;
    
    const int mod = 1e9 + 7;
    const int_t LARGE = 2e7;
    int_t power(int_t base, int_t index) {
        index = (index % (mod - 1) + mod - 1) % (mod - 1);
        int_t result = 1;
        while (index) {
            if (index & 1) result = result * base % mod;
            index >>= 1;
            base = base * base % mod;
        }
        return result;
    }
    
    int fact[LARGE + 1], inv[LARGE + 1], factInv[LARGE + 1], inv2[LARGE + 1];
    inline int C(int n, int m) {
        // if (n < m) return 0;
        return (int_t)fact[n] * factInv[m] % mod * factInv[n - m] % mod;
    }
    //把2n个带标号元素划分成n个大小为2的集合的方案数(集合内无序)
    inline int G(int n) {
        return (int_t)fact[2 * n] * inv2[n] % mod * factInv[n] % mod;
    }
    //至少淘汰了m个玩家的方案数
    inline int F(int n, int m) {
        // if (n < m) return 0;
        if (m == 0) return G(n);
        //前半部分把2n-m个元素划分成m段,每段再塞一个被淘汰的玩家,后半部分钦定剩下的随便选。
        // 2n个元素本身未考虑循环同构。
        return (int_t)2 * n * inv[m] % mod * C(2 * n - m - 1, m - 1) % mod *
               G(n - m) % mod;
    }
    int main() {
        fact[0] = inv[1] = factInv[0] = fact[1] = factInv[1] = 1;
        inv2[0] = 1;
        int n, k;
        // cin >> n >> k;
        scanf("%d%d", &n, &k);
        inv2[1] = (mod - mod / 2);
        for (int i = 2; i <= 2 * n; i++) {
            // inv[i] = (int_t)(mod - mod / i) * inv[mod % i] % mod;
            fact[i] = (int_t)fact[i - 1] * i % mod;
            inv2[i] = (int_t)inv2[i - 1] * inv2[1] % mod;
        }
        // factInv[i] = (int_t)factInv[i - 1] * inv[i] % mod;
        factInv[2 * n] = power(fact[2 * n], -1);
        for (int_t i = 2 * n; i >= 1; i--) {
            inv[i] = (int_t)factInv[i] * fact[i - 1] % mod;
            factInv[i - 1] = (int_t)factInv[i] * i % mod;
        }
        int result = 0;
    
        for (int i = k; i <= n; i++) {
            result = (result + (int_t)(((i - k) & 1) ? (mod - 1) : 1) * C(i, k) %
                                   mod * F(n, i) % mod) %
                     mod;
        }
        printf("%lld\n", (int_t)result * power(G(n), -1) % mod);
        return 0;
    }

     

  • BZOJ2839 集合计数

    $$ \sum_{k\le i\le n}{\left( -1 \right) ^{i-k}\left( \begin{array}{c} i\\ k\\ \end{array} \right) \left( \begin{array}{c} n\\ i\\ \end{array} \right) \left( 2^{2^{n-i}}-1 \right)} \\ \text{枚举}i\text{个位置,钦定这}i\text{个位置所构成的集合合法。} \\ \text{除去这}i\text{个位置之外的位置随意。} \\ $$

    #include <iostream>
    
    using int_t = long long int;
    using std::cin;
    using std::cout;
    using std::endl;
    
    const int_t mod = 1000000007;
    const int_t LARGE = 1e6;
    int_t power(int_t base, int_t index, int_t mod) {
        int_t result = 1;
        base = (base % mod + mod) % mod;
        while (index) {
            if (index & 1) result = result * base % mod;
            index >>= 1;
            base = base * base % mod;
        }
        return result;
    }
    int_t fact[LARGE + 1];
    int_t C(int_t n, int_t m) {
        return fact[n] * power(fact[m] * fact[n - m] % mod, mod - 2, mod) % mod;
    }
    int main() {
        int_t n, k;
        cin >> n >> k;
        fact[0] = 1;
        for (int_t i = 1; i <= LARGE; i++) fact[i] = fact[i - 1] * i % mod;
        int_t result = 0;
        for (int_t i = k; i <= n; i++) {
            result =
                (result +
                 power(-1, i - k, mod) * C(i, k) % mod * C(n, i) % mod *
                     (power(2, power(2, n - i, mod - 1), mod) % mod - 1 + mod)) %
                mod;
        }
        cout << result << endl;
        return 0;
    }

     

  • APIO2018 铁人两项

    题意:求满足如下条件的有序三元组个数$(u,v,w)$且u到v的路径与v到w的路径不重叠。

    对原图建立圆方树,然后枚举中间点m,如果存在两个点u和v,满足u到m的路径和v到m的路径不经过同一个圆点。

    考虑容斥,枚举中间点m,然后计算除了m之外的总的有序点对数,然后减掉到达m必须要经过同一个点的点对数

    如何容斥?

    考虑一个方点u的所有相邻的圆点v,v的子树内的一个点对,要想到达u除了v之外的一个相邻节点,则必须经过v。

    所以对于每个方点u,统计其所有相邻圆点v子树内的有序点对数,然后乘u的相邻节点个数-1,即为u的每个相邻节点应该减掉的值的和。

    #include <iostream>
    #include <algorithm>
    #include <vector>
    #include <stack>
    #include <assert.h>
    using int_t = long long int;
    using std::cin;
    using std::cout;
    using std::endl;
    
    #define debug(x) cout << #x << " = " << x << endl;
    
    const int_t LARGE = 300000;
    
    constexpr inline int_t C2(int_t x)
    {
        return x * (x - 1) / 2;
    }
    struct Edge
    {
        int_t v1, v2;
    };
    
    int_t n, m;
    std::vector<int_t> graph[LARGE + 1];
    std::vector<int_t> tree[LARGE + 1];
    std::vector<int_t> BCC[LARGE + 1];
    std::stack<Edge> stack;
    int_t size[LARGE + 1];
    bool isCut[LARGE + 1];
    int_t DFSN[LARGE + 1];
    int_t LOW[LARGE + 1];
    int_t BCCID[LARGE + 1];
    int_t parent[LARGE + 1];
    int_t block[LARGE + 1];
    int_t nextVtx;
    void DFS(int_t vtx, int_t from = -1)
    {
        DFSN[vtx] = LOW[vtx] = ++DFSN[0];
        int_t chdCount = 0;
        for (int_t to : graph[vtx])
        {
            if (to == from)
                continue;
    
            if (DFSN[to] == 0)
            {
                stack.push(Edge{vtx, to});
                chdCount++;
                DFS(to, vtx);
                LOW[vtx] = std::min(LOW[vtx], LOW[to]);
                //判断不能写LOW[vtx]>=DFSN[vtx],否则会把一棵不是BCC的子树算重
                if (LOW[to] >= DFSN[vtx])
                {
                    isCut[vtx] = true;
                    int_t id = ++BCCID[0];
                    int_t next = ++nextVtx;
                    while (true)
                    {
                        auto top = stack.top();
                        stack.pop();
                        if (BCCID[top.v1] != id)
                        {
                            BCCID[top.v1] = id;
                            BCC[id].push_back(top.v1);
                            tree[next].push_back(top.v1);
                            tree[top.v1].push_back(next);
                        }
                        if (BCCID[top.v2] != id)
                        {
                            BCCID[top.v2] = id;
                            BCC[id].push_back(top.v2);
                            tree[next].push_back(top.v2);
                            tree[top.v2].push_back(next);
                        }
                        if ((top.v1 == vtx && top.v2 == to) || (top.v1 == to && top.v2 == vtx))
                            break;
                    }
                }
            }
            //只判断后向边,防止算重
            else if (DFSN[to] < DFSN[vtx])
            {
                stack.push(Edge{vtx, to});
                LOW[vtx] = std::min(LOW[vtx], DFSN[to]);
            }
        }
        if (from == -1 && chdCount == 1)
            isCut[vtx] = false;
    }
    void DFS1(int_t vtx, int_t block, int_t from = -1, int_t depth = 1)
    {
        static bool visited[LARGE + 1];
        if (visited[vtx])
            return;
        parent[vtx] = from;
        visited[vtx] = true;
        size[vtx] = (vtx <= n);
        ::block[vtx] = block;
        for (int_t to : tree[vtx])
        {
            if (visited[to])
                continue;
            DFS1(to, block, vtx, depth + 1);
            size[vtx] += size[to];
        }
    }
    
    int main()
    {
        cin >> n >> m;
        for (int_t i = 1; i <= m; i++)
        {
            int_t v1, v2;
            cin >> v1 >> v2;
            graph[v1].push_back(v2);
            graph[v2].push_back(v1);
        }
        nextVtx = n;
        std::vector<int_t> roots;
        for (int_t i = 1; i <= n; i++)
        {
            if (DFSN[i] == 0)
            {
                DFS(i);
                DFS1(i, i);
                roots.push_back(i);
            }
        }
        for (int_t i = 1; i <= nextVtx; i++)
        {
            auto &curr = tree[i];
            std::sort(curr.begin(), curr.end());
            curr.resize(std::unique(curr.begin(), curr.end()) - curr.begin());
        }
    
        //我放弃排列组合了
        int_t result = 0;
        for (int_t i = 1; i <= n; i++)
        {
            result += C2(::size[::block[i]] - 1) * 2;
        }
        for (int_t i = n + 1; i <= nextVtx; i++)
        {
            for (int_t vtx : tree[i])
            {
                if (vtx == parent[i])
                {
                    result -= (tree[i].size() - 1) * C2(size[block[i]] - size[i]) * 2;
                }
                else
                {
                    result -= (tree[i].size() - 1) * C2(size[vtx]) * 2;
                }
            }
        }
        cout << result << endl;
        return 0;
    }

     

  • CQOI2014 数三角形

    $$ \text{计算}\left( \begin{array}{c} \left( n+1 \right) \times \left( m+1 \right)\\ 3\\ \end{array} \right) \text{,然后减掉不合法的方案。} \\ \text{不合法的方案为三点共线。} \\ \text{首先考虑水平或者竖直直线的三点共线,很好计算。} \\ \text{其次考虑倾斜直线的三点共线。} \\ \text{枚举直线的两个端点,钦定两个端点选中的情况下,第三个点作为直线两端点} \\ \text{中间的一个点。} \\ \text{枚举直线两个端点}O\left( n^2m^2 \right) \text{显然}TLE\text{,可以考虑转变一下思路。} \\ \text{枚举一条以原点为一个端点,}\left( i,j \right) \text{为另一个端点的直线。} \\ \text{然后看一下有多少条直线和这个直线长度一样,斜率也一样。} \\ \text{说白了就是看这个直线可以往右边平移几次,往下边平移几次(认为左上是原点)} \\ \text{以原点为一个端点,另一个端点为}\left( i,j \right) \text{的直线可以沿水平方向移动}\left( m-j \right) \text{次,沿竖直方向} \\ \text{移动}n-i\text{次。} \\ \text{画画图就能看出来。} \\ \text{同时这个直线,除去两个端点,中间有}gcd\left( i,j \right) -\text{1个整数点。} \\ $$

     

    #include <iostream>
    #include <algorithm>
    using int_t = long long int;
    
    using std::cin;
    using std::cout;
    using std::endl;
    int_t n, m;
    
    int_t C3(int_t x)
    {
        return x * (x - 1) * (x - 2) / 6;
    }
    int_t gcd(int_t a, int_t b)
    {
        if (b == 0)
            return a;
        return gcd(b, a % b);
    }
    int main()
    {
        cin >> n >> m;
        if (n < m)
            std::swap(n, m);
        n++;
        m++;
        //水平或竖直共线的
        int_t result = C3((n) * (m)) - C3(n) * m - C3(m) * n;
        for (int_t i = 1; i < n; i++)
        {
            for (int_t j = 1; j < m; j++)
            {
                result -= 2 * (gcd(i, j) - 1) * (n - i) * (m - j);
            }
        }
        cout << result << endl;
        return 0;
    }