Codeforces Round 993 div4 个人题解(A~H)

Codeforces Round 993 div4 个人题解(A~H)

Dashboard - Codeforces Round 993 (Div. 4) - Codeforces

A. Easy Problem

Cube给定一个整数 \(n\)。她想知道有多少个正整数的有序对 \((a,b)\) 使得 \(a=n-b\)。由于Cube的数学水平有限,请帮助她!

输入

第一行包含一个整数 \(t\)\(1 \leq t \leq 99\))— 测试用例的数量。

每个测试用例的唯一一行包含一个整数 \(n\)\(2 \leq n \leq 100\))。

输出

对于每个测试用例,在新的一行输出有序对 \((a,b)\) 的数量。

样例

输入

3
2
4
6

输出

1
3
5

提示

在第一个测试用例中,唯一有效的有序对是 \((a,b)=(1,1)\)

在第二个测试用例中,有三个有效的有序对 \((a,b)\) 分别是 \((3,1),(2,2),(1,3)\)

解题思路

对于 \(n\) 为奇数的时候,我们一定最多可以找出 \(n-1\) 个有序对,对于 \(n\) 为偶数的时候,由于 \((n/2,n/2)\) 不能进行互换,所以也会少一对。

实现代码

#include <bits/stdc++.h>

using namespace std;

using ll = long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
using vi = vector<int>;
using vl = vector<ll>;

#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fLL
void solve()
{
    ll n;
    cin >> n;
    cout << n - 1 << "\n";
}

int main()
{
    ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
    // freopen("test.in", "r", stdin);
    // freopen("test.out", "w", stdout);
    int _ = 1;
    std::cin >> _;
    while (_--)
    {
        solve();
    }
    return 0;
}

/*######################################END######################################*/
// 链接:

B. Normal Problem

在商店的玻璃窗上画着一个只由字符 'p'、'q' 和 'w' 组成的字符串。Ship在商店外面走过,站在玻璃窗前,观察到了字符串 \(a\)。然后,Ship走进商店,直接看着同一扇玻璃窗,观察到了字符串 \(b\)

Ship给你字符串 \(a\)。你的任务是找到并输出字符串 \(b\)

输入

第一行包含一个整数 \(t\)\(1 \leq t \leq 100\))— 测试用例的数量。

每个测试用例的唯一一行包含一个字符串 \(a\)\(1 \leq |a| \leq 100\))— Ship从商店外观察到的字符串。保证字符串 \(a\) 只包含字符 'p'、'q' 和 'w'。

输出

对于每个测试用例,在新的一行输出字符串 \(b\),即Ship从商店内观察到的字符串。

样例

输入

5
qwq
ppppp
pppwwwqqq
wqpqwpqwwqp
pqpqpqpq

输出

pwp
qqqqq
pppwwwqqq
qpwwpqwpqpw
pqpqpqpq

解题思路

按照题目要求,将字符串逆序之后做个转换即可。

实现代码

#include <bits/stdc++.h>

using namespace std;

using ll = long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
using vi = vector<int>;
using vl = vector<ll>;

#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fLL
void solve()
{
    string s;
    cin >> s;
    string ans;
    for (auto &c : s)
    {
        if (c == 'q')
            ans += 'p';
        else if (c == 'p')
            ans += 'q';
        else
            ans += c;
    }
    reverse(ans.begin(), ans.end());
    cout << ans << "\n";
}

int main()
{
    ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
    // freopen("test.in", "r", stdin);
    // freopen("test.out", "w", stdout);
    int _ = 1;
    std::cin >> _;
    while (_--)
    {
        solve();
    }
    return 0;
}

/*######################################END######################################*/
// 链接:

C. Hard Problem

Ball是Paperfold大学的老师。他的教室座位分为2排,每排有\(m\)个座位。

Ball正在教\(a+b+c\)只猴子,他希望尽可能多地给猴子分配座位。Ball知道\(a\)只猴子只想坐在第一排,\(b\)只猴子只想坐在第二排,而\(c\)只猴子没有偏好。每个座位只能坐一只猴子,且必须遵循每只猴子的偏好。

那么,Ball最多可以坐多少只猴子?

输入

第一行包含一个整数\(t\)\(1 \leq t \leq 10^4\))— 测试用例的数量。

每个测试用例包含四个整数\(m\)\(a\)\(b\)\(c\)\(1 \leq m, a, b, c \leq 10^8\))。

输出

对于每个测试用例,输出你可以坐下的最多猴子的数量。

样例

输入

5
10 5 5 10
3 6 1 1
15 14 12 4
1 1 1 1
420 6 9 69

输出

20
5
30
2
84

提示

在第二个测试用例中,6只猴子想坐在前排,但只有3个座位可用。没有偏好的猴子和想坐在第二排的猴子可以一起坐在第二排。因此,答案是3+2=5。

解题思路

算出按照偏好安排完猴子的剩余座位数 \(r\) ,把 \(r\)\(c\)\(\min\) 加上之前安排的猴子数即可。

实现代码

#include <bits/stdc++.h>

using namespace std;

using ll = long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
using vi = vector<int>;
using vl = vector<ll>;

#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fLL
void solve()
{
    ll m, a, b, c;
    cin >> m >> a >> b >> c;
    ll ans = 0;
    ll ra = max(0ll, m - a);
    ll rb = max(0ll, m - b);
    cout << min(m, a) + min(m, b) + min(c, ra + rb) << "\n";
}

