01-CF2053-Solution

【思维场!】CF2053解析

今天带来的是 CF2053:Good Bye 2024: 2025 is NEAR 前六题的解析,每道题配备了图片留白,你能坚持到第几关呢?

时间限制均为 2s ,内存限制均为 512 MB 。

A. Tender Carpenter

【题目描述】

定义一个集合 S 是“好的”,当且仅当对于 S 中任意三个数 x,y,z(可以相同),存在三边长为 x,y,z 的非退化三角形。

给出一个数组 a1,a2,an ,要求把 a 分成若干个非空连续子段,使得:对于每个子段,包含其中所有元素的集合是“好的” 。

询问是否存在两种不同的分割方式。(YES or NO)

【数据规模与约定】

1nle200,1ai105

【图片留白】

【个人解析】

第一种方式:每个元素单独一段,一共 n 段。

第二种方式:在 n 段的基础上,合并两个相邻的段,那就需要找到一组下标 (i,i+1) ,满足 max(ai,ai+1)<min(ai,ai+1) .

附上个人 AC 代码:

#include <bits/stdc++.h>
using namespace std;
void solve ()
{
int n;
cin >> n;
vector<int> a(n, 0);
for (int i = 0; i < n; i++) cin >> a[i];
int f = 0;
for (int i = 0; i + 1 < n; i++)
{
int u = max(a[i], a[i + 1]);
int v = min(a[i], a[i + 1]);
if (v * 2 > u) f = 1;
}
if (f) cout << "YES\n";
else cout << "NO\n";
}
int main ()
{
int _;
cin >> _;
while (_--) solve();
return 0;
}

B. Outstanding Impressionist

【题目描述】

对于一个长度为 n 的数组 w1,w2,wn ,我们只知道对于每个 1inwi 满足 liwiri 的限制 。(li,ri) 会给出。

称下标 i 是唯一的,当且仅当可以构造出一个满足限制的 w ,使得对所有 jiwiwj 成立。

请你判断每个 i 是否唯一。

【数据规模与约定】

1n2105,1liri2n

【图片留白】

【个人解析】

记每个下标 i 的选择数量是 [li,ri] 区间的长度。

下标 i 可以分为两类:

  1. li=ri :只有一个选择。

  2. liri:有多个选择。

需要先想到,如果所有下标 i 都是第二类,每个 i 都是唯一的。因为:

  • 对于当前的 i ,选中区间内一个点,那么对于剩下的 j ,选择数减一。但是它们本来就有多个选择,所以一定有得选。

然后推广到一般情况,分别考虑两类下标的策略。

为了方便描述,我们记一个第一类下标 i ,占领了数轴上的 li 这个点。

对于第二类下标 iliri

  • 如果 [li,ri] 区间内的每个点都被第一类下标占领了,那么无论 i 选哪个,一定会让某个第一类下标没得选,因此 i 不独立。
  • 否则,i 选择一个没有被任何第一类下标占领的点即可。所有第一类下标都有得选,剩下的只有第二类下标。

对于第一类下标 ili=ri ,它的选择唯一,对于剩下的 j ,选择数减一。其中:

  • 对于第二类下标,本来就有多个选择,不构成影响;

  • 对于 ljli 的第一类下标,本来唯一的选择就和 i 不同,不构成影响。

  • 对于 lj=li 的第一类下标,选择数变为零。这种情况一旦发生,就说明 i 不是唯一的!

我们在代码中讨论清楚上面的情况即可。

可以用前缀和等技巧优化时间复杂度到 O(n)

#include <bits/stdc++.h>
using namespace std;
void solve ()
{
int n;
cin >> n;
vector<pair<int, int> > a(n);
vector<int> b(2 * n + 1, 0);
for (int i = 0; i < n; i++)
cin >> a[i].first >> a[i].second;
map<int, int> mp;
for (int i = 0; i < n; i++)
{
if (a[i].first != a[i].second) continue;
mp[a[i].first]++;
}
for (int i = 1; i <= 2 * n; i++)
if (!mp[i]) b[i] = 1;
for (int i = 1; i <= 2 * n; i++)
b[i] += b[i - 1];
for (int i = 0; i < n; i++)
{
auto [l, r] = a[i];
if (l == r) {
if (mp[l] == 1) cout << "1";
else cout << "0";
continue;
}
if (b[r] - b[l - 1]) cout << "1";
else cout << "0";
}
cout << "\n";
}
int main ()
{
int _;
cin >> _;
while (_--) solve();
return 0;
}

