发现已经错过最美的花期|

ricky_lin

园龄:3年8个月粉丝:11关注:2

【题解】CF1830 合集

CF1830A Copil Copac Draws Trees

标签:思维题 \(C^-\)

你考虑对于每一条边打上时间标记,然后在树上 DFS 的时候维护一下以 \(u\) 为根的答案即可,然后将答案合并,反正很简单,看代码就懂。

code:

#include<bits/stdc++.h>
using namespace std;
const int NN = 2e5 + 8;
int t,n;
struct Edge{
int to,next,val;
}edge[NN << 1];
int head[NN],cnt;
void init(){
for(int i = 1; i <= n; ++i) head[i] = -1;
cnt = 1;
}
void add_edge(int u,int v,int w){
edge[++cnt] = {v,head[u],w};
head[u] = cnt;
}
int dfs(int u,int fa,int pre){
int ans = 0;
for(int i = head[u]; i != -1; i = edge[i].next){
int v = edge[i].to,val = edge[i].val;
if(v == fa) continue;
ans = max(ans,dfs(v,u,val) + (val < pre));
}
return ans;
}
int main(){
scanf("%d",&t);
while(t--){
scanf("%d",&n);init();
for(int i = 1,u,v; i < n; ++i) scanf("%d%d",&u,&v),add_edge(u,v,i),add_edge(v,u,i);
printf("%d\n",dfs(1,1,0)+1);
}
}

CF1830B The BOSS Can Count Pairs

标签:思维题 \(B^-\)

你考虑,我们观察数据范围,发现可以是 \(O(n\sqrt n) / O(n\log n)\) 的,我们又看到乘法,便有几个大概的想法:

  • 数论分块
  • \(O(\sqrt n)\) 枚举其中一个乘数
  • 还有什么……(笔者学识浅陋,读者可以帮忙补充)

我们可以找到两种 \(O(n^2)\) 做法:

  • \(O(n^2)\) 枚举数对 \((i,j)\) 然后进行判断。(这个很好想,就是平常的暴力)
  • \(O(n^2)\) 一个 \(n\) 枚举 \(a_i\) 的值,并将 \(b_i\) 记录在桶中,另一个 \(n\) 枚举 \(j\) 并在桶中查找 \(a_i \times a_i - b_j\) (有时候换一种枚举方式 确实不失为一种打开新思路的好方法)

我们可以发现 \(a_i \times a_j\) 是不大于 \(2n\) 的,所以里面最小的数是不大于 \(\sqrt {2n}\) 的,然后我们就可以将第一维从 \(O(n)\) 变为 \(O(\sqrt n)\)

当然细节上还是需要处理一下,因为每个数对只能出现一次,所以我们让 \(a\) 小的在前面,\(a\) 大的在后面,\(a\) 相同再是按下标(说得有点玄乎?看不懂可以直接看代码,代码还是很好懂的)。

然后我们就做完了这道题。

code:

#include<bits/stdc++.h>
using namespace std;
const int NN = 2e5 + 8;
typedef long long ll;
int T;
int n;
int a[NN],b[NN];
int cnt[NN << 1];
int main(){
scanf("%d",&T);
while(T--){
ll ans = 0;
scanf("%d",&n);
for(int i = 1; i <= n; ++i) scanf("%d",&a[i]);
for(int i = 1; i <= n; ++i) scanf("%d",&b[i]);
int c = sqrt(2 * n) + 1;
for(int j = 1; j <= c; ++j){
for(int i = 0; i <= n; ++i) cnt[i] = 0;
for(int i = 1; i <= n; ++i) if(a[i] == j){
if(j*j - b[i] >= 0 && j * j - b[i] <= n) ans += cnt[j*j-b[i]];
++cnt[b[i]];
}
for(int i = 1; i <= n; ++i){
if(a[i]*j-b[i] >= 0 && a[i] > j && a[i] * j - b[i] <= n) ans += cnt[a[i]*j-b[i]];
}
}
printf("%lld\n",ans);
}
}

CF1830C Hyperregular Bracket Strings

标签:思维题 \(B\) | 杂项 \(B^-\)

