把每个式子化成 `(#s-#c, #s+#c)`,再做差值背包,求总差值为 0 时总长度最大,答案就是长度的一半。
OJ: luogu
题目 ID: P4832
难度:提高+/省选-
标签:动态规划背包数学状态设计
日期: 2026-06-21 07:59
题意
每个式子由若干个 s 和 c 相加组成,其中:
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
于是一个式子只需要关心两件事:
- 它一共有多少个字符,记作
len - 它的
s比c多多少,记作diff = #s - #c
如果最终选中的所有式子总共有:
S个sC个c
那么总和是:
S * a + C * b
只有当 S = C 时,才能全部配成若干个 a+b=1,结果才是整数。
而这时答案正好就是:
(S + C) / 2
所以题目变成:
选出若干个式子,使总差值
diff为0,并让总长度最大。
这就是一个差值背包:
- 状态:总差值为某个值时的最大总长度
- 转移:每个式子选或不选
DP 转移方程
设第 i 个式子的差值为 diff_i,长度为 len_i。
若当前总差值是 d,选择这个式子后:
代码里会给差值整体加一个偏移量,避免出现负下标。
最后输出:
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;
}复杂度
时间复杂度 m 是题目中的总字符规模量。
总结
这题最关键的不是三角函数,而是把它转化成:
- 差值是否能配平
- 配平后总长度尽量大
一旦想到 sin^2x + cos^2x = 1,就会变成很自然的背包题。