C. Bewitching Stargazer

【题目描述】

天空中有 n 颗星星,排成一排。有一架望远镜,它用来观察星星。

最初,观测到的星星位于 [1,n] 段,它的幸运值为 0

希望在它观测到的每个星段 [l,r] 中寻找位于中间位置的恒星。因此,使用了下面的递归程序:

  • 首先,它将计算 m=l+r2 .

  • 如果星段的长度(即 rl+1)是偶数,将把它分成两个同样长的星段 [l,m][m+1,r] ,以便进一步观测 .

  • 否则,将把望远镜瞄准 m 星,幸运值增加 m ;随后,如果 lr

    ,将继续观测星段 [l,m1][m+1,r] .

随着观察的进行,不会继续观察任何长度严格小于 k 的线段 [l,r]。在这种情况下,请预测它的最终幸运值。

【数据规模与约定】

1kn2×109

【图片留白】

【个人解析】

O(n) 做法是显然的,暴力递归模拟题意即可。大致代码如下:

void dfs(int l, int r)
{
int mid = (l + r) / 2;
if ((r - l + 1) % 2 == 0)
{
dfs(l, mid);
dfs(mid + 1, r);
}
else
{
ans += mid;
dfs(l, mid - 1);
dfs(mid + 1, r);
}
}

需要想到,每次往右边的递归,是没有必要的。

设当前区间 [l,r] ,中点为 mid 。不妨设区间长度为偶数。

  • 往左区间 [l,mid] 递归,和往右区间 [mid+1,r] 递归,幸运值的增加次数,是一样的,因为它们在数轴上的长度相等,每次取中点,分割,取中点,分割 ... 是完全相同的两个过程。

  • 唯一的区别在于,往右区间,幸运值每次增加,要多加 mid

  • 因此,让程序往左区间递归计算,记录幸运值的增加总量 u ,和增加次数 v

  • 右区间的整体答案可以被描述为 2×u+v×mid

  • 区间 [l,r] 的幸运值增加次数可以被描述为 2×v

  • 记录下这个答案,返回上一层函数即可。

整体时间复杂度 O(logn)

具体看代码。

#include <bits/stdc++.h>
using namespace std;
int n, k;
pair<long long, long long> dfs(int l, int r)
// first: 答案
// second: 提取的数量
{
if (r - l + 1 < k) return {0, 0};
long long u = 0, v = 0;
int mid = (l + r) / 2;
if ((r - l + 1) % 2 == 0)
{
auto ans = dfs(l, mid);
u = ans.first;
v = ans.second;
u += ans.first + 1ll * mid * ans.second;
v = v * 2;
return {u, v};
}
else
{
u = u + mid;
v = v + 1;
auto ans = dfs(l, mid - 1);
u += ans.first;
v += ans.second;
u += ans.first + 1ll * mid * ans.second;
v += ans.second;
return {u, v};
}
}
void solve ()
{
cin >> n >> k;
cout << dfs(1, n).first << "\n";
}
int main ()
{
int _;
cin >> _;
while (_--) solve();
return 0;
}

D. Refined Product Optimality

【题目描述】

  • 给定两个数组 ab,它们都包含 n 个整数。
  • Iris关心的是通过任意重新排列数组 b 后,P=i=1nmin(ai,bi) 的最大值。注意,她只关心 P 的最大值,并不需要实际重新排列 b
  • 将有 q 次修改。每次修改可以用两个整数 ox 表示,其中 o121xn)。若 o=1,则表示 ax 增加 1;若 o=2,则表示 bx 增加 1
  • Iris会向Chris询问 P 的最大值,共有 q+1 次:一次是在任何修改之前,然后是每次修改之后。
  • 由于 P 可能非常大,Chris只需要计算 Pmod998244353

Chris很快解决了这个问题,但他太累了,睡着了。现在,除了感谢Chris,你的任务是编写程序,计算给定输入数据的答案。

【数据规模与约定】

保证 1n,q2×105,1ai,bi5×108

【图片留白】

【个人解析】

首先考虑如何解决不带修改的版本。

询问重新排列数组 b 后,P 的最大值,就等价于重新排列数组 a,b 后,P 的最大值。因为本质上是找一个对应关系,对每个 ai 找到一个 bj 和它对应,同时不能有两个 ai 共用一个 bj

这里需要猜到,对数组 a,b 各自从小都大排序, 就可以让 P 最大。

