【复健试手】老年选手的挣扎
高考结束之后进行简单估分,觉得复读和上带学五五开,所以先把写代码的能力捡起来再说。
如果真的复读了又要停更一年了
这里都是简单题,建议初学者阅读学习。
不更新不代表我复读了!(有学弟问我是不是复读了 /kk )可能只是因为我去看梦华录和甄嬛传了
(或者去给 npy 帮忙。。
我 真 的 是 妹 子 不 是 伪 娘
upd: 确实能上带学,但是想学 cs 估计会很困难。。
CF1691E Number of Groups
大意:有一堆红色蓝色的线段,定义不同颜色的线段连通为当且仅当他们至少有一个公共点。问连通团数量。
开始想把线段按照颜色排序后再按照左端点排序,然后建立两棵线段树,对于每条线段在另一种颜色的线段树上找可以连通的线段,然后并查集维护一下并把多余的全都删掉,结果写了一下发现空间复杂度不行(用了一大把 vector )。
思考了一下决定用 set 维护,然后每次在另一个颜色的集合里二分一下始末,再把中间的全都合并,然后删除。复杂度大约是 ?(我也不太清楚)
但是发现可以更优化:由于现在线段已经有序,那么可以合并的线段肯定是一块块的。所以只需要维护一个队列,把待合并的扔进去,每次把可以合并的拉出来合并。这样复杂度应该是 的。
调了一个小时才发现加队列的时候颜色挂了
code
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=200005;
int T,n,fa[N],ans,top[2];
struct ky{
int l,r,c;
bool operator < (const ky&p)const{return r^p.r?r<p.r:(l^p.l?l<p.l:c<p.c);}
bool operator == (const ky&p)const{return l==p.l&&r==p.r&&c==p.c;}
}a[N],b[2][N];
int fd(int x){return fa[x]^x?fa[x]=fd(fa[x]):x;}
void mer(int u,int v){u=fd(u),v=fd(v),u^v?--ans,fa[u]=v:0;}
void work(int c,int l,int r,int id){
ky now=(ky) {-1,-1,-1};
if(top[c]) now=b[c][top[c]];
while(top[c]&&b[c][top[c]].r>=l) mer(b[c][top[c]].c,id),--top[c];
now.c!=-1?(((!top[c])||(!(now==b[c][top[c]])))?b[c][++top[c]]=now,0:0):0;
c^=1,b[c][++top[c]]=(ky) {l,r,id};
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n),ans=n,top[0]=top[1]=0;
for(int i=1;i<=n;++i) scanf("%d%d%d",&a[i].c,&a[i].l,&a[i].r),fa[i]=i;
sort(a+1,a+1+n);
for(int i=1;i<=n;++i) work(a[i].c^1,a[i].l,a[i].r,i);
printf("%d\n",ans);
}
return 0;
}
CF1684E MEX vs DIFF
大意:给定一个数组,有 次操作,每次把任意一个数改成非负整数,使 最小。 数组内不重复元素个数, 为数组内未出现的最小元素。
如果把一个出现多次的数改成 ,实则贡献为 ,所以尽量改出现一次的数。实际上 ,考虑枚举一下 ,看一下 需要填充的步数是否 ,并且找数填充,尽量找只出现一次的填充。用 维护一下权值和次数,每次尽量把出现多的拿出来补前面的空缺(用一个优先队列维护),然后计算 的时候要注意把用掉的除去(减去优先队列中等待被拿出来补缺补差的)。
又调了半天,老年选手真的救不活了
code
#include<cstdio>
#include<map>
#include<queue>
#include<algorithm>
using namespace std;
const int N=200005;
int n,k,a[N],T,tot;
map<int,int> cn;
priority_queue<int> q;
void Min(int &p,int q){p=(p<q?p:q);}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&k),tot=0,cn.clear();while(!q.empty()) q.pop();
for(int i=1;i<=n;++i) scanf("%d",&a[i]),++cn[a[i]];
for(int i=0;i<=n;++i) tot+=(cn.count(i)?0:1);
int ans=cn.size(),s=0,sz=ans;
for(auto i : cn) i.first>n?q.push(i.second),s+=i.second:0;
for(int i=n;i>=0;--i){
cn[i]?q.push(cn[i]),s+=cn[i]:--tot;
while(s>k&&(!q.empty())) s-=q.top(),q.pop();
if(k>=tot) Min(ans,(sz-q.size()+tot)-i);
}
printf("%d\n",ans);
}
return 0;
}
CF1681F Unique Occurrences
大意:求 到 路径上恰好出现一次的颜色数量。
线段树分治 并查集模板题,每种颜色边的贡献就是它连通的两个连通块的大小的积。(我觉得我初三暑假的时候应该会写,可惜现在我处于高三暑假)
只是锻炼一下代码能力而已。忘记把还原数组开成局部的又调了很久很久,翻阅以前代码才发现问题
code
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int N=500005;
typedef long long ll;
int n,a[N],u,v,fa[N],q[N],top,sz[N];
ll ans;
struct ky{
int u,v;
};
vector<ky> g[N],f[N<<2];
int fd(int x){while(fa[x]^x) x=fa[x];return x;}
void add(int rt,int L,int R,int l,int r){
if(L>=l&&R<=r) return f[rt].push_back({u,v});
int mid=L+R>>1;
l<=mid?add(rt<<1,L,mid,l,r),0:0;
r>mid?add(rt<<1|1,mid+1,R,l,r),0:0;
}
void que(int rt,int L,int R){
vector<int> q;
for(auto p : f[rt]){
u=fd(p.u),v=fd(p.v);
if(sz[u]>sz[v]) swap(u,v);
fa[u]=v,sz[v]+=sz[u],q.push_back(u);
}
if(L==R){
for(auto p : g[L]) ans+=(ll) sz[fd(p.u)]*sz[fd(p.v)];
reverse(q.begin(),q.end());for(auto x : q) sz[fa[x]]-=sz[x],fa[x]=x;
return;
}
int mid=L+R>>1;
que(rt<<1,L,mid),que(rt<<1|1,mid+1,R);
reverse(q.begin(),q.end());for(auto x : q) sz[fa[x]]-=sz[x],fa[x]=x;
}
int main(){
scanf("%d",&n);
for(int i=1,x;i<n;++i)
scanf("%d%d%d",&u,&v,&x),g[x].push_back({u,v}),fa[i]=i,sz[i]=1,
x>1?add(1,1,n,1,x-1),0:0,x<n?add(1,1,n,x+1,n),0:0;fa[n]=n,sz[n]=1;
que(1,1,n),printf("%lld",ans);
return 0;
}
CF1675G Sorting Pancakes
大意:有 个盘子, 个饼子,第 个盘子有 个饼子,每次只能把一个饼子挪到相邻的盘子里。问最少挪动次数使得 恒成立。
奇妙的 ,看着就是个 左右的 。设 表示 第 个箱子,前面的箱子一共放了 个饼子,第 个箱子放 个饼子。那么 ,很显然最小值可以随便维护一下,查询就是 的了。总复杂度是 的。实现的时候 数组似乎能滚掉一维。
这题怎么 2300 啊最多 1900 吧,2300 的难度把我骗进来是干啥的
code
#include<cstdio>
#include<cstring>
using namespace std;
const int N=255;
int n,m,a[N],f[2][N][N],s[N],g[2][N][N],ans;
int Abs(int x){return x<0?-x:x;}
void Min(int &p,int q){p=(p<q?p:q);}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) scanf("%d",&a[i]),s[i]=s[i-1]+a[i];
memset(f,63,sizeof(f)),ans=f[0][0][0];
for(int i=0;i<=m;++i) f[0][0][i]=0;
for(int i=1,cur=1,ruc=0;i<=n;++i,cur^=1,ruc^=1){
memset(f[cur],63,sizeof(f[cur]));
for(int j=0;j<=m;++j)
for(int k=0,w=Abs(s[i]-j);k<=j;++k)
Min(f[cur][j][k],g[ruc][j-k][k]+w);
memset(g[cur],63,sizeof(g[cur]));
for(int j=0;j<=m;++j)
for(int k=j;k>=0;--k)
g[cur][j][k]=g[cur][j][k+1],Min(g[cur][j][k],f[cur][j][k]);
}
n&=1;
for(int i=0;i<=m;++i) Min(ans,f[n][m][i]);
printf("%d",ans);
return 0;
}
CF1665E MinimizOR
大意:有一个 数组,每次给定 ,求 。
看到 ,开始想的是 但是没搞出来(实际上后来看到有老哥搞出来了,我比较菜)。然后观察了一下性质,现在需要每一位的 尽量少,可以考虑在区间选前 小的数出来在里面选两个(我一开始想的是前 小的,但是 WA
了,后来仔细想了想是前 小的,因为 实际上有 位,而如果取前 会忽略掉一些最优组合)。因为我们每一位都要尽量小的,比如 1000
和 111
相比,看起来后面的 1
多,但实际上数更小,也就是数更小的高位的 1
更少。所以随便维护一下区间内前 小的然后暴力枚举就行了。
code
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int N=100005;
vector<int> f[N<<2];
int a[N],n,Q,u,v,o,T;
void Min(int &p,int q){p=(p<q?p:q);}
void B(int rt,int L,int R){
if(L==R) return f[rt].push_back(a[L]);
int mid=L+R>>1,ls=rt<<1,rs=rt<<1|1;
B(ls,L,mid),B(rs,mid+1,R);
vector<int> vec=f[ls];
for(auto p : f[rs]) vec.push_back(p);
sort(vec.begin(),vec.end());
for(int i=0;i<vec.size()&&i<31;++i) f[rt].push_back(vec[i]);
}
vector<int> que(int rt,int L,int R){
if(L>=u&&R<=v) return f[rt];
int mid=L+R>>1,ls=rt<<1,rs=rt<<1|1;
vector<int> vec1,vec2,vec;
if(u<=mid) vec1=que(ls,L,mid);
if(v>mid) vec2=que(rs,mid+1,R);
for(auto p : vec2) vec1.push_back(p);
sort(vec1.begin(),vec1.end());
for(int i=0;i<vec1.size()&&i<31;++i) vec.push_back(vec1[i]);
return vec;
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
B(1,1,n),scanf("%d",&Q);
while(Q--){
scanf("%d%d",&u,&v),o=0x7fffffff;
vector<int> ans=que(1,1,n);
for(int i=0;i<ans.size();++i)
for(int j=i+1;j<ans.size();++j)
Min(o,ans[i]|ans[j]);
printf("%d\n",o);
}
for(int i=n<<2;i;--i) f[i].clear();
}
return 0;
}
CF1654E Arithmetic Operations
大意:问最少修改给定数组中多少个数才能让它变成等差数列
奇怪的题。。。感觉像是为了出题而出题。。。
由于 ,即 ,要修改最少的数,所以保留最多的数。所以 为 后的众数。求众数,又看到 ,根据经验基本可以断定这是个根号级别的做法。显然 ,所以合理分块算这个 就行了。
代码鸽了。
CF1634F Fibonacci Additions
大意:有两个数组 ,每次在 或 的区间 中每个数依次加上 ,并对给定的模数 取模,问每次操作后 是否相同。( 表示斐波那契数列第 项)
相同肯定不是一个个比,也就是要转化成区间。(比如转化成 ,且 的区间和为 ,区间积为 这种形式。)那么每次操作都是在区间上加减,考虑到 数列的性质:,考虑维护数组 ,如果 为 就自然为 了。
代码比较简单,实现的时候居然搞了半天。。。感觉自己药丸(
code
#include<cstdio>
const int N=1000005;
int n,Q,P,a[N],b[N],c[N],d[N],ans,f[N],m;
void add(int pos,int x){
if(pos>m) return ;
ans-=(!d[pos]),d[pos]=((d[pos]+x)%P+P)%P,ans+=(!d[pos]);
}
int main(){
scanf("%d%d%d",&n,&Q,&P),m=n+1;
for(int i=2;i<=m;++i) scanf("%d",&a[i]);
for(int i=2;i<=m;++i) scanf("%d",&b[i]),c[i]=((a[i]-b[i])%P+P)%P;
for(int i=(f[1]=f[2]=1)+2;i<=n+3;++i) f[i]=(f[i-1]+f[i-2])%P;
for(int i=2;i<=m;++i) d[i]=((c[i]-c[i-1]-c[i-2])%P+P)%P,ans+=(!d[i]);
while(Q--){
char C=getchar();while(C!='A'&&C!='B') C=getchar();
int u,v,x;scanf("%d%d",&u,&v),x=(C=='A'?1:-1),++u,++v;
add(u,x),add(v+1,(-x)*f[v-u+2]),add(v+2,(-x)*f[v-u+1]),ans==n?puts("YES"):puts("NO");
}
return 0;
}
CF446C DZY Loves Fibonacci Numbers
大意: 表示斐波那契数列第 项,现在要进行以下操作:
· 1 l r
,给 中每个数 加上 。
· 2 l r
,求
做上题的时候有题解推荐了这题,所以进来看看。想了半天没推出来什么性质,就想到用 数列的通项公式,就是:
然后线段树区间加等比数列,可以用求和公式。然后二次剩余又不太记得了,翻了题解看有老哥说有二次剩余就直接复制他的了
代码先鸽一小会儿。
code
CF1713E Cross Swapping
题意:每次可以交换有公共点的一行和一列,问怎样让矩阵字典序最小,输出最小的字典序。
震撼!鸽子居然更博了!
考虑这个图:
绿色和红色换,红色和紫色换,相当于绿色和紫色换。
也就是说行之间可以随便换,列之间可以随便换,行列之间可以随便换。
那就比较简单了,判断一下换合不合适并且记录就好了。
就是说,如果暴力换,次数不够用,现在只需要考虑行列之间要不要换,把换的步骤记下来一把换就行了。
怎么记录换的步骤最后一起换?
大概是这样:
最后执行的其实是:
发现这个有点像路径压缩??那就并查集
然后发现换了偶数次其实就是没换,所以记录一下路径长度(或者每次 xor
一下,或者用 表示啥的,都行),然后判断是否要换就行了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
2021-06-14 题解 Codeforces LATOKEN Round 1 (Div. 1 + Div. 2) (CF1534)