Contestant'. Rank 1013. Rating +32.

A. Twin Permutations

题意

给定一个排列 \(a\),构造另一个排列 \(b\),满足 \(a_i + b_i\) 不递减。

思路

那也就可以是相等。

因为是长度相等的排列,因而一定可以得到 \(n\) 个和为 \(n + 1\) 的组合。

因而,输出 \(n - a_i + 1\) 即可。

时间复杂度:\(O(n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 998244353, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int n;
    cin >> n;
    for(int i=0;i<n;i++){
        int cur;
        cin >> cur;
        cout << (n - cur + 1) << ' ';
    }
    cout << '\n';
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --) solve();
}

签到签到

B. Array merging

题意

给定两个序列,定义一次操作为选择一个序列,并将序列的头元素取出,放入新序列的尾部。当所有元素被取出后,输出最长相等元素连续子序列的长度。

思路

首先,操作等价于将某个序列的元素按顺序插入另一个序列中,那么可以证明的是,我们只能合并两个连续相等的子序列,无法合并多个。

那么,我们遍历找出每一种元素在两个序列中的连续最大长度,最后将两个序列中的每一个元素的长度加起来,和答案取最大值即可。

时间复杂度:\(O(n \log n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 998244353, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int n;
    cin >> n;
    map<int, int> cnt1, cnt2;
    vector<int> a(n + 1);
    int cnt = 0;
    for(int i=1;i<=n;i++) {
        cin >> a[i];
        if (a[i] != a[i - 1]) cnt = 0;
        if (cnt == 0 || a[i] == a[i - 1]) cnt++;
        cnt1[a[i]] = max(cnt1[a[i]], cnt);
    }
    cnt = 0;
    for(int i=1;i<=n;i++) {
        cin >> a[i];
        if (a[i] != a[i - 1]) cnt = 0;
        if (cnt == 0 || a[i] == a[i - 1]) cnt++;
        cnt2[a[i]] = max(cnt2[a[i]], cnt);
    }
    int ans = 0;
    for(int i=1;i<=2 * n;i++){
        ans = max(ans, cnt1[i] + cnt2[i]);
    }
    cout << ans << '\n';
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --) solve();
}

草,一开始读假了

C. Copil Copac Draws Trees

题意

对于一棵 \(n\) 个点的树,给定其 \(n - 1\) 个边。

初始状态下,图中只有点 \(1\),定义一次操作为按输入顺序枚举所有边,如果这个边有一个端点在图中,那么将这条边和另一个端点加入图中,否则跳过这条边。

输出建立这棵树需要的最小操作数。

思路

首先,显然这是一棵根为 \(1\) 的有根树,只要父节点不存在,这条边也一定不存在。

我们将每条边按照输入顺序编号,那么我们不难发现下面几点:

  1. 对于一个节点,它的每个子树的操作是互不干扰的;

  2. 只要上一条边的编号小于这条边的编号,那么这两条边都可以在一次操作内完成,否则需要多一次操作

因而,我们考虑树型 \(dp\)

我们从根节点开始 \(\mathtt{dfs}\),并遍历所有子树。

对于某一个点,如果它的子节点和它的边的编号小于它的父节点和它的编号,那么这棵子树的 \(dp\) 值需要 \(+1\)

对于所有子树,我们取 \(dp\) 的最大值即可。

最后,输出 \(dp[1] + 1\)

时间复杂度:\(O(n)\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define int long long
#define pii pair<int, int>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 998244353, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int n;
    cin >> n;
    vector<vector<pii>> e(n + 1);
    for(int i=1;i<n;i++){
        int u, v;
        cin >> u >> v;
        e[u].pb(v, i), e[v].pb(u, i);
    }
    vector<int> dp(n + 1);
    vector<bool> st(n + 1);
    auto dfs = [&](auto self, int x, int p, int id) -> void{
        if(st[x]) return;
        st[x] = true;
        for(auto t : e[x]){
            if(t.fs == p) continue;
            self(self, t.fs, x, t.sc);
            dp[x] = max(dp[x], dp[t.fs] + (id > t.sc ? 1 : 0));
        }
    };
    dfs(dfs, 1, -1, -inf);
    cout << dp[1] + 1 << '\n';
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --) solve();
}