证明:

  • a1a2,b1b2,一定有:min(a1,b2)×min(a2,b1)min(a1,b1)×(a2,b2)

  • 讨论 [a1,a2][b1,b2] 这两个区间的相对位置就可以证明上面不等式的正确性。为了方便讨论,不妨设 a1b1

    具体的:

    • 如果 [a1,a2] 整体在 [b1,b2] 的左侧,即 a2b1 ,那么min(a1,b2)×min(a2,b1)=a1×a2

      min(a1,b1)×min(a2,b2)=a1×a2

      不等式成立。

    • 如果 [a1,a2][b1,b2] 构成交叉但不覆盖的关系,即 a2b1a2<b2,那么:

      min(a1,b2)×min(a2,b1)=a1×b1

      min(a1,b1)×min(a2,b2)=a1×a2

      后者显然大于前者。不等式成立。

    • 如果 [a1,a2][b1,b2] 构成覆盖的关系,即 a2b2 ,那么:

      min(a1,b2)×min(a2,b1)=a1×b1

      min(a1,b1)×min(a2,b2)=b1×a2

      后者显然大于前者。不等式成立。

因此得证。

现在我们的问题转化为:

  • 每次单点修改时,如何快速的维护 P

如果是一般的修改,其实是没法维护的,因为要维护 a,b 排序后的配对。

需要注意到题目中,只有 +1 这种修改操作。那就有的说了。

额外维护两个 map ,我们记为 map_amap_b 。Key 类型是 int ,Value 类型是 pair<int, int>

  • map_a[u] 存储的信息是:数组 a 从小到大排序后,u 这个值分布的区间左右端点。(都从小到大排序了,u一定是连续出现的)
  • map_b[u] 存储的信息类似,只不过是对数组 b 存的。

额外维护两个数组 pa,pb ,表示数组 a,b 从小到大排序后的状态。

思路的关键是:利用 map_a 定位到 ax 这个值在 pa 中的最后一次出现位置,修改那个位置。

下面讨论让 ax=ax+1 ,如何维护 P 值。

  • map_a 定位到 ax 这个值在排序后的数组 a 中,分布的区间的右端点 r
  • P 除掉 min(par,pbr) 。这里我们计划修改 paax 这个值最后一次出现位置。模数意义下的除法用逆元来做。
  • 让右端点减 1 。如果 ax 这个值在数组 a 中出现的次数已经是 0 了,就删除 map_a 中的这个 Key 。
  • ax=ax+1. par+1=par+1+1. P=P×min(par+1,pbr+1)
  • 如果现在 ax 这个值在 map_a 中,让它的左端点减一。
  • 如果不存在,插入 {ax,r+1} 这个键值对到 map_a 中。

对于 bx=bx+1 的操作,维护方法类似。

#include <bits/stdc++.h>
using namespace std;
const int mod = 998244353;
long long qpow(long long a, long long b)
{
long long ans = 1, base = a;
while (b)
{
if (b & 1) ans = ans * base % mod;
base = base * base % mod;
b /= 2;
}
return ans;
}
void solve ()
{
int n, q;
cin >> n >> q;
vector<int> a(n, 0);
vector<int> b(n, 0);
for (int i = 0; i < n; i++) cin >> a[i];
for (int i = 0; i < n; i++) cin >> b[i];
vector<int> pa(n, 0), pb(n, 0);
for (int i = 0; i < n; i++)
pa[i] = a[i], pb[i] = b[i];
sort(pa.begin(), pa.end());
sort(pb.begin(), pb.end());
long long ans = 1;
for (int i = 0; i < n; i++)
{
ans = ans * min(pa[i], pb[i]) % mod;
}
map<int, pair<int, int> > mpa, mpb;
for (int i = 0; i < n; i++)
{
int j;
for (j = i; j < n; j++)
{
if (pa[j] != pa[i]) break;
}
mpa[pa[i]] = {i, j - 1};
i = j - 1;
}
for (int i = 0; i < n; i++)
{
int j;
for (j = i; j < n; j++)
{
if (pb[j] != pb[i]) break;
}
mpb[pb[i]] = {i, j - 1};
i = j - 1;
}
cout << ans << " ";
while (q--)
{
int p, x;
cin >> p >> x;
x--;
if (p == 1)
{
auto [l, r] = mpa[a[x]];
ans = ans * qpow(min(pa[r], pb[r]), mod - 2) % mod;
r = r - 1;
mpa[a[x]].second--;
if (r < l) mpa.erase(a[x]);
a[x]++;
pa[r + 1]++;
if (mpa.count(a[x]))
{
mpa[a[x]].first--;
ans = ans * min(pa[r + 1], pb[r + 1]) % mod;
}
else
{
mpa[a[x]] = {r + 1, r + 1};
ans = ans * min(pa[r + 1], pb[r + 1]) % mod;
}
}
else
{
auto [l, r] = mpb[b[x]];
ans = ans * qpow(min(pa[r], pb[r]), mod - 2) % mod;
r = r - 1;
mpb[b[x]].second--;
if (r < l) mpb.erase(b[x]);
b[x]++;
pb[r + 1]++;
if (mpb.count(b[x]))
{
mpb[b[x]].first--;
ans = ans * min(pa[r + 1], pb[r + 1]) % mod;
}
else
{
mpb[b[x]] = {r + 1, r + 1};
ans = ans * min(pa[r + 1], pb[r + 1]) % mod;
}
}
cout << ans << " ";
}
cout << "\n";
}
int main ()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int _;
cin >> _;
while (_--) solve();
return 0;
}

