[Codeforces]Round 656 div3 题解(A-E)
Before the Beginning
转载请将本段放在文章开头显眼处,如有二次创作请标明。
原文链接:https://www.codein.icu/cf1385/
前言
放假后的第一场比赛,深夜场。
Div3开小号打了,开始状态奇差,后来还可以,A-E都一发过了,没罚多少时,但开局不利,Rank没进500.
整体有一定的思维难度,质量不错的题目。
A
这次的 A 题做出人数比 B 题还少,卡了我20min……
给出,, 要求找到任意一组符合条件的
一开始想分类讨论没想清楚,后来官方发了个 Announcement 说输出顺序任意,就想到怎么打了。
首先可以确定最大的数,确定最大的数后,最大的数一定出现两次。
那么剩下那个数就是第二大的数,最小的数取 即可。
#include <cstdio> #include <algorithm> #include <ctype.h> const int bufSize = 1e6; inline char nc() { #ifdef DEBUG return getchar(); #endif static char buf[bufSize], *p1 = buf, *p2 = buf; return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, bufSize, stdin), p1 == p2) ? EOF : *p1++; } inline void read(char *s) { static char c; for (; !isalpha(c); c = nc()); for (; isalpha(c); c = nc()) *s++ = c; *s = '\0'; } template<typename T> inline T read(T &r) { static char c; static int flag; flag = 1, r = 0; for (c = nc(); !isdigit(c); c = nc()) if (c == '-') flag = -1; for (; isdigit(c); c = nc()) r = r * 10 + c - 48; return r *= flag; } int T; int a[4]; int main() { read(T); while(T--) { for(int i = 1;i<=3;++i) read(a[i]); std::sort(a + 1,a + 4); int maxx = a[3]; if(a[2] != a[3]) { puts("NO"); goto end; } puts("YES"); printf("%d %d %d\n",maxx,a[1],1); end:; } return 0; }
B
B 题的数据范围看上去似乎想让人用暴力,但其实是个 的结论题。
观察样例发现,只取序列中首次出现的数,就能还原数组。现在我们证明这个结论:
第一个数一定是原数组的第一个数。
将数组中所有出现的第一个数删除后,原先偏序不变。
随后问题即缩小成子问题,依次求解。
删除过程等价于打个值标记,每次取第一个数其实就是顺序遍历。
#include <cstdio> #include <algorithm> #include <ctype.h> const int bufSize = 1e6; inline char nc() { #ifdef DEBUG return getchar(); #endif static char buf[bufSize], *p1 = buf, *p2 = buf; return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, bufSize, stdin), p1 == p2) ? EOF : *p1++; } inline void read(char *s) { static char c; for (; !isalpha(c); c = nc()); for (; isalpha(c); c = nc()) *s++ = c; *s = '\0'; } template<typename T> inline T read(T &r) { static char c; static int flag; flag = 1, r = 0; for (c = nc(); !isdigit(c); c = nc()) if (c == '-') flag = -1; for (; isdigit(c); c = nc()) r = r * 10 + c - 48; return r *= flag; } int T,n; const int maxn = 120; int a[maxn],vis[maxn]; int main() { read(T); while(T--) { read(n); for(int i = 1;i<=n;++i) vis[i] = 0; for (int i = 1; i <= n * 2; ++i) { int x; read(x); if(!vis[x]) vis[x] = 1,printf("%d ",x); } putchar('\n'); } return 0; }
c
首先考虑 数组的性质,要求每次取头尾元素,所取元素单调不减。
那么一定存在一个峰点,峰点左边元素单调不减,峰点右边元素单调不增。
否则,如果存在双峰点,那么某个峰点内侧的元素就会比其小,从而不满足要求。
那么解法就简单了,从后往前找到第一个峰点,从峰点向前找到第二个峰点,删除第二个峰点即其左边的元素即可。
#include <cstdio> #include <algorithm> #include <ctype.h> const int bufSize = 1e6; inline char nc() { #ifdef DEBUG return getchar(); #endif static char buf[bufSize], *p1 = buf, *p2 = buf; return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, bufSize, stdin), p1 == p2) ? EOF : *p1++; } inline void read(char *s) { static char c; for (; !isalpha(c); c = nc()); for (; isalpha(c); c = nc()) *s++ = c; *s = '\0'; } template<typename T> inline T read(T &r) { static char c; static int flag; flag = 1, r = 0; for (c = nc(); !isdigit(c); c = nc()) if (c == '-') flag = -1; for (; isdigit(c); c = nc()) r = r * 10 + c - 48; return r *= flag; } const int maxn = 2e5 + 100; int T,n; int a[maxn],f[maxn]; int front[maxn],back[maxn]; int main() { read(T); while(T--) { read(n); for (int i = 1; i <= n; ++i) read(a[i]); int top = n; while(top > 1 && a[top - 1] >= a[top]) --top; int head = top; while(head > 1&& a[head - 1] <= a[head]) --head; printf("%d\n",head - 1); } return 0; }
D
阅读题面,发现好串的定义就是在提示使用分治做法。
每次可以假设左边全是 或右边全是 ,计算相应的代价,取最小值即可。
这样处理过程就像遍历一棵线段树一样,复杂度 。
计算代价可以用前缀和快速处理。
#include <cstdio> #include <algorithm> #include <ctype.h> #define int long long const int bufSize = 1e6; inline char nc() { #ifdef DEBUG return getchar(); #endif static char buf[bufSize], *p1 = buf, *p2 = buf; return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, bufSize, stdin), p1 == p2) ? EOF : *p1++; } inline void read(char *s) { static char c; for (; !isalpha(c); c = nc()); for (; isalpha(c); c = nc()) *s++ = c; *s = '\0'; } template<typename T> inline T read(T &r) { static char c; static int flag; flag = 1, r = 0; for (c = nc(); !isdigit(c); c = nc()) if (c == '-') flag = -1; for (; isdigit(c); c = nc()) r = r * 10 + c - 48; return r *= flag; } const int maxn = 2e5 + 100; char s[maxn]; int sum[maxn][26]; int T,n; inline int solve(int l,int r,int c) { if(l == r) return c != s[l]; int mid = l + r >> 1; int left = solve(l,mid,c + 1),right = solve(mid + 1,r,c + 1); int lval = left + r - mid - (sum[r][c] - sum[mid][c]); int rval = right + (mid - l + 1) - (sum[mid][c] - sum[l - 1][c]); return std::min(lval,rval); } signed main() { read(T); while(T--) { read(n); read(s + 1); for(int i = 1;i<=n;++i) s[i] -= 'a'; for (int i = 1; i <= n; ++i) { for (int j = 0; j < 26; ++j) sum[i][j] = sum[i - 1][j]; sum[i][s[i]]++; } printf("%lld\n",solve(1,n,0)); } return 0; }
E
给定一个有向图,给一些无向边,要为无向边决定方向,使最终图是个有向无环图。
一开始想在原图上乱搜一通,然后试错法乱选方向,样例都没过去……
但可以发现,无论如何,都是从给定的有向图的性质入手。
如果原图含有环,一定是 NO,否则一定是 YES。
有向无环图才有拓扑排序,而如果最终图有拓扑排序,它就是有向无环图。
对原图进行拓扑排序,顺便判环。
对于每条无向边,按照拓扑排序的顺序决定方向即可。
代码写得乱了点。
#include <cstdio> #include <algorithm> #include <ctype.h> const int bufSize = 1e6; inline char nc() { #ifdef DEBUG return getchar(); #endif static char buf[bufSize], *p1 = buf, *p2 = buf; return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, bufSize, stdin), p1 == p2) ? EOF : *p1++; } inline void read(char *s) { static char c; for (; !isalpha(c); c = nc()); for (; isalpha(c); c = nc()) *s++ = c; *s = '\0'; } template <typename T> inline T read(T &r) { static char c; static int flag; flag = 1, r = 0; for (c = nc(); !isdigit(c); c = nc()) if (c == '-') flag = -1; for (; isdigit(c); c = nc()) r = r * 10 + c - 48; return r *= flag; } const int maxn = 2e5 + 100, maxm = maxn << 1; struct node { int from, to, next, directed; } E[maxm]; int head[maxn], tot; inline void add(const int &x, const int &y, const int &t) { E[++tot].next = head[x], E[tot].from = x, E[tot].to = y, E[tot].directed = t, head[x] = tot; } int T, n, m; int in[maxn]; int q[maxn], qh, qt, id[maxn], vis[maxn], cnt, ans; inline void topo() { qh = 1, qt = 0; for (int i = 1; i <= n; ++i) if (!in[i]) q[++qt] = i; while (qt >= qh) { if(ans) return; int u = q[qh++]; id[u] = ++cnt, vis[u]++; // printf("inq:%d\n",u); for (int p = head[u]; p; p = E[p].next) { if (!E[p].directed) continue; int v = E[p].to; if (--in[v] == 0) q[++qt] = v; if (in[v] < 0) ans = 1; // printf("in:%d %d\n",v,in[v]); } } // for(int i = 1;i<=n;++i) printf("%d %d %d\n",i,id[i],vis[i]); } int main() { read(T); while (T--) { ans = tot = cnt = 0; read(n), read(m); for (int i = 1; i <= n; ++i) vis[i] = head[i] = 0, in[i] = 0; for (int i = 1; i <= m; ++i) { int t, a, b; read(t), read(a), read(b); add(a, b, t); if (t) in[b]++; } topo(); if(ans) {puts("NO"); goto end;} for (int i = 1; i <= n; ++i) if (!id[i] || vis[i] != 1){puts("NO");goto end;} puts("YES"); for (int i = 1; i <= m; ++i) { if (E[i].directed) printf("%d %d\n", E[i].from, E[i].to); else if (id[E[i].from] < id[E[i].to]) printf("%d %d\n", E[i].from, E[i].to); else printf("%d %d\n", E[i].to, E[i].from); } continue; end:; } return 0; }
F
这题赛中没做出来,现在也没来得及补正解,随便口胡两句。
该解法是错误的,第二个pretest就WA了QAQ
主要原因是看错题了。要求删除的叶子都在同一点上,我以为是任选……
以下解法看看就好,愚蠢的代码也贴上来吧。
每次删除恰好 个叶子,很容易让人想到贪心。
如果一个节点恰好与 个叶子相连,那么删除这 个叶子后它将成为叶子。将这种即为一类点。
将所有一类点放入堆中,按 升序排序。
也将所有与叶子相连的、不全与叶子相连的点放入堆中,优先级最低,即放在尾部。将这类点记为二类点。
选取 个叶子时,从堆顶取出:
如果 ,那么该节点会成为叶子,检查它的父亲是否能成为一类点,如果可以放入堆中,否则作为二类点放入堆中。
如果 ,那么该节点会取部分,且当前次取叶子成功。更新在堆中的状态。
用配对堆来维护即可。
#include <cstdio> #include <ext/pb_ds/priority_queue.hpp> #include <algorithm> #include <ctype.h> const int bufSize = 1e6; using namespace std; using namespace __gnu_pbds; inline char nc() { #ifdef DEBUG return getchar(); #endif static char buf[bufSize], *p1 = buf, *p2 = buf; return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, bufSize, stdin), p1 == p2) ? EOF : *p1++; } inline void read(char *s) { static char c; for (; !isalpha(c); c = nc()); for (; isalpha(c); c = nc()) *s++ = c; *s = '\0'; } template<typename T> inline T read(T &r) { static char c; static int flag; flag = 1, r = 0; for (c = nc(); !isdigit(c); c = nc()) if (c == '-') flag = -1; for (; isdigit(c); c = nc()) r = r * 10 + c - 48; return r *= flag; } const int maxn = 2e5 + 100; int fa[maxn],sonnum[maxn],leafnum[maxn]; struct node { int x,id,type; bool operator<(const node &that) const { if(this->type != that.type) return this->type > that.type; return leafnum[id] > leafnum[that.id]; } }; int T,n,k; struct edge { int to,next; }E[maxn << 1]; int head[maxn],tot; inline void add(const int &x,const int &y) { E[++tot].next = head[x], head[x] = tot, E[tot].to = y; } __gnu_pbds::priority_queue<node,less<node>,pairing_heap_tag> q; __gnu_pbds::priority_queue<node,less<node>,pairing_heap_tag>::point_iterator vis[maxn]; void dfs(int u) { if (!head[u]) leafnum[fa[u]]++; for (int p = head[u]; p; p = E[p].next) { int v = E[p].to; if(v == fa[u]) continue; fa[v] = u,dfs(v); sonnum[u]++; } if(sonnum[u] == leafnum[u]) { node t; t.x = leafnum[u],t.id = u,t.type = 0; vis[u] = q.push(t); } else if(leafnum[u]) { node t; t.x = leafnum[u],t.id = u,t.type = 1; vis[u] = q.push(t); } } inline bool take(int now) { while(!q.empty()) { node u = q.top(); if(now >= u.x) { //拿光当前点情况 q.pop(); now -= u.x; if(!u.type) { sonnum[u.id] = leafnum[u.id] = 0; int v = fa[u.id]; leafnum[v]++; if(leafnum[v] == sonnum[v]) { node t; t.x = leafnum[v],t.type = 0,t.id = v; if(vis[v] == 0) vis[v] = q.push(t); else q.modify(vis[v],t); } else if(leafnum[v] < sonnum[v]) { node t; t.x = leafnum[v],t.type = 1,t.id = v; if(vis[v] == 0) vis[v] = q.push(t); else q.modify(vis[v],t); } } else sonnum[u.id] -= u.x, leafnum[u.id] -= u.x; if(now == 0) return true; } else { //部分取走情况 leafnum[u.id] -= now, sonnum[u.id] -= now; node t; t.id = u.id, t.x = leafnum[u.id], t.type = u.type; if(vis[u.id] == 0) vis[u.id] = q.push(t); else q.modify(vis[u.id], t); return true; } } return false; } int main() { read(T); while(T--) { tot = 0; read(n), read(k); for(int i = 1;i<=n;++i) vis[i] = 0,head[i] = 0,sonnum[i] = leafnum[i] = 0,fa[i] = 0; while(!q.empty()) q.pop(); for (int i = 1; i < n; ++i) { int a, b; read(a), read(b); add(a, b), add(b, a); } dfs(1); int ans = 0; while(take(k)) ++ans; printf("%d\n",ans); } return 0; }
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET 原生驾驭 AI 新基建实战系列:向量数据库的应用与畅想
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
· 一次Java后端服务间歇性响应慢的问题排查记录
· dotnet 源代码生成器分析器入门
· ASP.NET Core 模型验证消息的本地化新姿势
· 从零开始开发一个 MCP Server!
· ThreeJs-16智慧城市项目(重磅以及未来发展ai)
· .NET 原生驾驭 AI 新基建实战系列(一):向量数据库的应用与畅想
· Ai满嘴顺口溜,想考研?浪费我几个小时
· Browser-use 详细介绍&使用文档