首页 > 其他分享 >Codeforces Round 977 (Div. 2, based on COMPFEST 16 - Final Round)

Codeforces Round 977 (Div. 2, based on COMPFEST 16 - Final Round)

时间:2024-10-08 15:12:08浏览次数:1  
标签:977 std le based cin int kN operatorname Round

目录

写在前面

补题地址:https://codeforces.com/contest/2021

上大分失败呃呃呃呃

我不要上班呜呜

A 签到

考虑仅有三个数 \(a, b, c(a < b < c)\) 时最优操作,手玩下发现最优操作顺序一定是按照升序进行操作。

进一步归纳可知对于全局按照升序操作一定是最优的。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 110;
//=============================================================
int n, a[kN];
//=============================================================
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n;
    for (int i = 1; i <= n; ++ i) std::cin >> a[i];
    std::sort(a + 1, a + n + 1);
    
    int ans = a[1];
    for (int i = 2; i <= n; ++ i) ans = (ans + a[i]) / 2;
    std::cout << ans << "\n";
  }
  return 0;
}

B 贪心,枚举

显然应该先把原数列的 \(\operatorname{mex}\) 求出来,在此过程中对 \(\operatorname{mex}\) 有用的数显然之后不会被操作。然后仅需不断考虑使用剩下的数如何使 \(\operatorname{mex}\) 增加 1 即可。

发现每次操作一定选择最小的可行的数,且发现每次操作仅能加 \(x\),即每个数仅能被修改成在 \(\bmod x\) 剩余系下相同,且大于该数的数。于是考虑将所有数 \(a_i\) 插入下标为 \(a_i\bmod x\) 的 set 中,每次仅需检查 \(\bmod x\) \(\operatorname{mex} \bmod x\) 的 set 中最小值是否不大于 \(\operatorname{mex}\) 即可。

总时间复杂度 \(O(n\log n)\) 级别。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, x, mex, a[kN];
//=============================================================
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n >> x;
    for (int i = 1; i <= n; ++ i) std::cin >> a[i];
    std::sort(a + 1, a + n + 1);

    mex = 0;
    std::map<int, std::multiset<int> > cnt;
    for (int i = 1; i <= n; ++ i) {
      if (a[i] != mex) cnt[a[i] % x].insert(a[i]);
      if (a[i] == mex) ++ mex;
    } 
    while (!cnt[mex % x].empty() && *cnt[mex % x].begin() <= mex) {
      cnt[mex % x].erase(cnt[mex % x].begin());
      ++ mex;
    }
    std::cout << mex << "\n";
  }
  return 0;
}

C1 贪心

发现这个“上台过的人随便插队”是非常灵活的,则若某个人能够成功第一次上台,则之后的上台一定可以满足。

于是仅需考虑 \(b\) 中每个权值第一次出现的位置,则可对 \(b\) 去重后得到一个排列,显然当且仅当该排列是 \(a\) 的一个前缀时合法。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, m, q, a[kN], b[kN];
bool vis[kN];
//=============================================================
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n >> m >> q;
    for (int i = 0; i < n; ++ i) std::cin >> a[i], vis[a[i]] = 0;
    for (int i = 0; i < m; ++ i) std::cin >> b[i];
    
    int now = 0;
    bool flag = 1;
    for (int i = 0; i < m; ++ i) {
      if (vis[b[i]]) continue;
      if (a[now] == b[i]) vis[a[now]] = 1, ++ now;
      else flag = 0;
    }
    std::cout << (flag ? "YA" : "TIDAK") << "\n";
  }
  return 0;
}

C2 贪心,枚举

发现 \(a\) 是排列,于是考虑按下标对 \(a\) 进行重映射到排列 \(1\sim n\) 上,同理对 \(b\) 进行重映射一下。则根据 C1 的结论,仅需检查 \(b\) 中出现的所有权值是不是恰好为 \(1, 2, \cdots\),且权值 \(1, 2, \cdots\) 的第一次出现位置是升序的。