int main()
{
    ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
    // freopen("test.in", "r", stdin);
    // freopen("test.out", "w", stdout);
    int _ = 1;
    std::cin >> _;
    while (_--)
    {
        solve();
    }
    return 0;
}

/*######################################END######################################*/
// 链接:

D. Harder Problem

给定一个正整数序列,如果一个正整数在序列中出现的次数是任何正整数出现次数的最大值,则称该正整数为序列的众数。例如,序列 [2,2,3] 的众数是 2。对于序列 [9,9,8,8,7,7],9、8 或 7 都可以被视为众数。

你给 UFO 提供了一个长度为 \(n\) 的数组 \(a\)。为了感谢你,UFO 决定构造另一个长度为 \(n\) 的数组 \(b\),使得对于所有 \(1 \leq i \leq n\)\(a_i\) 是序列 \([b_1,b_2,…,b_i]\) 的众数。

然而,UFO 不知道如何构造数组 \(b\),所以你必须帮助她。注意,对于所有 \(1 \leq i \leq n\),必须满足 \(1 \leq b_i \leq n\)

输入

第一行包含整数 \(t\)\(1 \leq t \leq 10^4\))— 测试用例的数量。

每个测试用例的第一行包含一个整数 \(n\)\(1 \leq n \leq 2 \cdot 10^5\))— 数组 \(a\) 的长度。

接下来的每行包含 \(n\) 个整数 \(a_1, a_2, \ldots, a_n\)\(1 \leq a_i \leq n\))。

保证所有测试用例中 \(n\) 的总和不超过 \(2 \cdot 10^5\)

输出

对于每个测试用例,在新的一行输出 \(n\) 个数字 \(b_1, b_2, \ldots, b_n\)\(1 \leq b_i \leq n\))。可以证明数组 \(b\) 总是可以构造。如果有多个可能的数组,你可以输出任意一个。

样例

输入

4
2
1 2
4
1 1 1 2
8
4 5 5 5 1 1 2 1
10
1 1 2 2 1 1 3 3 1 1

输出

1 2
1 1 2 2
4 5 5 1 1 2 2 3
1 8 2 2 1 3 3 9 1 1

提示

让我们验证样例输出在测试用例 2 中的正确性。

\(i=1\) 时,1 是 \([1]\) 的唯一可能众数。
\(i=2\) 时,1 是 \([1,1]\) 的唯一可能众数。
\(i=3\) 时,1 是 \([1,1,2]\) 的唯一可能众数。
\(i=4\) 时,1 或 2 都是 \([1,1,2,2]\) 的众数。由于 \(a_i=2\),这个数组是有效的。

解题思路

考虑将 \(1\sim n\) 的所有数填入 \(b\) 数组中,这样所有数都是众数。

观察发现,如果一个在之前的位置出现过,那么我们就可以在 \(a\) 数组中该位置设置成这个数。

因此,枚举 \(a\) 数组,如果 \(a_i\) 之前已经填入过 \(b\) 数组中,随便找一个没填过的数填入即可,否则就填入 \(a_i\)

代码中使用了set来实现。

实现代码

#include <bits/stdc++.h>

using namespace std;

using ll = long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
using vi = vector<int>;
using vl = vector<ll>;

#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fLL
void solve()
{
    int n;
    cin >> n;
    vi a(n + 1);
    vi b(n + 1);
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    set<int> st;
    for (int i = 1; i <= n; i++)
    {
        st.insert(i);
    }
    for (int i = 1; i <= n; i++)
    {
        if (st.find(a[i]) == st.end())
        {
            cout << *st.begin() << " ";
            st.erase(st.begin());
        }
        else
        {
            st.erase(a[i]);
            cout << a[i] << " ";
        }
    }
    cout << "\n";
}

int main()
{
    ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
    // freopen("test.in", "r", stdin);
    // freopen("test.out", "w", stdout);
    int _ = 1;
    std::cin >> _;
    while (_--)
    {
        solve();
    }
    return 0;
}

/*######################################END######################################*/
// 链接:

E. Insane Problem

Wave 给定了五个整数 \(k\)\(l_1\)\(r_1\)\(l_2\)\(r_2\)。Wave 希望你帮助她计算满足以下所有条件的有序对 \((x,y)\) 的数量:

  • \(l_1 \leq x \leq r_1\)
  • \(l_2 \leq y \leq r_2\)
  • 存在一个非负整数 \(n\) 使得 \(\frac{y}{x} = k^n\)

输入

第一行包含一个整数 \(t\)\(1 \leq t \leq 10^4\))— 测试用例的数量。

每个测试用例的唯一一行包含五个整数 \(k\)\(l_1\)\(r_1\)\(l_2\)\(r_2\)\(2 \leq k \leq 10^9\)\(1 \leq l_1 \leq r_1 \leq 10^9\)\(1 \leq l_2 \leq r_2 \leq 10^9\))。

输出

对于每个测试用例,在新的一行输出匹配的有序对 \((x,y)\) 的数量。

样例

输入

5
2 2 6 2 12
2 1 1000000000 1 1000000000
3 5 7 15 63
1000000000 1 5 6 1000000000
15 17 78 2596 20914861

输出

12
1999999987
6
1
197

提示

在第三个测试用例中,匹配的有序对如下:

  • (5,15)
  • (5,45)
  • (6,18)
  • (6,54)
  • (7,21)
  • (7,63)

