【复健试手】老年选手的挣扎

高考结束之后进行简单估分,觉得复读和上带学五五开,所以先把写代码的能力捡起来再说。如果真的复读了又要停更一年了

这里都是简单题,建议初学者阅读学习。

不更新不代表我复读了!(有学弟问我是不是复读了 /kk )可能只是因为我去看梦华录和甄嬛传了 (或者去给 npy 帮忙。。

我 真 的 是 妹 子 不 是 伪 娘

upd: 确实能上带学,但是想学 cs 估计会很困难。。

CF1691E Number of Groups

题面

大意:有一堆红色蓝色的线段,定义不同颜色的线段连通为当且仅当他们至少有一个公共点。问连通团数量。

开始想把线段按照颜色排序后再按照左端点排序,然后建立两棵线段树,对于每条线段在另一种颜色的线段树上找可以连通的线段,然后并查集维护一下并把多余的全都删掉,结果写了一下发现空间复杂度不行(用了一大把 vector )。

思考了一下决定用 set 维护,然后每次在另一个颜色的集合里二分一下始末,再把中间的全都合并,然后删除。复杂度大约是 O(nlogn) ?(我也不太清楚)

但是发现可以更优化:由于现在线段已经有序,那么可以合并的线段肯定是一块块的。所以只需要维护一个队列,把待合并的扔进去,每次把可以合并的拉出来合并。这样复杂度应该是 O(n) 的。

调了一个小时才发现加队列的时候颜色挂了

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

题面

大意:给定一个数组,有 k 次操作,每次把任意一个数改成非负整数,使 difmex 最小。dif= 数组内不重复元素个数,mex 为数组内未出现的最小元素。

如果把一个出现多次的数改成 mex ,实则贡献为 0,所以尽量改出现一次的数。实际上 mexn ,考虑枚举一下 mex ,看一下 0mex1 需要填充的步数是否 k ,并且找数填充,尽量找只出现一次的填充。用 map 维护一下权值和次数,每次尽量把出现多的拿出来补前面的空缺(用一个优先队列维护),然后计算 dif 的时候要注意把用掉的除去(减去优先队列中等待被拿出来补缺补差的)。

又调了半天,老年选手真的救不活了

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

题面

大意:求 i=1nj=i+1ns(i,j),s(i,j)=ij 路径上恰好出现一次的颜色数量。

线段树分治 + 并查集模板题,每种颜色边的贡献就是它连通的两个连通块的大小的积。(我觉得我初三暑假的时候应该会写,可惜现在我处于高三暑假)

只是锻炼一下代码能力而已。忘记把还原数组开成局部的又调了很久很久,翻阅以前代码才发现问题

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

题面

大意:有 n 个盘子,m 个饼子,第 i 个盘子有 ai 个饼子,每次只能把一个饼子挪到相邻的盘子里。问最少挪动次数使得 aiai+1 恒成立。

奇妙的 n,m250 ,看着就是个 n3 左右的 dp 。设 f[i][j][k] 表示 第 i 个箱子,前面的箱子一共放了 j 个饼子,第 i 个箱子放 k 个饼子。那么 f[i][j][k]=mint=kmf[i1][jk][t]+|sij| ,很显然最小值可以随便维护一下,查询就是 O(1) 的了。总复杂度是 O(nm2) 的。实现的时候 f 数组似乎能滚掉一维。

这题怎么 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

题面

大意:有一个 a 数组,每次给定 l,r,求 minai|aj(i,j[l,r],ij)

看到 ai<230 ,开始想的是 trie 但是没搞出来(实际上后来看到有老哥搞出来了,我比较菜)。然后观察了一下性质,现在需要每一位的 1 尽量少,可以考虑在区间选前 31 小的数出来在里面选两个(我一开始想的是前 30 小的,但是 WA 了,后来仔细想了想是前 31 小的,因为 230 实际上有 31 位,而如果取前 30 会忽略掉一些最优组合)。因为我们每一位都要尽量小的,比如 1000111 相比,看起来后面的 1 多,但实际上数更小,也就是数更小的高位的 1 更少。所以随便维护一下区间内前 31 小的然后暴力枚举就行了。

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

题面

大意:问最少修改给定数组中多少个数才能让它变成等差数列

奇怪的题。。。感觉像是为了出题而出题。。。

由于 ai=a0+i×k,即 a0=aii×k,要修改最少的数,所以保留最多的数。所以 a0aii×k 后的众数。求众数,又看到 ai105,根据经验基本可以断定这是个根号级别的做法。显然 a0=(aiaj)/(ij),所以合理分块算这个 a0 就行了。

代码鸽了。

CF1634F Fibonacci Additions

题面

大意:有两个数组 A,B,每次在 AB 的区间 [l,r] 中每个数依次加上 F1,F2,,Frl+1 ,并对给定的模数 mod 取模,问每次操作后 A,B 是否相同。(Fi 表示斐波那契数列第 i 项)

相同肯定不是一个个比,也就是要转化成区间。(比如转化成 AB=C ,且 C 的区间和为 0 ,区间积为 0 这种形式。)那么每次操作都是在区间上加减,考虑到 fib 数列的性质:Fi=Fi1+Fi2,考虑维护数组 Ci=AiBi,Di=CiCi1Ci2,如果 D0,C 就自然为 0 了。

代码比较简单,实现的时候居然搞了半天。。。感觉自己药丸(

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

题面

大意:fi 表示斐波那契数列第 i 项,现在要进行以下操作:

· 1 l r,给 [l,r] 中每个数 ai 加上 fil+1
· 2 l r,求 i=lraimod109+9

做上题的时候有题解推荐了这题,所以进来看看。想了半天没推出来什么性质,就想到用 fib 数列的通项公式,就是:

fi=15[(1+52)n(152)n]

然后线段树区间加等比数列,可以用求和公式。然后二次剩余又不太记得了,翻了题解看有老哥说有二次剩余就直接复制他的了

代码先鸽一小会儿。

code

CF1713E Cross Swapping

题面

题意:每次可以交换有公共点的一行和一列,问怎样让矩阵字典序最小,输出最小的字典序。

震撼!鸽子居然更博了!

考虑这个图:

绿色和红色换,红色和紫色换,相当于绿色和紫色换。

也就是说行之间可以随便换,列之间可以随便换,行列之间可以随便换。

那就比较简单了,判断一下换合不合适并且记录就好了。

就是说,如果暴力换,次数不够用,现在只需要考虑行列之间要不要换,把换的步骤记下来一把换就行了。

怎么记录换的步骤最后一起换?

大概是这样: ij,jk,ktl

最后执行的其实是: il

发现这个有点像路径压缩??那就并查集

然后发现换了偶数次其实就是没换,所以记录一下路径长度(或者每次 xor 一下,或者用 +1 表示啥的,都行),然后判断是否要换就行了。

posted @   kylin_xy  阅读(281)  评论(4编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
历史上的今天:
2021-06-14 题解 Codeforces LATOKEN Round 1 (Div. 1 + Div. 2) (CF1534)
点击右上角即可分享
微信分享提示