240104 杂题全谈 四边形不等式
因为输入法没有给我满意的候选项所以这次就不取抽象标题了。
A - Chef and Bitwise OR Operation
https://vjudge.net/contest/602275#problem/A
CodeChef - CHEFAOR。
看到「刚好 \(K\) 个」,脑子一热差点去写 wqs(
实际上当然是不需要的。观察数据范围,至少 \(n^2\) 跑得过。令 \(f_{i, j}\) 表示将前 \(i\) 个分为 \(j\) 段的最大价值。预处理一个 \(s_{l,r}\) 表示 \([l,r]\) 按位或起来的值,用一个类前缀和就可以 \(\mathcal O(n^2)\)。那么有:
然后对于代价函数 \(w=s\),四边形不等式成立。不是很想写,但是还是写一下吧。
就,最好还是把线段图画出来,其实挺显然的。
假设 \(x=w(b,d),y=w(a,d)-w(b,d),z=w(a,c)-y\),有 \(w(a,c)=y+z,w(a,c)+w(b,d)=x+y+z\),这是交叉。
那么 \(w(b,c)\ge z,w(a,d)=x+y\),有 \(w(a,d)+w(b,c)\ge x+y+z\),这是包含。交叉小于包含,四边形不等式成立。
那么就可以直接写了。复杂度 \(\mathcal O(T\times n^2)\)。
#define int long long
namespace XSC062 {
using namespace fastIO;
const int maxn = 5e3 + 5;
int T, n, k;
int a[maxn];
int s[maxn][maxn];
int f[maxn][maxn], w[maxn][maxn];
int main() {
read(T);
while (T--) {
read(n), read(k);
for (int i = 1; i <= n; ++i) read(a[i]);
memset(w, 0, sizeof (w));
memset(f, -0x3f, sizeof (f));
for (int i = 1; i <= n; ++i) {
for (int j = i; j <= n; ++j) {
s[i][j] = s[i][j - 1] | a[j];
// printf("s[%lld][%lld] = %lld\n",
// i, j, s[i][j]);
}
f[i][1] = s[1][i];
w[i][1] = 0;
}
for (int i = 1; i <= n; ++i) {
for (int j = 2;
j <= i && j <= k; ++j) {
for (int k = i - 1;
k >= w[i - 1][j - 1]; --k) {
if (f[i][j] < f[k][j - 1]
+ s[k + 1][i]) {
f[i][j] = f[k][j - 1]
+ s[k + 1][i];
w[i][j] = k;
}
}
// printf("f[%lld][%lld] = %lld\n",
// i, j, f[i][j]);
}
}
print(f[n][k], '\n');
}
return 0;
}
} // namespace XSC062
#undef int
B - Lawrence
https://vjudge.net/contest/602275#problem/B
HDU2829。
大意就是把一个长度为 \(n\) 的序列分成连续的 \(m+1\) 段,定义每段的价值为任意两个数相乘的和。
虽然但是,好板。然后我们看看每段的价值怎么算。数学一下就是 \(\dfrac {(\sum a_i)^2-\sum {a_i}^2}2\)。
然后证明显然吧,大概。所以直接写。
#define int long long
namespace XSC062 {
using namespace fastIO;
const int maxn = 1e3 + 5;
int n, m;
int a[maxn];
int s[maxn][maxn];
int f[maxn][maxn], w[maxn][maxn];
int main() {
read(n), read(m);
while (m++ || n) {
for (int i = 1; i <= n; ++i) read(a[i]);
memset(w, 0, sizeof (w));
memset(f, 0x3f, sizeof (f));
for (int i = 1; i <= n; ++i) {
int s1 = 0, s2 = 0;
for (int j = i; j <= n; ++j) {
s1 += a[j], s2 += a[j] * a[j];
s[i][j] = (s1 * s1 - s2) / 2;
}
f[i][1] = s[1][i];
w[i][1] = 0;
}
for (int i = 1; i <= n; ++i) {
for (int j = 2;
j <= i && j <= m; ++j) {
for (int k = i - 1;
k >= w[i - 1][j - 1]; --k) {
if (f[i][j] > f[k][j - 1]
+ s[k + 1][i]) {
f[i][j] = f[k][j - 1]
+ s[k + 1][i];
w[i][j] = k;
}
}
// printf("f[%lld][%lld] = %lld\n",
// i, j, f[i][j]);
}
}
print(f[n][m], '\n');
read(n), read(m);
}
return 0;
}
} // namespace XSC062
#undef int
C - Monkey Party
https://vjudge.net/contest/602275#problem/C
HDU3506。
环形石子合并。不妨先回忆一下线性的怎么做,我们打了个区间 DP,式子是 \(f_{i,j}=\min\{f_{i,k}+f_{k+1,j}+s_{i,j}\}\)。
环形的很好整,我们复制一遍序列然后在所有长度为 \(n\) 的区间中找最小的 \(f\) 就好了。
然后套一个四边形不等式就可以了。
需要注意的是本题的 \(n\) 可能等于 \(1\),所以可能会有一些奇怪的细节导致挂分 😭
namespace XSC062 {
const int maxn = 2e3 + 5;
const int inf = 0x3f3f3f3f;
int n, res;
int a[maxn], s[maxn];
int f[maxn][maxn], w[maxn][maxn];
int min(int x, int y) {
return x < y ? x : y;
}
int main() {
while (~scanf("%d", &n)) {
memset(w, 0, sizeof (w));
memset(f, 0x3f, sizeof (f));
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
a[n + i] = a[i];
s[i] = s[i - 1] + a[i];
}
for (int i = n + 1; i <= 2 * n; ++i)
s[i] = s[i - 1] + a[i];
for (int i = 1; i <= 2 * n; ++i)
f[i][i] = 0, w[i][i] = i;
for (int l = 2; l <= n; ++l) {
for (int i = 1;
i <= 2 * n - l + 1; ++i) {
int j = i + l - 1;
for (int k = w[i][j - 1];
k <= w[i + 1][j]
&& k < j; ++k) {
int val = f[i][k] +
f[k + 1][j] +
s[j] - s[i - 1];
if (val < f[i][j]) {
f[i][j] = val;
w[i][j] = k;
}
}
}
}
res = inf;
for (int i = 1; i <= n; ++i)
res = min(res, f[i][i + n - 1]);
printf("%d\n", res);
}
return 0;
}
} // namespace XSC062
D - Division
https://vjudge.net/contest/602275#problem/D
HDU3480。
这个主要问题在 \(n\) 和 \(m\) 都特别大,时间特别卡。排序是不难想的,只要你不和我一样排完序还要线性求区间极差。
然后这个四边形要好好写,不然会 T 飞。
namespace XSC062 {
const int maxn = 1e4 + 5;
const int maxm = 5e3 + 5;
const int inf = 0x3f3f3f3f;
int a[maxn];
int T, n, m, _t;
int f[maxm][maxn], w[maxm][maxn];
int min(int x, int y) {
return x < y ? x : y;
}
int max(int x, int y) {
return x > y ? x : y;
}
int main() {
scanf("%d", &T);
while (T--) {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
std::sort(a + 1, a + n + 1);
for (int i = 1; i <= n; ++i) {
f[1][i] = (a[i] - a[1]) * (a[i] - a[1]);
w[1][i] = 0;
}
for (int i = 2; i <= m; ++i) {
w[i][n + 1] = n;
for (int j = n; j > i; --j) {
f[i][j] = inf;
for (int k = w[i - 1][j];
k <= w[i][j + 1]; ++k) {
if (f[i][j] > f[i - 1][k]
+ (a[j] - a[k + 1])
* (a[j] - a[k + 1])) {
f[i][j] = f[i - 1][k]
+ (a[j] - a[k + 1])
* (a[j] - a[k + 1]);
w[i][j] = k;
}
}
}
}
printf("Case %d: %d\n", ++_t, f[m][n]);
}
return 0;
}
} // namespace XSC062
E - Ciel and Gondolas
https://vjudge.net/contest/602275#problem/E
我觉得我算是发现了四边形不等式板子的规律。
长得差不多的式子,要么区间 DP 要么连续划分,然后加一些挂件。
比如这道题我们可以发现将 \([l,r]\) 划分成一段的代价是以 \((l, l)\) 为左上角,\((r,r)\) 为右下角的二维前缀和。
然后就跟前面一样了。式子都不用变的。
namespace XSC062 {
using namespace fastIO;
const int maxm = 805;
const int maxn = 4e3 + 5;
const int inf = 0x3f3f3f3f;
int n, m;
int a[maxn][maxn];
int f[maxm][maxn], w[maxm][maxn];
int min(int x, int y) {
return x < y ? x : y;
}
int max(int x, int y) {
return x > y ? x : y;
}
int sum(int i, int j) {
return (a[j][j] - a[i - 1][j] -
a[j][i - 1] + a[i - 1][i - 1]) / 2;
}
int main() {
read(n), read(m);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
read(a[i][j]);
a[i][j] += a[i - 1][j] +
a[i][j - 1] - a[i - 1][j - 1];
}
}
for (int i = 1; i <= n; ++i) {
f[1][i] = sum(1, i);
w[1][i] = 0;
}
for (int i = 2; i <= m; ++i) {
w[i][n + 1] = n;
for (int j = n; j > i; --j) {
f[i][j] = inf;
for (int k = w[i - 1][j];
k <= w[i][j + 1]; ++k) {
if (f[i][j] > f[i - 1][k]
+ sum(k + 1, j)) {
f[i][j] = f[i - 1][k]
+ sum(k + 1, j);
w[i][j] = k;
}
}
}
}
print(f[m][n], '\n');
return 0;
}
} // namespace XSC062
—— · EOF · ——
真的什么也不剩啦 😖