口胡乱写居然过了(

D. The BOSS Can Count Pairs

题意

给定两个序列 \(a, b\),输出二元组 \((i, j)\) 的个数,满足 \(1 \leq i < j \leq n, a_i \cdot a_j = b_i+b_j\)

思路1

首先,数据范围很好的将这道签到题变成了难题。

那么,普通的 \(O(n ^ 2)\) 是绝对无法通过的,那么在显然需要二重遍历的情况下,我们考虑优化某一维的计算。

因为 \(b_i \leq n, b_j \leq n\),所以 \(b_i + b_j = a_i \cdot a_j \leq 2n\)

因此,\(\min(a_i, a_j) \leq \sqrt{2n}\)

所以,我们转而考虑对 \(a_j\) 的可能的值的枚举。

定义 \(fr[x][y]\)\((x, y)\) 二元组的个数,\(lim = \sqrt{2n}\)

针对重复计算,我们枚举 \(a_i\),对 \(a_j\) 进行分讨:

  1. \(a_j = a_i\),那么有 \(\displaystyle{\frac{\sum_{i=1}^{n}fr[a_i][a_i \cdot a_i - b_i]-\sum_{i=1}^{lim}fr[i][\frac{i \cdot i}{2}]}{2}}\) 在上式中,我们筛除了 \(a_i\)\(a_j\) 互换的重复和 \((a_i, b_i) = (a_j, b_j) \rightarrow b_i = b_j\) 的重复;

  2. \(a_j \neq a_i\),因为重复的原因,我们直接计算 \(a_i > a_j\) 的情况,那么有: \(\displaystyle{\sum_{i=1}^n \sum_{j=1}^{min(a_i-1,\frac{2\cdot n}{a_i})}fr[j][a_i \cdot j-b_i]}\)

最后,答案就是两者之和。

时间复杂度:\(O(n \sqrt{n})\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define ll long long
#define pii pair<int, int>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, mod = 998244353, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    ll n;
    cin >> n;
    vector<ll> a(n + 1), b(n + 1);
    for(ll i=1;i<=n;i++) cin >> a[i];
    for(ll i=1;i<=n;i++) cin >> b[i];
    ll lim = (ll) sqrt(2 * n);
    vector<vector<int>> fr(lim + 1, vector<int>(n + 1));
    for(ll i=1;i<=n;i++)
        if(a[i] <= lim)  fr[a[i]][b[i]] ++;
    ll ans1 = 0, ans2 = 0;
    for(ll i=1;i<=n;i++){
        if(a[i] * a[i] - b[i] >= 0 && a[i] * a[i] - b[i] <= n)
            ans1 += fr[a[i]][a[i] * a[i] - b[i]];
        if(i <= lim && i % 2 == 0) ans1 -= fr[i][i * i / 2];
        for(ll j=1;j<=min(a[i] - 1, 2 * n / a[i]);j++){
            if(a[i] * j - b[i] >= 0 && a[i] * j - b[i] <= n)
                ans2 += fr[j][a[i] * j - b[i]];
        }
    }
    cout << ans1 / 2 + ans2 << '\n';
}

int main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --) solve();
}

思路2

这边放一个队友的简要思路:

首先,我们固定 \(a_i\),那么只要 \(a_j + 1\)\(b_j\) 就会加上 \(a_i\)

也就是说,这一趟我们只需枚举 \(\frac{n}{a_i}\),而如果 \(a_i\) 比较小,那么这一趟枚举的次数是可观的。

那么, 我们考虑预处理一部分的答案,即使用 \(O(nk)\) 的复杂度先暴力跑一部分的答案。

如上,加上数组的初始化,我们得到该思路的复杂度约为:\(O(2nk + \frac{n ^ 2}{k})\),因而 \(k = \sqrt{2n}\) 时取到较优解。

时间复杂度:约\(O(2n \sqrt{2n} + \frac{2n}{k})\)

对应AC代码

#include <bits/stdc++.h>

using namespace std;

#define ll long long
#define pii pair<int, int>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#define all(x) x.begin(),x.end()

const ll N = 2e5 + 10, mod = 998244353, inf = 0x3f3f3f3f3f3f3f3f;

ll hs(ll x, ll y){
    return x * 979797 + y;
}

void solve() {
    ll n;
    cin >> n;
    ll lim = (ll) sqrt(2 * n);
    vector<ll> a(n + 1), b(n + 1);
    vector<vector<int>> q(lim + 1, vector<int>(n + 1));
    unordered_map<ll, ll> cnt;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= n; i++) cin >> b[i], cnt[hs(a[i], b[i])]++;
    for (int k = 1; k <= lim; k++) {
        for (int i = 1; i <= n; i++) {
            ll bj = a[i] * k - b[i];
            if (a[i] * k > b[i] && bj <= n) q[k][bj]++;
        }
    }
    ll ans = 0;
    for (int i = 1; i <= n; i++) {
        if (a[i] * a[i] == b[i] + b[i]) ans--;
        if (a[i] <= lim) {
            ans += q[a[i]][b[i]];
            continue;
        }
        ll y = (-b[i]) % a[i], x = (b[i] + y) / a[i];
        for (; x <= n && y <= n && a[i] * x <= 2 * n && b[i] + y <= 2 * n; x++, y += a[i]) {
            if (y > 0 && cnt.count(hs(x, y))) ans += cnt[hs(x, y)];
        }
    }
    cout << ans / 2 << '\n';
}

signed main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while(t --) solve();
}

傻逼题,卡全局long long