则此时仅需比较 \(b\) 中所有位置 \(i\),其对应的权值 \(b_{i}\) 第一次出现位置,是否小于 \(b_{i}+1\) 第一次出现位置即可。若上述位置数量恰好为 \(n-1\),则说明数列 \(b\) 合法。

考虑使用 set 维护每种权值的出现数量,则检查位置合法仅需检查两个 set 中最小值即可。发现每次修改仅会影响两个 set,至多 4 个位置的合法性,即可 \(O(\log n)\) 级别维护合法性。

总时间复杂度 \(O((n + q)\log n)\) 级别。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, m, q, a[kN], b[kN], map[kN];
std::set<int> s[kN];
int cnt;
//=============================================================
bool check(int p_) {
  if (p_ <= 0 || p_ >= n) return 0;
  return (*s[a[p_]].begin()) <= (*s[a[p_ + 1]].begin());
}
bool query() {
  return cnt == n - 1;
}
void modify(int p_, int t_) {
  cnt -= check(map[b[p_]]) + check(map[b[p_]] - 1);
  s[b[p_]].erase(p_);
  cnt += check(map[b[p_]]) + check(map[b[p_]] - 1);

  cnt -= check(map[t_]) + check(map[t_] - 1);
  s[t_].insert(p_), b[p_] = t_;
  cnt += check(map[t_]) + check(map[t_] - 1);
}
void init() {
  cnt = 0;
  for (int i = 1; i < n; ++ i) cnt += check(i);
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n >> m >> q;
    for (int i = 1; i <= n; ++ i) std::cin >> a[i], map[a[i]] = i;
    for (int i = 1; i <= n; ++ i) s[i].clear(), s[i].insert(m + 1);
    for (int i = 1; i <= m; ++ i) std::cin >> b[i], s[b[i]].insert(i);
    init();
    std::cout << (query() ? "YA" : "TIDAK") << "\n";
    
    while (q --) {
      int p, t; std::cin >> p >> t;
      modify(p, t);
      std::cout << (query() ? "YA" : "TIDAK") << "\n";
    }
  }
  return 0;
}

D DP

我去这题在哪见过?这不今年牛客6的I?不过那题是钦定选择的区间中的某个数,而这题是钦定选择的区间的某个端点。

参考:https://blog.csdn.net/Code92007/article/details/142734368

E1/E2 Kruscal 重构树,树上背包

我去这题在哪见过?这不去年牛客6的A

两点间的贡献是路径上的最长边这个性质太典了,考虑 Kruscal 重构树,原图节点变为重构树中的叶节点,两点间的最长边贡献变为两点 lca 的点权值的贡献。

考虑树上背包,枚举边时将两个子树合并,两子树代表的点集之间的最长边,即为枚举到的边的边权。则每次转移时仅需考虑两个子树中分别已经选择了多少点作为服务器即可,且当且仅当其中某个子树没有服务器选择时,需要考虑当前边权的贡献。

于是可以在 Kruscal 重构树上得到一个很显然的树形 DP,设 \(f_{u, i}\) 表示在 \(u\) 的子树中一共有 \(i\) 个点为服务器时,这棵树代表的点集对答案的贡献,初始化 \(f_{u, i} = +\infin, f_{u, 1} = 0\)。在构建 Kruscal 重构树枚举到边 \((u, v, w)\) 合并 \(u, v\) 所在点集 \(r_u, r_v\) 得到点集 \(r\) 的同时,进行转移,考虑从 \(r_u, r_v\) 中分别选择了多少个服务器,有:

\[\begin{cases} f_{r, i + j} \leftarrow f_{r_u, i} + f_{r_v, j}(1\le i\le \operatorname{size}_{r_u}, 1\le j\le \operatorname{size}_{r_v})\\ f_{r, i}\leftarrow f_{r_u, i} + w\times \operatorname{cnt}_{r_v} (1\le i\le \operatorname{size}_{r_u})\\ f_{r, j}\leftarrow f_{r_v, j} + w\times \operatorname{cnt}_{r_u} (1\le j\le \operatorname{size}_{r_v})\\ \end{cases}\]

