THU训练营预选赛2023

比赛地址

A

Tag: 排列 置换

  • 遍历排列中每个置换环, 找到每个元素需要跳几次才能回到与之相同的元素(最多为环的长度个数)
  • 对每个元素所的次数取max
点击查看代码
// https://tsinghua.contest.codeforces.com/group/sTsHnFxwiH/contest/453495/problem/A
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
#include <map>
using namespace std;
using LL = long long;
using PII = pair<int, int>;
const int N = 5e5 + 10;
int a[N];
int p[N];
bool st[N];
int n;

void solv()
{
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i];
    for (int i = 1; i <= n; i ++) cin >> p[i];
    int ans = 0;
    for (int i = 1; i <= n; i ++)
        if (!st[i])
        {
            // 遍历包含i的置换环, 并将每个元素对应位置的ai保存到tmp
            vector<int> tmp;
            int pt = i;
            do
            {
                tmp.push_back(a[pt]);
                st[pt] = true;
                pt = p[pt];
                
            } while (!st[pt]);

            map<int, int> mp;  // mp[i] = j, 上次遍历到元素i时的cnt序号为j
            pt = tmp.size() - 1;  // 倒序遍历tmp, 指针从最后一个元素开始
            int cnt = 0;
            mp[tmp[0]] = ++ cnt;  // 倒序遍历tmp, 若循环遍历, 则最后一个元素的上一个元素为第一个元素
            // 第一圈
            while (pt >= 0)
            {
                int x = tmp[pt];
                cnt ++;
                if (mp[x])
                {
                    int t = cnt - mp[x];
                    ans = max(ans, t);
                }
                mp[x] = cnt;
                pt --;
            }
            // 第二圈
            // 因为仅遍历第一圈可能导致某些元素并没有找到与其相同的之前的元素的位置(如第一次访问)
            // 两圈则能保证
            pt = tmp.size() - 1;
            while (pt >= 0)
            {
                int x = tmp[pt];
                cnt ++;
                if (mp[x])
                {
                    int t = cnt - mp[x];
                    ans = max(ans, t);
                }
                mp[x] = cnt;
                pt --;
            }
        }
    cout << ans << endl;
}

int main()
{
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int T = 1;
	// cin >> T;
    while (T --)
    {
        solv();
    }
    return 0;
}

B

Tag: 前缀和 贪心

  • 通过差分计算每个不同观众受到几个评委, 即这个观众的权重
  • 贪心: 按照以上计算的权重, 从大到小, 对观众进行操作减少其 critics;
点击查看代码
// https://tsinghua.contest.codeforces.com/group/sTsHnFxwiH/contest/453495/problem/B
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
using LL = long long;
using PII = pair<LL, LL>;
const int N = 1e5 + 10;
LL a[N];
LL b[N];


void solv()
{   
    LL n, m, k;
    cin >> n >> m >> k;
    for (int i = 1; i <= n; i ++)
    {
        cin >> a[i];
    }
    for (int i = 1; i <= m; i ++)
    {
        int l, r;
        cin >> l >> r;
        b[l] ++;
        b[r+1] --; 
    }
    vector<PII> tmp;
    LL ans = 0;
    for (int i = 1; i <= n; i ++)
    {
        b[i] += b[i-1];
        tmp.push_back({b[i], a[i]});
        ans += a[i] * b[i];
    } 
    sort(tmp.begin(), tmp.end());
    for (int i = tmp.size() - 1; i >= 0; i --)
    {
        LL x = tmp[i].first, y = tmp[i].second;
        LL c = min(k, y);
        ans -= c * x;
        k -= c;
        if (k == 0) break;
    }
    cout << ans << endl;

}

int main()
{
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int T = 1;
	// cin >> T;
    while (T --)
    {
        solv();
    }
    return 0;
}

D

Tag: 树上差分
总结补充:

  • 树上差分(分为点差分和边, 本题使用点差分), 参考;
  • 最近公共祖先, 参考;
点击查看代码
// https://tsinghua.contest.codeforces.com/group/sTsHnFxwiH/contest/453495/problem/D
/**
 * tag: 树上差分
 */
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
#include <queue>
#include <map>
using namespace std;
using LL = long long;
using PII = pair<LL, LL>;
const int N = 1e5 + 10;
int n;
LL time;
int p[N], t[N], a[N]; // i的父节点, p[i]->t的边权, i的点权
LL depth[N];          // 节点i的深度(按时间边权计算)
int q[N];             // 队列, 存路径
int sup[N];           // 节点i在time时间内, 向上能够到达的最远祖先节点为sup[i]
LL pre[N];            // 树上差分

struct Edge  // 边 u -> v, 边权为 t
{
    int v;
    LL t;
};
vector<Edge> adj[N];  // i的邻接边列表为adj[i]
int ans1;
LL ans2;

// 计算每个节点深度(按边权计算)
void dfs1(int u)
{
    for (auto [v, t] : adj[u])
    {
        depth[v] = depth[u] + t;
        dfs1(v);
    }
}

// 得到sup数组, 即计算 节点i在time时间内, 向上能够到达的最远祖先节点sup[i]
void dfs2(int u, int hh, int tt)
{
    // 将 从节点 sup[i] 到 节点 i 的节点放入队列 q, 通过调整队头指针hh, 找到 sup[i]的位置
    // 这里dfs2中的hh和tt通过局部变量的方式传入, 避免递归返回时破坏队头位置
    q[++tt] = u;
    while (depth[q[tt]] - depth[q[hh]] > time)
        hh++;
    sup[u] = q[hh];
    for (auto [v, t] : adj[u])
        dfs2(v, hh, tt);
}

// 树上差分
// 树上差分公式 :
// 将x-y节点最短路径上的所有节点(含x y)的权值+c, 数组b[i]为差分数组, 则
//      1. b[x] += c
//      2. b[y] += c
//      3. b[a] -= c , a 为 xy 的最近公共祖先
//      4. b[p] -= c , p 为 a 的父节点
// 由于这里的xy节点关系特殊(sup[u]一定是u的祖宗节点, 或等于u), 则 二者的LCA为 sup[u],
// 整理可得dfs3总的两个式子
void dfs3(int u)
{
    pre[p[sup[u]]] -= a[u];
    pre[u] += a[u];
    for (auto [v, t] : adj[u])
        dfs3(v);
}

// 计算树上前缀和, 并维护最大ans
void dfs4(int u)
{
    for (auto [v, t] : adj[u])
    {
        dfs4(v);
        pre[u] += pre[v];
    }

    if (pre[u] > ans2)
    {
        ans2 = pre[u];
        ans1 = u;
    }
}

void solv()
{
    cin >> n >> time;
    for (int i = 2; i <= n; i++)
        cin >> p[i];
    for (int i = 2; i <= n; i++)
    {
        cin >> t[i];
        adj[p[i]].push_back({i, t[i]});
    }
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        a[i]++;
    }
    dfs1(1);
    dfs2(1, 1, 0);
    dfs3(1);
    dfs4(1);
    cout << ans1 << ' ' << ans2 << endl;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int T = 1;
    // cin >> T;
    while (T--)
    {
        solv();
    }
    return 0;
}

posted @   O2iginal  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示