xcpc训练题记录第-1-卷

注意: \color{red}{注意:} 注意: 个人博客传送门

A. Type The Strings

来源: 2021 − 2022 2021-2022 20212022 年度国际大学生程序设计竞赛第 10 10 10 届陕西省程序设计竞赛 C C C

备注:这题在 2024 2024 2024 湖北省 I C P C ICPC ICPC 省赛的热身赛上也见到了,然后不会,现在属于赛后补题目

题目大意:

  • 给你 n n n 个字符串,然后首先需要找到一个字符串,将其打印在工作区,然后你要将这个字符串进行变换,使得所有字符串都出现至少一次,求最小的代价,其中:
    1. 打印一个字符串花费为 l e n s i len_{s_i} lensi
    2. 复制一个字符串花费为 k k k
    3. 从当前的序列的任意位置删除一个字符花费为 1 1 1
    4. 从当前的序列的任意位置添加一个字符花费为 1 1 1

思路:

  • 首先,我们看 n n n 的范围,然后可以把时间复杂度控制到 1 0 8 10^8 108 以内。
  • 然后我们发现,这个字符串的操作,两两之间互相转换的花费 w i w_i wi l e n s i + l e n s j − 2 × c o m s i & s j len_{s_i} + len_{s_j} - 2 \times com_{s_i \& s_j} lensi+lensj2×comsi&sj,其中 c o m s i & s j com_{s_i \& s_j} comsi&sj 表示字符 s i s_i si 和字符串 s j s_j sj 的最长公共子序列的长度。
  • 然后我们把它们的编号作为点,我们将 s i s_i si s j s_j sj 的花费 w i w_i wi 作为边权,我们建一个虚点,这个虚点到任意一个字符串的边权值就是字符串的长度( l e n s i len_{s_i} lensi),然后跑最小生成树,这里我写的 p r i m prim prim,其实也可以写 k r u s k a l kruskal kruskal 算法,可以发现,通过最小生成树算法,我们可以遍历到所有的边,将所有的形态都转换出来。

时间复杂度: O ( n 4 ) O(n^4) O(n4)

constexpr int inf = 0x3f3f3f3f;

signed main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    
    int n, k;
    std::cin >> n >> k;

    std::vector<std::string> s(n + 1);
    for (int i = 1; i <= n; i++) {
        int x;
        std::cin >> x >> s[i];
    }

    std::vector<std::vector<int>> G(110, std::vector<int>(110, inf));
    for (int i = 1; i <= n; i++) {
        for (int j = i + 1; j <= n; j++) {
            auto LCS = [&](std::string a, std::string b) {
                std::vector<std::vector<int>> dp(a.size() + 1, std::vector<int>(b.size() + 1));
                for (int i = 1; i <= a.size(); i++) {
                    for (int j = 1; j <= b.size(); j++) {
                        dp[i][j] = std::max(dp[i - 1][j], dp[i][j - 1]);
                        if (a[i - 1] == b[j - 1]) {
                            dp[i][j] = std::max(dp[i - 1][j - 1] + 1, dp[i][j]);
                        }
                    }
                }
                int li = a.size(), lj = b.size();
                G[i][j] = G[j][i] = 
                std::min(std::min({li, lj, k}) + li + lj - 2 * dp[a.size()][b.size()], G[i][j]);
            };
            LCS(s[i], s[j]);
        }
    }   

    for (int i = 1; i <= n; i++) {
        G[n + 1][i] = s[i].size();
    }

    std::vector<int> dist(n + 2, inf), vis(n + 2);
    dist[n + 1] = 0;
    std::function<int()> prim = [&]() {
        int res = 0;
        for (int i = 1; i <= n + 1; i++) {
            int t = -1;
            for (int j = 1; j <= n + 1; j++) {
                if (!vis[j] && (t == -1 || dist[t] > dist[j])) {
                    t = j;
                }
            }
            if (i && dist[t] == inf) return inf;

            if (i) res += dist[t];
            vis[t] = true;

            for (int j = 1; j <= n; j++) dist[j] = std::min(dist[j], G[t][j]);
        }

        return res;
    };

    int t = prim();

    std::cout << t << "\n";

    return 0;
}

B. Countless Me

来源:第 49 49 49 I C P C ICPC ICPC 国际大学生程序设计竞赛邀请赛武汉站 - 正式赛

备注:赛后补题目

题目大意:

  • 给你一个序列,你可以对其中任意一个数进行 + x +x +x,对另一个数进行 − x -x x,即 { a i : = a i + x a j : = a j − x \begin{cases}{} a_i := a_i + x \\ a_j := a_j - x \end{cases} {ai:=ai+xaj:=ajx
  • 最后求将这个序列的所有数或起来的最小值,即 a n s = a 1   ∣   a 2   ∣   . . .   ∣   a n ans = a_1 \space | \space a_2 \space | \space ... \space | \space a_n ans=a1  a2  ...  an

思路:

  • 怎么让一些数或起来的结果尽可能小,那么我们就需要尽可能的减少最高位的 1 1 1,同时,这里有一个很明显的结论

    • n n n 次操作里,我们每次操作两个数,对其加减任意 x x x,我们可以让序列里的数字变成任意数字
  • 从高位向低位进行贪心,如果这一位构成的数,尽可能的减少最高位的 1 1 1

  • 我们有了贪心的思路,还需要考虑一个特殊情况:当这个数组的和可以整除这一位时,我们要取一个 m i n min min,以保证最小。

时间复杂度: O ( n ) O(n) O(n)

signed main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    
    int n;
    std::cin >> n;

    i64 sum = 0;
    for (int i = 0; i < n; i++) {
        int x;
        std::cin >> x;
        sum += x;
    }

    i64 ans = 0;
    for (int i = 29; i >= 0; i--) {
        if (((1LL << i) - 1) * n < sum) {
            ans |= 1LL << i;
            int t = std::min(1LL * n, sum >> i);
            sum -= 1LL * t * (1 << i);
        }
    }

    std::cout << ans << "\n";

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值