其中 \(\operatorname{size}_{r_u}\) 表示点集大小,\(\operatorname{cnt}_{r_u}\) 表示点集中限定的需要连接的点的数量。

设最终得到的点集为 \(\operatorname{root}\),答案即:

\[\forall 1\le i\le n, f_{\operatorname{root}, i} \]

这样实现起来比较符合重构树的思路也比较直观,但是时间复杂度上限是 \(O(n^3)\) 级别,跑不过去。

发现新建节点 \(rt\) 表示合并后的点集是没有必要的,可以在维护并查集时直接按秩合并到原节点上进行转移。于是修改定义 \(f_{u, i}\) 表示以 \(u\) 为根的点集中一共有 \(i\) 个点为服务器时,这棵树代表的点集对答案的贡献,其余部分均不变。

并查集合并时使用按秩合并时,总时间度复杂度变为 \(O(n^2)\) 级别。

1A 了爽。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 5010;
const LL kInf = 1e18 + 2077;
//=============================================================
int n, m, p, yes[kN];
struct Edge {
  int u, v, w;
} e[kN];
std::vector<int> edge[kN << 1];
int fa[kN], sz[kN], cnt[kN];
LL f[kN][kN], temp[kN];
//=============================================================
bool cmp(const Edge& fir_, const Edge& sec_) {
  return fir_.w < sec_.w;
}
void init() {
  for (int i = 1; i <= n; ++ i) yes[i] = 0, fa[i] = i, sz[i] = 1, cnt[i] = 0;
  for (int i = 1; i <= p; ++ i) {
    int x; std::cin >> x;
    yes[x] = cnt[x] = 1;
  }
  for (int i = 1; i <= m; ++ i) {
    int u, v, w; std::cin >> u >> v >> w;
    e[i] = (Edge) {u, v, w};
  }
  std::sort(e + 1, e + m + 1, cmp);
}
int find(int x_) {
  return x_ == fa[x_] ? x_ : (fa[x_] = find(fa[x_]));
}
void merge(int x_, int y_, int w_) {
  int fx = find(x_), fy = find(y_);
  int fxy = (sz[fx] > sz[fy]) ? fx : fy;

  for (int i = 0; i <= n; ++ i) temp[i] = kInf;
  for (int i = 1; i <= sz[fx]; ++ i) {
    for (int j = 1; j <= sz[fy]; ++ j) {
      temp[i + j] = std::min(temp[i + j], f[fx][i] + f[fy][j]);
    }
  }
  for (int i = 1; i <= sz[fx]; ++ i) {
    temp[i] = std::min(temp[i], f[fx][i] + 1ll * w_ * cnt[fy]);
  }
  for (int j = 1; j <= sz[fy]; ++ j) {
    temp[j] = std::min(temp[j], f[fy][j] + 1ll * w_ * cnt[fx]);
  }

  fa[fx] = fa[fy] = fxy;
  sz[fxy] = sz[fx] + sz[fy];
  cnt[fxy] = cnt[fx] + cnt[fy];
  for (int i = 0; i <= n; ++ i) f[fxy][i] = temp[i];
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n >> m >> p;
    init();
    for (int i = 1; i <= n; ++ i) {
      for (int j = 0; j <= n; ++ j) f[i][j] = kInf;
      f[i][1] = 0;
    }
    for (int i = 1; i <= m; ++ i) {
      auto [u, v, w] = e[i];
      if (find(u) == find(v)) continue;
      merge(u, v, w);
    }
    for (int i = 1; i <= n; ++ i) std::cout << f[find(1)][i] << " ";
    std::cout << "\n";
  }
  return 0;
}
/*
1
3 3 2
3 1
1 2 1
2 3 3
1 3 2
*/

写在最后

参考:

学到了什么:

  • C2:排列可以随便映射;
  • D:限定选择的区间,转化为限定选择的区间中的某个位置;
  • E:两点间的贡献是路径上的最长边这个性质太典了,考虑 Kruscal 重构树。

标签:977,std,le,based,cin,int,kN,operatorname,Round
From: https://www.cnblogs.com/luckyblock/p/18451687