E. Resourceful Caterpillar Sequence

【题目描述】

有一棵由 n 个顶点组成的树。让一对整数 (p,q)1p,qnpq )表示一条毛毛虫:它的头部位于顶点 p ,尾部位于顶点 q ,它支配着从 pq 的简单路径上的所有顶点(包括 pq )。 (p,q) 的毛毛虫序列被定义为仅由简单路径上的顶点组成的序列,按照到 p 的距离升序排序。

诺拉和阿伦轮流移动毛毛虫,诺拉先移动。两位玩家都将使用各自的最优策略:

  • 他们会让自己赢;
  • 但是,如果不可能,他们就会努力阻止对方获胜(因此,游戏将以平局结束)。

轮到诺拉时,她必须选择一个与顶点 p 相邻的顶点 u ,该顶点不受毛毛虫支配,并将其中的所有顶点向顶点 u 移动一条边。轮到阿伦时,他必须选择一个与顶点 q 相邻的顶点 v ,该顶点不受毛毛虫的支配,并将其中的所有顶点向顶点 v 移动一条边。注意两位棋手允许的移动是不同的。

每当 p 是叶子 时,诺拉获胜 。每当 q 是叶子时,阿伦获胜。如果最初 pq 都是叶子,或者在 10100 个回合之后游戏还没有结束,那么结果就是平局。

请计算 (p,q)1p,qnpq 的整数对的数目,如果毛毛虫最初是 (p,q) ,那么阿伦获胜。

换句话说:假设当前的毛毛虫序列是 c1,c2,,ck ,那么移动之后,新的毛毛虫序列就变成了 d(u,c1),d(u,c2),,d(u,ck) 。这里, d(x,y) 是从 yx 的简单路径上的下一个顶点。

在一棵树中,当且仅当一个顶点的阶数为 1 时,它才被称为树叶。

因此,当游戏还没有结束时,诺拉绝不会不选择顶点 u 。阿伦也是如此。

【数据规模与约定】

2n2×105

【图片留白】

【个人解析】

把树上的点分为三类:

  • A类点:本身是叶子。
  • B类点:本身不是叶子,但和叶子相邻。
  • C类点:既不是 A 类,也不是 B 类。

容易发现 ABC 这三类点是没有交集的。

阿伦获胜的方案也可以分为下面两类来统计:

  • q 是 A 类点,p 是 BC 类点。这里说人话就是 q 是叶子,p 不是叶子,算下叶子的数量就可以算出方案数。

  • p 是 C 类点,但是 p(p,q) 路径以外的任何一个点拉动毛毛虫,q 都会被拉到 B 类点。那已知 p 拉动一步后没到叶子,q 被拉到了 B 类点,q 操作一次就可以赢。

    这里可以枚举 q ,然后在此基础上枚举和 q 相邻的点 v ,如果 v 是 B 类点,那么以 q 为根,子树 v 中的 C 类点都是合法的 p ,统计数量即可。

    具体维护手法见代码。

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
vector<int> g[N];
int sz[N], fa[N], b[N];
void dfs (int u)
{
if (g[u].size() != 1 && !b[u]) sz[u] = 1;
for (int v : g[u])
{
if (v == fa[u]) continue;
fa[v] = u;
dfs(v);
sz[u] += sz[v];
}
}
void solve ()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
g[i].clear();
b[i] = 0;
sz[i] = 0;
}
for (int i = 1; i < n; i++)
{
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
int cnt = 0;
for (int i = 1; i <= n; i++)
{
if (g[i].size() == 1)
{
cnt++;
continue;
}
int f = 0;
for (int v : g[i])
{
if (g[v].size() == 1) f = 1;
}
if (f) b[i] = 1;
}
dfs(1);
long long ans = 1ll * cnt * (n - cnt);
int sum = 0;
for (int i = 1; i <= n; i++)
{
if (g[i].size() != 1 && !b[i]) sum++;
}
for (int i = 1; i <= n; i++)
{
if (g[i].size() == 1) continue;
for (int v : g[i])
{
if (b[v])
{
if (v != fa[i])
{
ans += sz[v];
}
else
{
ans += sum - sz[i];
}
}
}
}
cout << ans << "\n";
}
int main ()
{
int _;
cin >> _;
while (_--) solve();
}

