• AT1919部門分け


    AT1919部門分け

    异世界传送门:
    洛谷AT1919
    ATcoder056C
    官方题解
    本题解来自

    题面

    高橋くんのいる会社は (N) 人の社員からなる。社員 (i) と社員 (j) の間には、信頼度 (w_{i, j})
    ​が定まっている。 おかげ様で会社はぐんぐん成長したため、 (N) 人をいくつかの部門に分けることになった。ここで、部門分けのスコアを、(部門の数)* (K) - (異なる部門に属する 2 人の間の信頼度の総和)と定める。 スコアの最大値を求めるプログラムを書いてください。
    ((n leq 17))

    一句话题意

    (n)个人分组,不同组的人之间会产生代价,使 (组数 * K - 总代价) 最大化。

    这题咋做

    考虑到这个题人数很少,我们可以进行状压。
    状态(f[S])表示选择了 (S)集合时 的状态最大值,从它的子集进行转移,则$$f[S] = max(f[S], K + f[S'] + (新加入的人与子集中的人产生的代价))$$但是如果每次转移的时候(n^2)计算新加入的人与子集中的人产生的代价,复杂度是不允许的。
    但是我们可以计算该集合内部每个人互相的代价,而他与另一个集合人产生的代价即为两个集合并集的代价减去两个集合内部的代价。

    [设集合A, B, C = A cup B$$ $$f[A, B之间] = f[C] - f[A] - f[B] ]

    这样我们可以通过(O(2^n))预处理各子集内部代价,转移时O(1)计算。由于枚举子集(O(3^n)), 故整体复杂度(O(3^n)), (3^{17} = 129, 140, 163), 可以通过此题。

    放代码

    #include <bits/stdc++.h>
    namespace fdata
    {
    inline char nextchar()
    {
        static const int BS = 1 << 21;
        static char buf[BS], *st, *ed;
        if (st == ed)
            ed = buf + fread(st = buf, 1, BS, stdin);
        return st == ed ? -1 : *st++;
    }
    #ifdef lky233
    #define nextchar getchar
    #endif
    template <typename T>
    inline T poread()
    {
        T ret = 0;
        char ch;
        while (!isdigit(ch = nextchar()))
            ;
    
        do
            ret = ret * 10 + ch - '0';
        while (isdigit(ch = nextchar()));
        return ret;
    }
    template <typename Y>
    inline void poread(Y &ret)
    {
        ret = 0;
        char ch;
        while (!isdigit(ch = nextchar()))
            ;
    
        do
            ret = ret * 10 + ch - '0';
        while (isdigit(ch = nextchar()));
    }
    #undef nextcar
    } // namespace fdata
    using fdata::poread;
    using namespace std;
    const int MAXN = 18;
    const long long INF = 1ll << 55;
    long long w[MAXN][MAXN];
    long long memo[1 << MAXN];
    long long f[1 << MAXN];
    int n;
    long long k;
    int main()
    {
    #ifdef lky233
        freopen("testdata.in", "r", stdin);
        freopen("testdata.out", "w", stdout);
    #endif
        poread(n);
        poread(k);
        for (register int i = 0; i < n; ++i)
            for (register int j = 0; j < n; ++j)
                poread(w[i][j]);
        for (register int s = 0; s < (1 << n); ++s)
        {
            for (register int i = 0; i < n; ++i)
            {
                for (register int j = i + 1; j < n; ++j)
                {
                    register int si = ((s >> i) & 1);
                    register int sj = ((s >> j) & 1);
                    if (si & sj)
                        memo[s] += w[i][j];
                }
            }
        }
        for (register int i = 0; i <= (1 << n); ++i)
        {
            for (register int j = i; j; j = (j - 1) & i)
            {
                f[i] = max(f[i], k + f[i ^ j] - memo[i] + memo[j] + memo[i ^ j]);
            }
        }
        cout << f[(1 << n) - 1] << endl;
    }
    

    还有一个小插曲

    原题解枚举子集是这么写的

    for (register int i = 0; i <= (1 << n); ++i)
        {
            register int j = i;
            do
            {
            } while (j != i);
        }
    

    至于(j) (!=) (i) 为啥能够成功结束循环,当$i = -1 $时,它的二次元二进制表示是(11111111...)(j)&(i) 恰好为 (j), 因此成功结束循环。

    本篇题解到此结束,ありがとうございます。
  • 相关阅读:
    动态规划解按摩师的最长预约时间
    C#中WinForm的Tab键顺序调整顺序
    内网穿透工具对比FRP+NPS+Zerotier与NAT服务器测试
    " " 和 ' ' 混用拼接html字符串,且含有事件,事件中有参数
    HAProxy在Windows下实现负载均衡与反向代理
    react 导入src外部的文件 Relative imports outside of src/ are not supported.
    11_实例
    C#删除指定目录下文件(保留指定几天前的日志文件)
    【转】系统创建定时执行任务bat批处理删除指定N天前文件夹的文件
    mariadb导如数据异常------Error Code: 1153
  • 原文地址:https://www.cnblogs.com/Shiina-Rikka/p/11574974.html
Copyright © 2020-2023  润新知