在第四个测试用例中,唯一有效的有序对是 (1,1000000000)。

解题思路

考虑到 \(\max(k^n) \lt \frac{r_2}{l_1}\le 10^9\) ,因此我们可以枚举 \(n\) ,最多不会超过 \(log_k(\frac{r_2}{l_1})\) 次,极限情况下约为 \(30\) 次。

由于 $\frac{y}{x}=k^n \Rightarrow \frac{y}{k^n} =x $,因此我们可以将 \(l_2\)\(r_2\) 除以 \(k^n\) 得到 \([L,R]\) ,与 \([l_1,l_2]\) 做一个交集,区间长度即为当前数对的数量。

注意,\(R\) 需要向下取整,\(L\)需要向上取整。

实现代码

#include <bits/stdc++.h>

using namespace std;

using ll = long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
using vi = vector<int>;
using vl = vector<ll>;

#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fLL

void solve()
{
    ll k, l1, r1, l2, r2;
    cin >> k >> l1 >> r1 >> l2 >> r2;
    ll mx = r2 / l1;
    ll ans = 0;
    ll now = 1;
    while (now <= mx)
    {
        ll L = (l2 + now - 1) / now;
        ll R = r2 / now;
        ll res = min(R, r1) - max(L, l1) + 1;
        ans += max(res, 0ll);
        now *= k;
    }
    cout << ans << "\n";
}

int main()
{
    ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
    // freopen("test.in", "r", stdin);
    // freopen("test.out", "w", stdout);
    int _ = 1;
    std::cin >> _;
    while (_--)
    {
        solve();
    }
    return 0;
}

/*######################################END######################################*/
// 链接:

F. Easy Demon Problem

对于任意网格,Robot 将其美感定义为网格中元素的总和。

Robot 给你一个长度为 \(n\) 的数组 \(a\) 和一个长度为 \(m\) 的数组 \(b\)。你构造了一个 \(n\)\(m\) 的网格 \(M\),使得对于所有 \(1 \leq i \leq n\)\(1 \leq j \leq m\),有 \(M_{i,j} = a_i \cdot b_j\)

然后,Robot 会给出 \(q\) 个查询,每个查询都包含一个整数 \(x\)。对于每个查询,请判断是否可以精确地执行一次下面的操作,从而使 \(M\) 的美感为 \(x\)

  • 选择整数 \(r\)\(c\),使得 \(1 \leq r \leq n\)\(1 \leq c \leq m\)
  • \(M_{i,j}\) 设为 0,对于所有有序对 \((i,j)\),使得 \(i = r\)\(j = c\),或两者都是。

请注意,查询是不持久的,这意味着在查询过程中你不会将任何元素设置为 0,你只需输出是否可以找到 \(r\)\(c\) 使得执行上述操作后网格的美感为 \(x\)。此外,即使原始网格的美感已经是 \(x\),你也必须为每个查询执行上述操作。

输入

第一行包含三个整数 \(n\)\(m\)\(q\)\(1 \leq n, m \leq 2 \cdot 10^5\)\(1 \leq q \leq 5 \cdot 10^4\))— 数组 \(a\) 的长度、数组 \(b\) 的长度和查询的数量。

第二行包含 \(n\) 个整数 \(a_1, a_2, \ldots, a_n\)\(0 \leq |a_i| \leq n\))。

第三行包含 \(m\) 个整数 \(b_1, b_2, \ldots, b_m\)\(0 \leq |b_i| \leq m\))。

接下来的 \(q\) 行每行包含一个整数 \(x\)\(1 \leq |x| \leq 2 \cdot 10^5\)),表示你希望通过将某一行和某一列的所有元素设为 0 来达到的网格美感。

输出

对于每个测试用例,如果存在一种方法可以执行上述操作使得美感为 \(x\),输出 "YES"(不带引号),否则输出 "NO"(不带引号)。

你可以以任意大小写输出 "YES" 和 "NO"(例如,字符串 "yES"、"yes" 和 "Yes" 都会被视为正面回答)。

样例

输入

3 3 6
-2 3 -3
-2 2 -1
-1
1
-2
2
-3
3

输出

NO
YES
NO
NO
YES
NO

输入

5 5 6
1 -2 3 0 0
0 -2 5 0 -3
4
-3
5
2
-1
2

输出

YES
YES
YES
YES
NO
YES

提示

在第二个例子中,网格为:

0 -2 5 0 -3
0 4 -10 0 6
0 -6 15 0 -9
0 0 0 0 0
0 0 0 0 0

通过选择 \(r=4\)\(c=2\) 执行操作后,得到的网格为:

0 0 5 0 -3
0 0 -10 0 6
0 0 15 0 -9
0 0 0 0 0
0 0 0 0 0

其美感为 4。因此,我们输出 YES。

在第二个查询中,选择 \(r=3\)\(c=5\) 创建了一个美感为 -3 的网格。

在第三个查询中,选择 \(r=3\)\(c=3\) 创建了一个美感为 5 的网格。

解题思路

我们有两个数组 $ a $ 和 $ b $,构成一个 $ n \times m $ 的矩阵 $ M $,其中每个元素由 $ M[i][j] = a[i] \cdot b[j] $ 生成。

我们定义矩阵的美感 $ B $ 为矩阵中所有元素的总和。根据矩阵的定义,矩阵的美感 $ B $ 可以表示为:

\[B = \sum_{i=1}^{n} \sum_{j=1}^{m} M[i][j] = \sum_{i=1}^{n} \sum_{j=1}^{m} (a[i] \cdot b[j]) \]

我们可以将其重写为:

\[B = \sum_{i=1}^{n} a[i] \cdot \sum_{j=1}^{m} b[j] = \text{SumA} \cdot \text{SumB} \]

其中:

$ \text{SumA} = \sum_{i=1}^{n} a[i] $

$ \text{SumB} = \sum_{j=1}^{m} b[j] $

如果我们选择一个行 $ r $ 和一个列 $ c $ 进行操作,会对美感产生如下影响:

去掉行 $ r $:美感减少 $ b[j] \cdot a[r] $(所有列的贡献)。

去掉列 $ c $:美感减少 $ a[i] \cdot b[c] $(所有行的贡献)。

交点重复计算:交点 $ M[r][c] $ 被重复减去,因此需要加回 $ a[r] \cdot b[c] $。

因此,操作后的美感 $ X $ 可以表示为:

\[X = B - (a[r] \cdot \text{SumB} + b[c] \cdot \text{SumA} - a[r] \cdot b[c]) \]

经过整理,我们得到:

\[X = \text{SumA} \cdot \text{SumB} - b[c] \cdot \text{SumA} - a[r] \cdot \text{SumB} + a[r] \cdot b[c] \]

因式分解可得

\[X = (\text{SumB} - b[c]) \cdot (\text{SumA} - a[r]) \]

因此新的美感 $ X $ 是两个减去某一行和某一列后的和的乘积。

所以我们只需要判断是否存在 $ r $ 和 $ c $ 使得:\(X = (\text{SumB} - b[c]) \cdot (\text{SumA} - a[r]) = x\)
设 $d_{a_r} = \text{SumA} - a[r] $ , $ d_{b_c} = \text{SumB} - b[c] $,我们可以开一个 \(\text{vis}\) 数组记录每一个 \(d_{a_r}\)\(d_{b_c}\)

由于 \(|X|\le 2\times10^5\),因此我们可以枚举所有可能的 \(d_a\)\(d_b\) ,时间复杂度为\(O(|X|ln|X|)\)

实现代码

#include <bits/stdc++.h>

using namespace std;

using ll = long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
using vi = vector<int>;
using vl = vector<ll>;

#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fLL

const int N = 2e5 + 5;

bool visA[2 * N + 5];
bool visB[2 * N + 5];

bool ans[2 * N + 5];
void solve()
{
    int n, m, q;
    cin >> n >> m >> q;
    vl a(n);
    ll sumA = 0;
    for (int i = 0; i < n; i++)
    {
        cin >> a[i];
        sumA += a[i];
    }
    vl b(m);
    ll sumB = 0;
    for (int i = 0; i < m; i++)
    {
        cin >> b[i];
        sumB += b[i];
    }
    for (int i = 0; i < n; i++)
    {
        ll d = sumA - a[i];
        if (abs(d) < N)
            visA[d + N] = 1;
    }
    for (int i = 0; i < m; i++)
    {
        ll d = sumB - b[i];
        if (abs(d) < N)
            visB[d + N] = 1;
    }
    for (int i = 1; i < N; i++)
    {
        for (int j = 1; j * i < N; j++)
        {
            ans[N + i * j] |= visA[N + i] && visB[N + j];
            ans[N + i * j] |= visA[N - i] && visB[N - j];
            ans[N - i * j] |= visA[N + i] && visB[N - j];
            ans[N - i * j] |= visA[N - i] && visB[N + j];
        }
    }
    while (q--)
    {
        int x;
        cin >> x;
        x += N;
        if (ans[x])
            cout << "YES\n";
        else
            cout << "NO\n";
    }
}

int main()
{
    ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
    // freopen("test.in", "r", stdin);
    // freopen("test.out", "w", stdout);
    int _ = 1;
    // std::cin >> _;
    while (_--)
    {
        solve();
    }
    return 0;
}

/*######################################END######################################*/
// 链接:

G1. Medium Demon Problem (easy version)

一组 \(n\) 只蜘蛛聚在一起交换毛绒玩具。最开始,每只蜘蛛都有 1 个毛绒玩具。每年,如果蜘蛛 \(i\) 至少有一个毛绒玩具,他将把一个毛绒玩具给蜘蛛 \(r_i\)。否则,他将什么也不做。请注意,所有毛绒玩具的转移同时发生。在这个版本中,如果任何蜘蛛在任何时刻有超过 1 个毛绒玩具,他们将把所有多余的毛绒玩具扔掉,只留一个。

当前年份的过程是稳定的,当且仅当每只蜘蛛在当前年份交换之前的毛绒玩具数量与前一年交换之前的数量相同。请注意,第一年永远不会稳定。

找出过程变得稳定的第一年。

输入

第一行包含一个整数 \(t\)\(1 \leq t \leq 10^4\)) — 测试用例的数量。

每个测试用例的第一行包含一个整数 \(n\)\(2 \leq n \leq 2 \cdot 10^5\)) — 蜘蛛的数量。

接下来的行包含 \(n\) 个整数 \(r_1, r_2, \ldots, r_n\)\(1 \leq r_i \leq n\)\(r_i \neq i\)) — 每只蜘蛛的毛绒玩具接收者。

保证所有测试用例中 \(n\) 的总和不超过 \(2 \cdot 10^5\)