F. Earnest Matrix Complement

【题目描述】

Aquawave 有一个大小为 n×m 的矩阵 A ,其元素只能是范围在 [1,k] (含)以内的整数。在矩阵中,有些单元格已经填满了整数,而其余单元格目前还没有填满,用 1 表示。

您将填入 A 中所有未填写的位置。之后,让 cu,i 成为元素 u 在第 i 行中出现的次数。Aquawave 将矩阵的美定义为

u=1ki=1n1cu,icu,i+1.

你必须找到填空后最美的 A

输出式子的最大值即可。

【数据规模与约定】

2n,m2×105,n×m6×105,1knm

【图片留白】

【提示1】

  • 如果保证每一行只有一个 -1 ,你有什么填空的策略吗?

【图片留白】

【个人解析】

为了方便描述,记第 i-1 的数量为 ci ,记数字 j 在第 i 行的出现次数为 di,j

首先需要想到,一行中的所有 -1 都填相同的数,一定是这一行的其中一个解。

证明:

  • 假设第 i1 行和第 i+1 行都已经填完了,第 i 行是最后处理的,那么在第 i 行中的一个 -1 处填数字 j ,对答案的贡献是 di1,j+di+1,j ,和这个 -1 的位置没有关系。
  • 因此完全可以全部填同一个 j,使得 di1,j+di+1,j 最大化。

然后可以推广出一个 O(n2m) 的做法,具体如下:

  • 先忽略 -1 ,计算出答案的一部分,也就是 i=1kj=1n1dj,i×dj+1,i

    这一步可以 O(nm) 计算。

  • 设计 dp 数组,dpi,j 表示仅考虑前 i 行,第 i+1 行不存在,在第 i 行填数字 j 的答案。

  • 基于 dpi1k 个状态,如何计算 dpi,j(1jk) 呢?有如下转移:

  • 上一行选的是 w,其中 wj,有

    dpi,j=max(dpi,j,dpi1,w+ci1×di,w+ci×di1,j) .

    其中,dpi1,w+ci1×di,w 这一部分,可以直接取所有 w 中的最大值,可以提前 O(k) 计算好。

    因此这一步转移的复杂度是 O(1) 的。

  • 上一行选的也是 j ,有

    dpi,j=max(dpi,j,dpi1,j+ci×ci1+ci×di1,j+ci1×di,j)

    这一步转移也是 O(1) 的。

所有的转移都是 O(1) 的,那么复杂度的瓶颈是 O(k) 枚举所有的 j

k 的上界是 nm ,因此整体复杂度 O(n2m)

可以写出如下代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
void solve ()
{
int n, m, k;
cin >> n >> m >> k;
int a[n + 1][m + 1] = {0};
long long dp[n + 1][k + 1] = {0};
long long c[n + 1] = {0};
long long d[n + 1][k + 1] = {0};
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) cin >> a[i][j];
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) if (a[i][j] == -1) c[i]++;
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) if (a[i][j] != -1) d[i][a[i][j]]++;
long long ans = 0;
for (int i = 2; i <= n; i++) for (int j = 1; j <= m; j++) if (a[i][j] != -1) ans += d[i - 1][a[i][j]];
for (int i = 2; i <= n; i++)
{
long long Max = 0;
for (int w = 1; w <= k; w++)
{
Max = max(Max, dp[i - 1][w] + c[i - 1] * d[i][w]);
}
for (int j = 1; j <= k; j++)
{
dp[i][j] = max(dp[i][j], Max + c[i] * d[i - 1][j]);
dp[i][j] = max(dp[i][j], dp[i - 1][j] + c[i] * c[i - 1] + c[i] * d[i - 1][j] + c[i - 1] * d[i][j]);
}
}
long long t = 0;
for (int i = 1; i <= k; i++) t = max(t, dp[n][i]);
cout << ans + t << "\n";
}
int main ()
{
int _;
cin >> _;
while (_--) solve();
}

