珈百璃堕落的开始

GitHub跳转原题关系图返回列表

把每个式子化成 `(#s-#c, #s+#c)`,再做差值背包,求总差值为 0 时总长度最大,答案就是长度的一半。

OJ: luogu

题目 ID: P4832

难度:提高+/省选-

标签:动态规划背包数学状态设计

日期: 2026-06-21 07:59

题意

每个式子由若干个 sc 相加组成,其中:

  • s = sin^2(pi/7)
  • c = cos^2(pi/7)

可以从给定式子中选一些相加,要求结果是整数,并且这个整数尽量大。

思路

先看小数据暴力:

cpp
#include <bits/stdc++.h>
using namespace std;

const int NEG_INF = -0x3f3f3f3f;

int n;
vector<int> diff_val, len_val;
int best_answer;

void dfs(int idx, int diff_sum, int len_sum) {
    if (idx == n) {
        if (diff_sum == 0) {
            best_answer = max(best_answer, len_sum / 2);
        }
        return;
    }

    // 不选当前式子。
    dfs(idx + 1, diff_sum, len_sum);
    // 选当前式子。
    dfs(idx + 1, diff_sum + diff_val[idx], len_sum + len_val[idx]);
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    // brute.cpp:小数据暴力枚举。
    // 直接枚举每个式子选或不选,统计总差值是否为 0。
    cin >> n;
    diff_val.resize(n);
    len_val.resize(n);

    for (int i = 0; i < n; i++) {
        string str;
        cin >> str;
        int cnt_s = 0;
        int cnt_c = 0;
        for (unsigned int j = 0; j < str.size(); j++) {
            if (str[j] == 's') {
                cnt_s++;
            } else if (str[j] == 'c') {
                cnt_c++;
            }
        }
        diff_val[i] = cnt_s - cnt_c;
        len_val[i] = cnt_s + cnt_c;
    }

    best_answer = 0;
    dfs(0, 0, 0);
    cout << best_answer << '\n';
    return 0;
}

设:

  • a = sin^2(pi/7)
  • b = cos^2(pi/7)

有恒等式:

a + b = 1

于是一个式子只需要关心两件事:

  1. 它一共有多少个字符,记作 len
  2. 它的 sc 多多少,记作 diff = #s - #c

如果最终选中的所有式子总共有:

  • Ss
  • Cc

那么总和是:

S * a + C * b

只有当 S = C 时,才能全部配成若干个 a+b=1,结果才是整数。

而这时答案正好就是:

(S + C) / 2

所以题目变成:

选出若干个式子,使总差值 diff0,并让总长度最大。

这就是一个差值背包:

  • 状态:总差值为某个值时的最大总长度
  • 转移:每个式子选或不选

DP 转移方程

设第 i 个式子的差值为 diff_i,长度为 len_i。 若当前总差值是 d,选择这个式子后:

dp[d+diffi]=max(dp[d+diffi], dp[d]+leni) dp[d+diff_i]=\max(dp[d+diff_i],\ dp[d]+len_i)

代码里会给差值整体加一个偏移量,避免出现负下标。

最后输出:

dp[0] / 2

代码

cpp
#include <bits/stdc++.h>
using namespace std;

const int MAXM = 1000005;
const int NEG_INF = -0x3f3f3f3f;

int n;
int diff_val[MAXM], len_val[MAXM];
int total_abs_diff;
int dp[2 * MAXM + 5];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 1; i <= n; i++) {
        string str;
        cin >> str;
        int cnt_s = 0;
        int cnt_c = 0;
        for (unsigned int j = 0; j < str.size(); j++) {
            if (str[j] == 's') {
                cnt_s++;
            } else if (str[j] == 'c') {
                cnt_c++;
            }
        }
        diff_val[i] = cnt_s - cnt_c;
        len_val[i] = cnt_s + cnt_c;
        total_abs_diff += abs(diff_val[i]);
    }

    int offset = total_abs_diff;
    for (int i = 0; i <= 2 * total_abs_diff; i++) {
        dp[i] = NEG_INF;
    }
    dp[offset] = 0;

    int cur = 0;
    for (int i = 1; i <= n; i++) {
        if (diff_val[i] >= 0) {
            for (int d = cur; d >= -cur; d--) {
                if (dp[d + offset] == NEG_INF) {
                    continue;
                }
                int nd = d + diff_val[i];
                dp[nd + offset] = max(dp[nd + offset], dp[d + offset] + len_val[i]);
            }
        } else {
            for (int d = -cur; d <= cur; d++) {
                if (dp[d + offset] == NEG_INF) {
                    continue;
                }
                int nd = d + diff_val[i];
                dp[nd + offset] = max(dp[nd + offset], dp[d + offset] + len_val[i]);
            }
        }
        cur += abs(diff_val[i]);
    }

    cout << dp[offset] / 2 << '\n';
    return 0;
}

复杂度

时间复杂度 O(nm)O(nm),空间复杂度 O(m)O(m),其中 m 是题目中的总字符规模量。

总结

这题最关键的不是三角函数,而是把它转化成:

  • 差值是否能配平
  • 配平后总长度尽量大

一旦想到 sin^2x + cos^2x = 1,就会变成很自然的背包题。