输出

对于每个测试用例,在新的一行输出一个整数,表示过程变得稳定的第一年。

样例

输入

5
2
2 1
5
2 3 4 5 1
5
2 1 4 2 3
5
4 1 1 5 4
10
4 3 9 1 6 7 9 10 10 3

输出

2
2
5
4
5

提示

对于第二个测试用例:

在第 1 年,所有蜘蛛的毛绒玩具数量为:[1, 1, 1, 1, 1]。然后进行第 1 年的交换。

在第 2 年,所有蜘蛛的毛绒玩具数量为:[1, 1, 1, 1, 1]。由于这个数组与前一年相同,因此这一年是稳定的。

对于第三个测试用例:

在第 1 年,所有蜘蛛的毛绒玩具数量为:[1, 1, 1, 1, 1]。然后进行第 1 年的交换。

在第 2 年,所有蜘蛛的毛绒玩具数量为:[1, 1, 1, 1, 0]。然后进行第 2 年的交换。注意,虽然有两只蜘蛛给了蜘蛛 2 毛绒玩具,但蜘蛛 2 只能保留一个。

在第 3 年,所有蜘蛛的毛绒玩具数量为:[1, 1, 0, 1, 0]。然后进行第 3 年的交换。

在第 4 年,所有蜘蛛的毛绒玩具数量为:[1, 1, 0, 0, 0]。然后进行第 4 年的交换。

在第 5 年,所有蜘蛛的毛绒玩具数量为:[1, 1, 0, 0, 0]。由于这个数组与前一年相同,因此这一年是稳定的。

解题思路

观察发现,对于一个环上的所有点,其毛绒玩具数因为一直循环传递,且最大持有数为 \(1\) ,外界毛绒玩具输入会被抛弃,所以其数量一定恒定为 \(1\) 。因此,最后稳定下来的情况一定是存在诺干个环上,环上每个节点都有一个毛绒玩具。

由于环外毛绒玩具输入环中会被抛弃,因此稳定下来的年份一定是离环最远的节点到环的距离+2(+2是需要留两年保持相同)。

因此,我们可以使用tarjan来对环进行缩点,然后反向建边,deg为0的点即为环。

从环dfs寻找最远点的距离即可。

实现代码

#include <bits/stdc++.h>

using namespace std;

using ll = long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
using vi = vector<int>;
using vl = vector<ll>;

#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fLL
#include <bits/stdc++.h>

using namespace std;

// 强连通分量类
struct SCC
{
    int n;                     // 图的节点数
    vector<vector<int>> adj;   // 邻接表表示的图
    vector<int> stk;           // 存储DFS路径的栈
    vector<int> dfn, low, bel; // dfn:节点的访问时间,low:节点的最小可达时间,bel:每个节点所属强连通分量的编号
    int cur, cnt;              // cur:当前时间戳,cnt:强连通分量的数量

    // 默认构造函数
    SCC() {}

    // 带参数的构造函数,初始化图的节点数
    SCC(int n)
    {
        init(n);
    }

    // 初始化函数
    void init(int n)
    {
        this->n = n;       // 设置节点数
        adj.assign(n, {}); // 初始化邻接表
        dfn.assign(n, -1); // 初始化访问时间为-1,表示未访问
        low.resize(n);     // 初始化low数组
        bel.assign(n, -1); // 初始化bel数组,表示每个节点的强连通分量编号
        stk.clear();       // 清空栈
        cur = cnt = 0;     // 当前时间戳和强连通分量计数器初始化为0
    }

    // 添加边的函数
    void addEdge(int u, int v)
    {
        adj[u].push_back(v); // 在邻接表中添加边 u -> v
    }

    // 深度优先搜索函数
    void dfs(int x)
    {
        dfn[x] = low[x] = cur++; // 记录访问时间和low值
        stk.push_back(x);        // 将当前节点压入栈中

        // 遍历当前节点的所有邻接节点
        for (auto y : adj[x])
        {
            if (dfn[y] == -1)
            {                                 // 如果邻接节点y未被访问
                dfs(y);                       // 递归访问邻接节点y
                low[x] = min(low[x], low[y]); // 更新当前节点x的low值
            }
            else if (bel[y] == -1)
            {                                 // 如果邻接节点y已被访问,但不在当前强连通分量中
                low[x] = min(low[x], dfn[y]); // 更新low值
            }
        }

        // 如果当前节点x是一个强连通分量的根节点
        if (dfn[x] == low[x])
        {
            int y; // 用于存储从栈中弹出的节点
            do
            {
                y = stk.back(); // 获取栈顶元素
                bel[y] = cnt;   // 将该节点标记为当前强连通分量
                stk.pop_back(); // 从栈中弹出该节点
            } while (y != x); // 直到弹出当前节点x
            cnt++; // 增加强连通分量的计数
        }
    }

    // 主函数,执行SCC算法
    vector<int> work()
    {
        for (int i = 0; i < n; i++)
        {
            if (dfn[i] == -1)
            {           // 如果节点i未被访问
                dfs(i); // 进行深度优先搜索
            }
        }
        return bel; // 返回每个节点的强连通分量编号
    }
};

