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

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

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

不更新不代表我复读了!(有学弟问我是不是复读了 /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\) 次操作,每次把任意一个数改成非负整数,使 \(dif-mex\) 最小。$dif = $ 数组内不重复元素个数,\(mex\) 为数组内未出现的最小元素。

如果把一个出现多次的数改成 \(mex\) ,实则贡献为 \(0\),所以尽量改出现一次的数。实际上 \(mex \leq n\) ,考虑枚举一下 \(mex\) ,看一下 \(0 \sim mex-1\) 需要填充的步数是否 \(\leq 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

题面

大意:求 \(\sum_{i=1}^n \sum_{j=i+1}^n s(i,j),s(i,j)= i\)\(j\) 路径上恰好出现一次的颜色数量。

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

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

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\) 个盘子有 \(a_i\) 个饼子,每次只能把一个饼子挪到相邻的盘子里。问最少挪动次数使得 \(a_i \geq a_{i+1}\) 恒成立。

奇妙的 \(n,m \leq 250\) ,看着就是个 \(n^3\) 左右的 \(dp\) 。设 \(f[i][j][k]\) 表示 第 \(i\) 个箱子,前面的箱子一共放了 \(j\) 个饼子,第 \(i\) 个箱子放 \(k\) 个饼子。那么 \(f[i][j][k]= \min_{t=k}^m f[i-1][j-k][t]+|s_i-j|\) ,很显然最小值可以随便维护一下,查询就是 \(O(1)\) 的了。总复杂度是 \(O(nm^2)\) 的。实现的时候 \(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\),求 \(\min a_i | a_j (i,j \in [l,r], i \neq j)\)

看到 \(a_i < 2^{30}\) ,开始想的是 \(trie\) 但是没搞出来(实际上后来看到有老哥搞出来了,我比较菜)。然后观察了一下性质,现在需要每一位的 \(1\) 尽量少,可以考虑在区间选前 \(31\) 小的数出来在里面选两个(我一开始想的是前 \(30\) 小的,但是 WA 了,后来仔细想了想是前 \(31\) 小的,因为 \(2^{30}\) 实际上有 \(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

题面

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

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

由于 \(a_i=a_0+i \times k\),即 \(a_0=a_i-i \times k\),要修改最少的数,所以保留最多的数。所以 \(a_0\)\(a_i-i \times k\) 后的众数。求众数,又看到 \(a_i \leq 10^5\),根据经验基本可以断定这是个根号级别的做法。显然 \(a_0=(a_i-a_j)/(i-j)\),所以合理分块算这个 \(a_0\) 就行了。

代码鸽了。

CF1634F Fibonacci Additions

题面

大意:有两个数组 \(A,B\),每次在 \(A\)\(B\) 的区间 \([l,r]\) 中每个数依次加上 \(F_1,F_2,\cdots,F_{r-l+1}\) ,并对给定的模数 \(mod\) 取模,问每次操作后 \(A,B\) 是否相同。(\(F_i\) 表示斐波那契数列第 \(i\) 项)

相同肯定不是一个个比,也就是要转化成区间。(比如转化成 \(A-B=C\) ,且 \(C\) 的区间和为 \(0\) ,区间积为 \(0\) 这种形式。)那么每次操作都是在区间上加减,考虑到 \(fib\) 数列的性质:\(F_i=F_{i-1}+F_{i-2}\),考虑维护数组 \(C_i=A_i-B_i,D_i=C_i-C_{i-1}-C_{i-2}\),如果 \(D\)\(0,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

题面

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

· 1 l r,给 \([l,r]\) 中每个数 \(a_i\) 加上 \(f_{i-l+1}\)
· 2 l r,求 \(\sum_{i=l}^r a_i \bmod 10^9+9\)

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

\[f_i = \frac{1}{\sqrt{5}} [(\frac{1+\sqrt{5}}{2})^n - (\frac{1-\sqrt{5}}{2})^n] \]

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

代码先鸽一小会儿。

code

CF1713E Cross Swapping

题面

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

震撼!鸽子居然更博了!

考虑这个图:

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

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

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

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

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

大概是这样: $ i \leftrightarrow j, j \leftrightarrow k, k \leftrightarrow t \cdots l$

最后执行的其实是: $ i \leftrightarrow l$

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

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

posted @ 2022-06-14 11:55  kylin_xy  阅读(268)  评论(4编辑  收藏  举报