相关文章

  • Codeforces Round 316 (Div. 2) D题 Tree Requests(二分,dfs,在线,前缀异或)
    题目链接CodeforcesRound316(Div.2)D题TreeRequests思路将262626个字母全部当作一个二进制数。将每个深度的结点按照dfs序放到一个vector里,同时记录每个vector......
  • 中国大学生程序设计竞赛(秦皇岛)正式赛东北大学秦皇岛分校(SMU Autumn 2024 Team Round 1
    中国大学生程序设计竞赛(秦皇岛)正式赛东北大学秦皇岛分校(SMUAutumn2024TeamRound1)ProblemA.贵校是构造王国吗I思路官方题解很清晰明了。代码#include<bits/stdc++.h>usingnamespacestd;#defineintlonglong#defineendl'\n'#definePIIpair<int,int>cons......
  • SMU Autumn 2024 Personal Round 1
    SMUAutumn2024PersonalRound1前言拉了,后面有空再补补。A.LexString思路排序后取最小,记录连续取了几个,不要超过\(k\)个即可。代码#include<bits/stdc++.h>usingnamespacestd;usingi64=longlong;voidsolve(){ intn,m,k; cin>>n>>m>>k;......
  • Codeforces Round 969 (Div. 2)
    一万五参赛,赛时(VP)排名80A.Dora'sSet比较新的结论题。显然,一个合法三元组最多只能有一个偶数。每个偶数都可以跟相邻两个奇数组成合法三元组。因此,答案为奇数个数的二分之一(下取整)。#include<bits/stdc++.h>usingnamespacestd;intT,l,r;intmain(){ scanf("%d",......
  • Codeforces Round 977 (Div. 2)
    手速局,因为水平不够三题遗憾离场。A.MeaningMean题意你一个序列,你每次可以选择两个数删掉,并把他们的平均数加入到序列的末尾。当序列长度为\(1\)的时候,剩下的数最大值是多少。思路当时比赛的时候唐了,耽误了好几分钟。想的是先奇数和奇数相加,偶数和偶数相加,这样能避免下取......
  • Codeforces Rund 977 div2 个人题解(A~E1)
    CodeforcesRund977div2个人题解(A,B,C1,C2,E1)Dashboard-CodeforcesRound977(Div.2,basedonCOMPFEST16-FinalRound)-Codeforces火车头#define_CRT_SECURE_NO_WARNINGS1#include<algorithm>#include<array>#include<bitset>#includ......
  • Codeforces Round 977 (Div. 2, based on COMPFEST 16 - Final Round)
    CodeforcesRound977(Div.2,basedonCOMPFEST16-FinalRound)A.MeaningMean题目链接:Problem-A-Codeforces题目描述:给定一个包含(n)个正整数的数组(a)。你可以执行如下操作直到数组中只剩下一个元素:从数组中选择两个不同的元素(a_i)和(a_j......
  • CF 977 Review
    CF977Review掉大分了,我去,绿名也是可以掉分的,我去你简直太牛了sgh。我是真正的飞舞。A排序以后贪心或者直接优先队列模拟即可,都可以过。Code#include<bits/stdc++.h>usingnamespacestd;template<typenameT>inlinevoidre(T&x){ x=0;intf=1;charc=getchar(); wh......
  • 【CodeForces训练记录】Codeforces Round 977 (Div. 2, based on COMPFEST 16 - Final
    赛后反思做红温了,太菜了,每题都需要WA几次才能过,B题看到MEX选择性害怕,时间复杂度又算错了A题每次选择一对\(a_i,a_j\)把均值插入数组最后面,要想结果最大,对于两个数求均值,最后的结果一定是小于等于其中的较大值,我们可以考虑如何最大化最后一次操作,想到将最大值保留在最后再......
  • WPF ListBoxItem Selected and background changed at the same time via ItemContain
    <Window.Resources><Stylex:Key="lbxItemContainerStyle"TargetType="ListBoxItem"><SetterProperty="Template"><Setter.Value><ControlTemplateTargetType=&quo......