我们知道,一个长度为 \(n\) 的合法括号序列的种数就是第 \(\frac n 2\) 个卡特兰数(当然 \(n\) 是奇数答案肯定就是 \(0\)

我们可以发现一件事情,如果两个区间相互包含,那么就可以将大区间分为中间被包含的小区间的部分和外面没有被小区间覆盖的部分。

如果两个区间相交,那么就可以分为三个部分,左半部分、中间相交部分、右半部分。

以上的所有 部分 都要满足是一个合法的括号序列才能满足条件。

但是很显然,题目给出的数据肯定会有一堆相交和包含的关系,我们需要将其理清。

我们可以发现两个位置在同一个 部分 当且仅当覆盖其上的区间完全相同,那么我们可以对每个区间一个特征值,然后使用 Xor-Hash 做到将所有关系理清,然后用 \(Map/Set\) 存储每一个 Hash 值的数量,最后将所有的卡特兰数相乘即可。

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int NN = 6e5 + 8, MOD = 998244353;
int n,k;
int t;
ll a[NN];
ll fac[NN],inv[NN],cat[NN];
map<ll,ll> vis;
ll ksm(ll x,ll k){
ll res = 1;
while(k){
if(k & 1) res = res * x % MOD;
x = x * x % MOD;
k >>= 1;
}
return res;
}
ll binom(int n,int m){
if(n < m) return 0;
return fac[n] * inv[m] % MOD * inv[n-m] % MOD;
}
int main(){
mt19937 rnd(time(0));
scanf("%d",&t);
fac[0] = inv[0] = 1;
for(int i = 1; i <= NN - 2; ++i) fac[i] = fac[i-1] * i % MOD;
inv[NN - 2] = ksm(fac[NN - 2], MOD - 2);
for(int i = NN - 2; i >= 1; --i) inv[i-1] = inv[i] * i % MOD;
for(int i = 0; i <= NN - 2; ++i) cat[i] = binom(2 * i,i) * ksm(i + 1,MOD - 2) % MOD;
while(t--){
vis.clear();
scanf("%d%d",&n,&k);
for(int i = 1; i <= n; i++) a[i] = 0;
for(ll i = 1,l, r; i <= k; i++) {
scanf("%lld%lld",&l,&r);
ll t = 1ll * rnd() * rnd() ^ rnd();
a[l] ^= t, a[r + 1] ^= t;
}
ll t = 1ll * rnd() * rnd() ^ rnd();
a[1] ^= t, a[n + 1] ^= t;
for(int i = 1; i <= n; i++) a[i] = a[i] ^ a[i - 1], ++vis[a[i]];
ll ans = 1;
for(auto i : vis) {
int cnt = i.second;
if (cnt % 2 == 1) ans = 0;
else ans = ans * cat[cnt / 2] % MOD;
}
printf("%lld\n",ans);
}
}

CF1830D Mex Tree

标签:思维题 \(A^-\) | DP \(B\)

我们考虑这道题一看题就特别难受,所有路径?\(mex\) 之和?这是什么东西?

我们考虑 \(mex\) 之和其实是有一点诈骗的感觉,毕竟是 \(0\)\(1\),还比较简单。就是路径上全都是 \(1\) 的时候是 \(0\),全都是 \(0\) 的时候是 \(1\),有 \(0\)\(1\) 的时候是 \(2\)

\(n \leq 2 \times 10^5\) 显然是有点 DP 的影子,但是我们想设出一个状态还是很难的,毕竟很难规避它的后效性的问题,子树内的路径即使在子树内不优,但是有可能在外面更优。

如果真的记下当前的 只含 \(0\)、只含 \(1\)、含 \(0\)\(1\) 的路径的个数,显然空间爆炸。

你考虑正难则反,我们考虑什么时候会损失 \(mex\) 之和,显然是链全为 \(1\) 或者全为 \(0\)

我们正常 DP,设 \(f_{u,j,0/1}\) 表示以 \(u\) 为根的子树,当前点放的是 \(0/1\),和点 \(u\) 相同颜色的和 \(u\) 直接相连的点的个数为 \(j\),当前最少的损失是多少。

转移式子如下:

\[\begin{cases} f_{u,j,0}=\min (f_{u,j,0}+\min\limits_{k}\{f_{v,k,1}\},\min\limits_{k}\{f_{u,k,0}+f_{v,j-k,0}+j(j-k)\})\\ f_{u,j,1}=\min (f_{u,j,1}+\min\limits_{k}\{f_{v,k,0}\},\min\limits_{k}\{f_{u,k,1}+f_{v,j-k,1}+2j(j-k)\})\\ \end{cases} \]

  • 为什么是 \(j(j-k)\):考虑显然被影响的链的数量为 \(j(j-k)\)
  • 为什么加上了 \(\min\limits_{k}\{f_{v,k,0/1}\}\):因为左半部分计算的是当前儿子染异色,右半部分是染同色。
  • 时间复杂度?:因为显然我们通过贪心之类的 算法可以构造出一个较优的解,就是进行黑白染色,然后可以发现所有长度大于等于 \(2\) 的链都是 \(2\) 的贡献,所以最多缺少的是 \(2n\),所以实际上 \(j\) 这一维最多只需要开到 \(\sqrt {2n}\),这个时候只需要代码优秀再加上使用优秀的 c++17 即可通过,好像 luogu 上有 \(j\) 这一维只需要开到 \(300\) 的证明,有兴趣可以自己去看一看。

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll NN = 2e5 + 8,B = 300,INF = 1e18;
vector<ll> f[NN][2];
int n;
struct Edge{
int to,next;
}edge[NN << 1];
int head[NN],cnt;
void init(){
for(int i = 1; i <= n; ++i) head[i] = -1;
cnt = 1;
}
void add_edge(int u,int v){
edge[++cnt] = {v,head[u]};
head[u] = cnt;
}
ll siz[NN];
ll g[2][300];
void dfs(int u,int fa){
siz[u] = 1;
f[u][0].resize(2);f[u][1].resize(2);
f[u][0][0] = f[u][1][0] = INF;f[u][0][1] = 1;f[u][1][1] = 2;
for(int i = head[u]; i != -1; i = edge[i].next){
int v = edge[i].to;
if(fa == v) continue;
dfs(v,u);
siz[u] += siz[v];
ll min0 = INF,min1 = INF;
for(int j = 1; j <= min(B,siz[v]); ++j) min0 = min(min0,f[v][0][j]),min1 = min(min1,f[v][1][j]);
for(int j = 1; j <= min(B,siz[u]-siz[v]); ++j) g[1][j] = f[u][1][j],g[0][j] = f[u][0][j];
f[u][0].resize(min(B,siz[u])+1);f[u][1].resize(min(B,siz[u])+1);
for(int j = 1; j <= min(B,siz[u]); ++j) f[u][0][j] = f[u][1][j] = INF;
for(ll j = 1; j <= min(B,siz[u]); ++j){
for(int k = max(1ll,j-min(B,siz[u]-siz[v])); k <= min(j-1,min(B,siz[v])); ++k){
f[u][0][j] = min(f[u][0][j], g[0][j-k] + f[v][0][k] + k * (j-k));
f[u][1][j] = min(f[u][1][j], g[1][j-k] + f[v][1][k] + 2 * k * (j-k));
}
}
for(int j = 1; j <= min(B,siz[u]-siz[v]); ++j){
f[u][0][j] = min(g[0][j]+min1,f[u][0][j]);
f[u][1][j] = min(g[1][j]+min0,f[u][1][j]);
}
f[v][0].clear(); f[v][0].shrink_to_fit(); f[v][1].clear(); f[v][1].shrink_to_fit();
}
}
int t;
int main(){
scanf("%d",&t);
while(t--){
scanf("%d",&n);init();
for(int i = 1,u,v; i < n; ++i){
f[i][0].clear();f[i][1].clear();
scanf("%d%d",&u,&v);
add_edge(u,v);add_edge(v,u);
}
f[n][0].clear();f[n][1].clear();
dfs(1,1);
ll ans = INF;
for(int i = 1; i <= min(B,siz[1]); ++i) ans = min(ans,min(f[1][0][i],f[1][1][i]));
printf("%lld\n",1ll * n * (n+1) - ans);
}
}

CF1830E Bully Sort

标签:思维题 \(S\) | 杂项 \(B^+\)

考虑一次交换,我们发现,被选出来的 \([i,j]\) 的区间里 \(p_i\) 一定是最大的,\(p_j\) 一定是最小的。

然后我们会发现,我们原序列的逆序对数量会减少 \(2(j-i) - 1\),而 \(\sum|p_i-i|\) 会减少 \(2(j-i)\)

那么答案就是原序列的两部分相减(神奇的性质又增加了!)。

至于我们的后半部分显然是很好维护的,而逆序对数量只需要使用三位偏序求解即可。

yes,搞定!

code:

#include<bits/stdc++.h>
using namespace std;
const int NN = 5e5 + 8;
typedef long long ll;
int n,m,tot;
int p[NN];
ll res[NN];
ll ans[NN];
struct Node{
int t,x,y,val;
bool operator < (const Node &A){
return x < A.x;
}
}q[NN << 1],Q[NN << 1];
int a[NN];
inline lowbit(int x){return x & (-x);}
void add(int x,int num){
while(x <= n){
a[x] += num;
x += lowbit(x);
}
}
int ask(int x){
int res = 0;
while(x > 0){
res += a[x];
x -= lowbit(x);
}
return res;
}
void solve(int l,int r){
if(l == r) return;
int mid = (l + r) / 2;
solve(l,mid);solve(mid+1,r);
int i = l,j = mid + 1,k = l;
while(i <= mid && j <= r){
if(q[i].x <= q[j].x) add(q[i].y,q[i].val),Q[k++] = q[i++];
else ans[q[j].t] += (ask(n) - ask(q[j].y)) * q[j].val,Q[k++] = q[j++];
}
while(i <= mid) add(q[i].y,q[i].val),Q[k++] = q[i++];
while(j <= r) ans[q[j].t] += (ask(n) - ask(q[j].y)) * q[j].val,Q[k++] = q[j++];
for(int i = l; i <= mid; ++i) add(q[i].y,-q[i].val);
i = mid,j = r;
while(i >= l && j > mid){
if(q[i].x >= q[j].x) add(q[i].y,q[i].val),--i;
else ans[q[j].t] += ask(q[j].y-1) * q[j].val,--j;
}
while(i >= l) add(q[i].y,q[i].val),--i;
while(j > mid) ans[q[j].t] += ask(q[j].y-1) * q[j].val,--j;
for(int i = l; i <= mid; ++i) add(q[i].y,-q[i].val);
for(int i = l; i <= r; ++i) q[i] = Q[i];
}
int main(){
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; ++i){
scanf("%d",&p[i]);
q[++tot] = {0,i,p[i],1};
res[0] += abs(p[i] - i);
}
for(int i = 1,x,y; i <= m; ++i){
scanf("%d%d",&x,&y);
res[i] = res[i-1];
res[i] -= abs(p[x]-x) + abs(p[y]-y);
q[++tot] = {i,x,p[x],-1}, q[++tot] = {i,y,p[y],-1};
swap(p[x],p[y]);
res[i] += abs(p[x]-x) + abs(p[y]-y);
q[++tot]={i,x,p[x],1},q[++tot]={i,y,p[y],1};
}
solve(1,tot);
for(int i = 1; i <= m; ++i){
ans[i] += ans[i-1];
printf("%lld\n",res[i] - ans[i]);
}
}
posted @   ricky_lin  阅读(51)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起
  1. 1 有我 周深