void solve()
{
    int n;
    cin >> n;
    SCC scc(n + 1);
    vi r(n + 1);
    for (int i = 1; i <= n; i++)
    {
        cin >> r[i];
        scc.addEdge(i, r[i]);
    }
    vi result = scc.work();
    vector<vi> adj(n + 1);
    vi deg(n + 1);
    for (int i = 1; i <= n; i++)
    {
        int u = result[r[i]];
        int v = result[i];
        if (u == v)
            continue;
        deg[v]++;
        adj[u].push_back(v);
    }
    int ans = 0;
    auto dfs = [&](auto self, int u, int dep) -> void
    {
        ans = max(ans, dep);
        for (auto v : adj[u])
        {
            self(self, v, dep + 1);
        }
    };
    for (int i = 1; i <= n; i++)
    {
        if (deg[i] == 0)
        {
            dfs(dfs, i, 0);
        }
    }
    cout << ans + 2 << "\n";
}

int main()
{
    ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
    // freopen("test.in", "r", stdin);
    // freopen("test.out", "w", stdout);
    int _ = 1;
    std::cin >> _;
    while (_--)
    {
        solve();
    }
    return 0;
}

/*######################################END######################################*/
// 链接:s

G2. Medium Demon Problem (hard version)

这是问题的困难版本。两个版本的主要区别以粗体标出。

一群 \(n\) 只蜘蛛聚在一起交换毛绒玩具。最开始,每只蜘蛛都有 1 个毛绒玩具。每年,如果蜘蛛 \(i\) 至少有一个毛绒玩具,他将给蜘蛛 \(r_i\) 一个毛绒玩具。否则,他将什么也不做。请注意,所有毛绒玩具的转移同时发生。在这个版本中,每只蜘蛛在任何时候都可以拥有多个毛绒玩具。

如果每只蜘蛛在本年度(本年度交换前)拥有的毛绒玩具数量与上一年度(上一年度交换前)相同,则本年度的进程是稳定的。请注意,第一年永远不会稳定。

找出进程变得稳定的第一年。

输入

第一行包含一个整数 \(t\)\(1 \leq t \leq 10^4\)) — 测试用例的数量。

每个测试用例的第一行包含一个整数 \(n\)\(2 \leq n \leq 2 \cdot 10^5\)) — 蜘蛛的数量。

接下来的行包含 \(n\) 个整数 \(r_1, r_2, \ldots, r_n\)\(1 \leq r_i \leq n\)\(r_i \neq i\)) — 每只蜘蛛的毛绒玩具接收者。

保证所有测试用例中 \(n\) 的总和不超过 \(2 \cdot 10^5\)

输出

对于每个测试用例,在新的一行输出一个整数,表示过程变得稳定的第一年。

样例

输入

5
2
2 1
5
2 3 4 5 1
5
2 1 4 2 3
5
4 1 1 5 4
10
4 3 9 1 6 7 9 10 10 3

输出

2
2
5
5
5

提示

对于第二个测试用例:

在第 1 年,所有蜘蛛的毛绒玩具数量为:[1, 1, 1, 1, 1]。然后进行第 1 年的交换。

在第 2 年,所有蜘蛛的毛绒玩具数量为:[1, 1, 1, 1, 1]。由于这个数组与前一年相同,因此这一年是稳定的。

对于第三个测试用例:

在第 1 年,所有蜘蛛的毛绒玩具数量为:[1, 1, 1, 1, 1]。然后进行第 1 年的交换。

在第 2 年,所有蜘蛛的毛绒玩具数量为:[1, 2, 1, 1, 0]。然后进行第 2 年的交换。

在第 3 年,所有蜘蛛的毛绒玩具数量为:[1, 3, 0, 1, 0]。然后进行第 3 年的交换。

在第 4 年,所有蜘蛛的毛绒玩具数量为:[1, 4, 0, 0, 0]。然后进行第 4 年的交换。

在第 5 年,所有蜘蛛的毛绒玩具数量为:[1, 4, 0, 0, 0]。由于这个数组与前一年相同,因此这一年是稳定的。

解题思路

与简单版本相似,观察发现,如果一个环没有其它节点对其进行输入,那么这个环上的所有节点一定都是持有 \(1\) 个毛绒玩具。

由于一次性只能传递一个,如果环上某个节点有外界节点的输入,那么外界输入的毛绒玩具一定会一直停在该节点上,不会传递到环中的其它节点。

因此最终的稳定形态一定是只剩诺干个环,环上每个节点至少有一个毛绒玩具且玩具数量不会改变。

考虑到达稳定形态的过程。

如果我们把环看做根对其反向建边,可以发现,一个环到达稳定状态的最早时间为其根下最大子树的大小。

因为一次只能传递一个,所以一个子树中的所有毛绒玩具想要传递到环上一定会经过子树大小的时间。

所以和简单版本一样,对环进行缩点,然后反向建边,直接dfs寻找下根最大子树的大小即可。

实现代码

#include <bits/stdc++.h>

using namespace std;

using ll = long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
using vi = vector<int>;
using vl = vector<ll>;

#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fLL
#include <bits/stdc++.h>

using namespace std;

// 强连通分量类
struct SCC
{
    int n;                     // 图的节点数
    vector<vector<int>> adj;   // 邻接表表示的图
    vector<int> stk;           // 存储DFS路径的栈
    vector<int> dfn, low, bel; // dfn:节点的访问时间,low:节点的最小可达时间,bel:每个节点所属强连通分量的编号
    int cur, cnt;              // cur:当前时间戳,cnt:强连通分量的数量

    // 默认构造函数
    SCC() {}