现在设计 O(nm) 的做法。

观察刚刚的 dp 转移过程,可以做出如下优化:

long long Max = 0;
for (int w = 1; w <= k; w++)
{
Max = max(Max, dp[i - 1][w] + c[i - 1] * d[i][w]);
}

对于这部分求解,可以在 for 循环外定义一个 Max,用来存储 dpi1 中的状态最大值,然后只需要更新 di,w 不为 0O(m) 个状态就可以了。

long long Max = 0;
for (int i = 2; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
int w = a[i][j];
if (w == -1) continue;
Max = max(Max, dp[w] + c[i - 1] * d[i][w]);
}
...// 记得计算当前这轮 dp 状态的时候更新 Max
dp[i][j] = max(dp[i][j], Max + c[i] * d[i - 1][j]);
dp[i][j] = max(dp[i][j], dp[i - 1][j] + c[i] * c[i - 1] + c[i] * d[i - 1][j] + c[i - 1] * d[i][j]);

对于这两部分转移,可以重新描述为:

  • 先让所有 dpi1,j 加上 ci×ci1+ci×di1,j+ci1×di,j ,作为 dpi,j
  • dpi,j=max(dpi,j,Max+ci×di1,j)

那么 ci×ci1 ,对所有 j 都是固定的,可以设计一个类似”懒惰标记“的变量 lz ,每次让 lz+ci×ci1 。(这里需要提前接触过线段树中的”懒惰标记“思想)

ci×di1,j ,这一部分只有在上一行出现过的 j ,才需要考虑,可以 O(m) 计算。

ci1×di,j ,这一部分只有在当前行出现过的 j,才需要考虑,可以 O(m) 计算。

Max+ci×di1,j 取较大值,可以分为两类:

  • 如果 j 在上一行没有出现过,那等价于和 Max 取较大值。

    这也可以用一个全局懒惰标记实现。

  • 如果 j 在上一行出现过,那 O(m) 暴力枚举这样的 j ,更新 dp 状态。

下面是我的 AC 代码,其中对懒惰标记、Max 变量的更新还有很多细节。

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
void solve ()
{
int n, m, k;
cin >> n >> m >> k;
int a[n + 1][m + 1] = {0};
long long dp[k + 1] = {0};
long long c[n + 1] = {0};
map<int, int> d[n + 1];
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) cin >> a[i][j];
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) if (a[i][j] == -1) c[i]++;
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) if (a[i][j] != -1) d[i][a[i][j]]++;
long long ans = 0;
for (int i = 2; i <= n; i++) for (int j = 1; j <= m; j++) if (a[i][j] != -1 && d[i - 1].count(a[i][j])) ans += d[i - 1][a[i][j]];
long long lz = 0, lz1 = -1e18;
long long Max = 0;
for (int i = 2; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
int w = a[i][j];
if (w == -1) continue;
dp[w] = max(dp[w], lz1);
Max = max(Max, dp[w] + c[i - 1] * d[i][w]);
}
long long t = Max;
lz += c[i] * c[i - 1];
set<int> st;
for (int j = 1; j <= m; j++)
{
int w = a[i - 1][j];
if (w == -1) continue;
if (st.count(w)) continue;
dp[w] = max(dp[w], lz1);
dp[w] += c[i] * d[i - 1][w];
t = max(t, dp[w]);
st.insert(w);
}
st.clear();
for (int j = 1; j <= m; j++)
{
int w = a[i][j];
if (w == -1) continue;
if (st.count(w)) continue;
dp[w] += c[i - 1] * d[i][w];
t = max(t, dp[w]);
st.insert(w);
}
for (int j = 1; j <= m; j++)
{
int w = a[i - 1][j];
if (w == -1) continue;
dp[w] = max(dp[w], Max + c[i] * d[i - 1][w] - c[i] * c[i - 1]);
t = max(t, dp[w]);
}
lz1 = max(lz1, Max - c[i] * c[i - 1]);
Max = t;
}
long long t = 0;
for (int i = 1; i <= k; i++) t = max(t, dp[i] + lz);
cout << ans + t << "\n";
}
int main ()
{
int _;
cin >> _;
while (_--) solve();
}
posted @   zlc0405  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
点击右上角即可分享
微信分享提示