有我 - 周深
00:00 / 00:00
An audio error has occurred.

作词 : 唐恬/闫光宇

作曲 : 钱雷

编曲 : 赵兆/付虹宇

制作人 : 赵兆

出品 : 共青团中央宣传部

版权 : 中国青少年新媒体协会

制作单位 : 能量悦动音乐

发行单位 : 银河方舟StarNation

出品人 : 郭峰

总监制 : 汤杰

总策划 : 钟亚楠

总统筹 : 金慧子

音乐监制 : 李天鹏/李三木

制作执行 : 张不贰/高聪怡

项目宣发 : 肖健/张国党/孙小千/戴胤/孙雯璟

音乐推广 : 代诗琪/杜思潮/马越/程铁峰/傅之豪

钢琴 : 赵兆

吉他 : 伍凌枫

贝斯 : 韩阳

鼓 : 武勇恒

合唱设计 : 赵兆

合唱 : 凡尔赛合唱团

人声录音 : 耿潇微

人声录音室 : 55TEC Studio Beijing

配唱 : 徐威@52Hz Studio (Shanghai)

混音 : 李游(小骷髅)@55TEC Studio Beijing

海报 : 格子

特别鸣谢 : 周深工作室

世界问 你是谁 来自哪 请回答

爱什么 梦什么 去何方 请回答