    // 带参数的构造函数,初始化图的节点数
    SCC(int n)
    {
        init(n);
    }

    // 初始化函数
    void init(int n)
    {
        this->n = n;       // 设置节点数
        adj.assign(n, {}); // 初始化邻接表
        dfn.assign(n, -1); // 初始化访问时间为-1,表示未访问
        low.resize(n);     // 初始化low数组
        bel.assign(n, -1); // 初始化bel数组,表示每个节点的强连通分量编号
        stk.clear();       // 清空栈
        cur = cnt = 0;     // 当前时间戳和强连通分量计数器初始化为0
    }

    // 添加边的函数
    void addEdge(int u, int v)
    {
        adj[u].push_back(v); // 在邻接表中添加边 u -> v
    }

    // 深度优先搜索函数
    void dfs(int x)
    {
        dfn[x] = low[x] = cur++; // 记录访问时间和low值
        stk.push_back(x);        // 将当前节点压入栈中

        // 遍历当前节点的所有邻接节点
        for (auto y : adj[x])
        {
            if (dfn[y] == -1)
            {                                 // 如果邻接节点y未被访问
                dfs(y);                       // 递归访问邻接节点y
                low[x] = min(low[x], low[y]); // 更新当前节点x的low值
            }
            else if (bel[y] == -1)
            {                                 // 如果邻接节点y已被访问,但不在当前强连通分量中
                low[x] = min(low[x], dfn[y]); // 更新low值
            }
        }

        // 如果当前节点x是一个强连通分量的根节点
        if (dfn[x] == low[x])
        {
            int y; // 用于存储从栈中弹出的节点
            do
            {
                y = stk.back(); // 获取栈顶元素
                bel[y] = cnt;   // 将该节点标记为当前强连通分量
                stk.pop_back(); // 从栈中弹出该节点
            } while (y != x); // 直到弹出当前节点x
            cnt++; // 增加强连通分量的计数
        }
    }

    // 主函数,执行SCC算法
    vector<int> work()
    {
        for (int i = 0; i < n; i++)
        {
            if (dfn[i] == -1)
            {           // 如果节点i未被访问
                dfs(i); // 进行深度优先搜索
            }
        }
        return bel; // 返回每个节点的强连通分量编号
    }
};

void solve()
{
    int n;
    cin >> n;
    SCC scc(n + 1);
    vi r(n + 1);
    for (int i = 1; i <= n; i++)
    {
        cin >> r[i];
        scc.addEdge(i, r[i]);
    }
    vi result = scc.work();
    vector<vi> adj(n + 1);
    vi deg(n + 1);
    for (int i = 1; i <= n; i++)
    {
        int u = result[r[i]];
        int v = result[i];
        if (u == v)
            continue;
        deg[v]++;
        adj[u].push_back(v);
    }
    vi siz(n + 1);
    int rt = -1;
    int ans = 0;
    auto dfs = [&](auto self, int u) -> void
    {
        siz[u] = 1;
        for (auto v : adj[u])
        {
            self(self, v);
            if (u == rt)
                ans = max(ans, siz[v]);
            siz[u] += siz[v];
        }
    };
    for (int i = 1; i <= n; i++)
    {
        if (deg[i] == 0)
        {
            rt = i;
            dfs(dfs, i);
        }
    }
    cout << ans + 2 << "\n";
}

int main()
{
    ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
    // freopen("test.in", "r", stdin);
    // freopen("test.out", "w", stdout);
    int _ = 1;
    std::cin >> _;
    while (_--)
    {
        solve();
    }
    return 0;
}

/*######################################END######################################*/
// 链接:s

H. Hard Demon Problem

Swing 要开一家煎饼厂!一家好的煎饼厂必须擅长把东西压平,所以 Swing 打算在二维矩阵上测试他的新设备。

Swing 给出了一个包含正整数的 \(n \times n\) 矩阵 \(M\)。他有 \(q\) 个问题要问你。

对于每个问题,他都会给出四个整数 \(x_1\)\(y_1\)\(x_2\)\(y_2\),并要求你将以 \((x_1,y_1)\)\((x_2,y_2)\) 为边界的子矩阵平铺成数组 \(A\)。形式上,

\[A = [M(x_1,y_1), M(x_1,y_1+1), \ldots, M(x_1,y_2), M(x_1+1,y_1), M(x_1+1,y_1+1), \ldots, M(x_2,y_2)]. \]

下图描述了以红色虚线为边界的子矩阵的扁平化过程。橙色箭头表示子矩阵元素追加到 \(A\) 后面的方向,\(A\) 显示在图像底部。

之后,他问你 \(\sum_{i=1}^{|A|} A_i \cdot i\) 的值(所有 \(i\)\(A_i \cdot i\) 的和)。

输入

第一行包含一个整数 \(t\)\(1 \leq t \leq 10^3\)) — 测试用例的数量。

每个测试的第一行包含两个整数 \(n\)\(q\)\(1 \leq n \leq 2000, 1 \leq q \leq 10^6\))— 矩阵 \(M\) 的大小和查询次数。

接下来的 \(n\) 行分别包含 \(n\) 个整数,其中第 \(i\) 行包含 \(M(i,1), M(i,2), \ldots, M(i,n)\)\(1 \leq M(i,j) \leq 10^6\))。