答案有 一百年的时光

我来自 硝烟中 课桌旁 的太阳

我来自 硝烟中 课桌旁 的太阳

他和她 宣的誓 迎的仗

来自那 燃烧的 和我一样 的年华

来自世间 一对平凡的夫妻 身旁

来自世间 一对平凡的夫妻 身旁

来自昨天 谁以青春赴万丈 理想

我是寸土 不让的 家乡啊

我是绝不 低头的 倔强啊

接过万千热血 的初衷

当有对答世界 的音量

要怎么形容明天 像我一样

要怎么形容明天 像我一样

承风骨亦有锋芒 有梦则刚

去何方 去最高 的想象

前往皓月星辰 初心不忘

那未来如何登场 有我担当

那未来如何登场 有我担当

定是你只能叫好 那种辉光

护身旁 战远方 有我啊

我的名字就是 站立的地方

Wu~

我的样子 就是 明天的模样

我是朝阳 落在乡间听书声 朗朗

我是朝阳 落在乡间听书声 朗朗

我是屏障 为谁挡一程厄运 的墙

我要一生 清澈地 爱着啊

我要长歌 领着风 踏着浪

朝着星辰大海 的方向

当有对答世界 的音量

要怎么形容明天 像我一样

要怎么形容明天 像我一样

承风骨亦有锋芒 有梦则刚

去远方 去最高 的想象

前往皓月星辰 初心不忘

那未来如何登场 有我担当

那未来如何登场 有我担当

定是你只能叫好 那种辉光

护身旁 战远方 有我啊

一生骄傲为我 站立的地方

Wu~

我的样子 就是 中国的模样

Wu~~~ Wu~~~

当炬火 去化作那道光

“谨以此歌献给一代代不负时代重托的中国青年”

“谨以此歌献给一代代不负时代重托的中国青年”