下面的 \(q\) 行包含四个整数 \(x_1\)\(y_1\)\(x_2\)\(y_2\)\(1 \leq x_1 \leq x_2 \leq n, 1 \leq y_1 \leq y_2 \leq n\))— 查询的边界。

保证所有测试用例中 \(n\) 的总和不超过 \(2000\),所有测试用例中 \(q\) 的总和不超过 \(10^6\)

输出

对于每个测试用例,在新的一行输出 \(q\) 个查询的结果。

样例

输入

2
4 3
1 5 2 4
4 9 5 3
4 5 2 3
1 5 5 2
1 1 4 4
2 2 3 3
1 2 4 3
3 3
1 2 3
4 5 6
7 8 9
1 1 1 3
1 3 3 3
2 2 2 2

输出

500 42 168
14 42 5

提示

在第一个测试用例的第二个查询中,\(A = [9, 5, 5, 2]\)。因此,和为 \(1 \cdot 9 + 2 \cdot 5 + 3 \cdot 5 + 4 \cdot 2 = 42\)

解题思路

先只考虑只有一行,即 \(y_1=y_2\) 的情况,那么我们实际上就是在就求一个等差数列乘以一个数组的区间和。

因此,我们可以计算出前缀和数组 \(\text{pre}_i=\sum_{i=1}^{n} a_i\)\(\text{preX}_i=\sum_{i=1}^{n}i\cdot a_i\)

利用等差数列乘数组的性质,我们可以得出区间 \([x_1,x_2]\) 的区间和

\[\text{sum}_{x_1,x_2}=\text{preX}_{x_2}-\text{preX}_{x_1-1}-x_1\cdot (\text{pre}_{x_2}-\text{pre}_{x_1-1}) \]

推广到二维的情况。

我们发现,对于一个子矩阵来说,同一列相乘的系数同样也是一个等差数列,只不过公差变成了 \(x_2-x_1+1\)

因此我们可以利用二维前缀和,计算出前缀和数组 \(\text{pre}_i,j=\sum_{i=1,j=1}^{n,m} a_{i,j}\)\(\text{preX}_i=\sum_{i=1,j=1}^{n,m}i\cdot a_{i,j}\)\(\text{preY}_i=\sum_{i=1,j=1}^{n,m}j\cdot a_{i,j}\)

每个子矩阵的和即为

\[\text{res} = \text{sum} + \text{sumY} + \text{sumX} \cdot (y_2 - y_1 + 1) \]

其中

\[\text{sum} = \text{pre}_{x_2, y_2} - \text{pre}_{x_2, y_1-1} - \text{pre}_{x_1-1, y_2} + \text{pre}_{x_1-1, y_1-1} \\ \text{sumX} = \text{preX}_{x_2, y_2} - \text{preX}_{x_2, y_1-1} - \text{preX}_{x_1-1, y_2} + \text{preX}_{x_1-1, y_1-1} \\ \text{sumY} = \text{preY}_{x_2, y_2} - \text{preY}_{x_2, y_1-1} - \text{preY}_{x_1-1, y_2} + \text{preY}_{x_1-1, y_1-1} \]

实现代码

#include <bits/stdc++.h>

using namespace std;

using ll = long long;
using ld = long double;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
using vi = vector<int>;
using vl = vector<ll>;

#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fLL
void solve()
{
    int n, q;
    cin >> n >> q;
    vector<vl> a(n + 1, vl(n + 1));
    vector<vl> pre(n + 1, vl(n + 1));
    vector<vl> preX(n + 1, vl(n + 1));
    vector<vl> preY(n + 1, vl(n + 1));
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            cin >> a[i][j];
            pre[i][j] = a[i][j];
            preX[i][j] = a[i][j] * i;
            preY[i][j] = a[i][j] * j;
        }
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = 0; j <= n; j++)
        {
            pre[i][j] += pre[i - 1][j];
            preX[i][j] += preX[i - 1][j];
            preY[i][j] += preY[i - 1][j];
        }
    }
    for (int i = 0; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            pre[i][j] += pre[i][j - 1];
            preX[i][j] += preX[i][j - 1];
            preY[i][j] += preY[i][j - 1];
        }
    }
    while (q--)
    {
        int x1, y1, x2, y2;
        cin >> x1 >> y1 >> x2 >> y2;
        ll sum = pre[x2][y2] - pre[x2][y1 - 1] - pre[x1 - 1][y2] + pre[x1 - 1][y1 - 1];
        ll sumX = preX[x2][y2] - preX[x2][y1 - 1] - preX[x1 - 1][y2] + preX[x1 - 1][y1 - 1];
        ll sumY = preY[x2][y2] - preY[x2][y1 - 1] - preY[x1 - 1][y2] + preY[x1 - 1][y1 - 1];
        sumX -= sum * x1;
        sumY -= sum * y1;
        cout << sum + sumY + sumX * (y2 - y1 + 1) << " \n"[!q];
    }
}

int main()
{
    ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
    // freopen("test.in", "r", stdin);
    // freopen("test.out", "w", stdout);
    int _ = 1;
    std::cin >> _;
    while (_--)
    {
        solve();
    }
    return 0;
}

/*######################################END######################################*/
// 链接:

这场难度比以往d4难度高一个档次啊,有d3的难度了。

不过这题面给的也是有点难绷,起一个认真一点的标题有这么难吗,还有F题的题面屎一样。

封面画师id:ムルムル - pixiv

posted @   ExtractStars  阅读(863)  评论(7编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示