NOIP模拟赛20190902

T1

洛谷P3938 斐波那契

带编号的斐波那契兔子,m个询问,给出两只兔子的编号,求最近公共祖先编号。

记兔子总数为 f i f_i fi,如果 f i − 1 &lt; x ≤ f i f_{i-1}&lt;x\le f_i fi1<xfi,那么 x x x就是由 f i − 2 f_{i-2} fi2生出来的。 x − f i − 1 x-f_{i-1} xfi1就是 x x x父亲的编号。斐波那契数60项就到了1012,所以直接暴力往上找父亲就好了。

#include<bits/stdc++.h>
#define LL long long
#define maxn 65
using namespace std;
char cb[1<<18],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<18,stdin),cs==ct)?0:*cs++)
template<class T>inline void read(T &a){
	char c;while(!isdigit(c=getc()));
	for(a=c-'0';isdigit(c=getc());a=a*10+c-'0');	
}
template<class T>inline void write(T x){
	if(x>=10) write(x/10);
	putchar(x%10+48);	
}
const int len = 60;
int m,x,y;
LL a,b,f[maxn];
int main()
{
	freopen("fibonacci.in","r",stdin);
	freopen("fibonacci.out","w",stdout);
	f[1]=1,f[2]=2;
	for(int i=3;i<=len;i++) f[i]=f[i-1]+f[i-2]; 
	read(m);
	while(m--){
		read(a),read(b);
		x=lower_bound(f+1,f+1+len,a)-f;
		y=lower_bound(f+1,f+1+len,b)-f;
		while(a!=b){
			if(x<y) swap(x,y),swap(a,b);
			a-=f[x-1];
			while(f[x-1]>=a) x--;
		}
		write(a),putchar('\n');
	}
}

T2

洛谷P3939 数颜色
序列,颜色 a i a_i ai,两种操作:询问 [ l , r ] [l,r] [l,r]颜色为 x x x的个数;交换 a x a_x ax a x + 1 a_{x+1} ax+1。 n,m<=300000

一眼暴力分块时间超限。
两眼动态开点思维僵化。
题解颜色排序惊为天人。

pair<颜色,位置>排序,查询就lower_bound找到对应区间即可,相邻修改不会改变同种颜色位置的相对顺序。或者使用vector也可以。
如果修改是任意两点交换可以把vector换成set。

Code:

#include<bits/stdc++.h>
#define maxn 300005
using namespace std;
int n,m,a[maxn];
vector<int>b[maxn];
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),b[a[i]].push_back(i);
	int op,l,r,x;
	while(m--){
		scanf("%d",&op);
		if(op==1){
			scanf("%d%d%d",&l,&r,&x);
			printf("%d\n",upper_bound(b[x].begin(),b[x].end(),r)-lower_bound(b[x].begin(),b[x].end(),l));
		}
		else{
			scanf("%d",&x);
			if(a[x]==a[x+1]) continue;
			(*lower_bound(b[a[x]].begin(),b[a[x]].end(),x))++;
			(*lower_bound(b[a[x+1]].begin(),b[a[x+1]].end(),x+1))--;
			swap(a[x],a[x+1]);
		}
	}
}

T3

洛谷P3940 分组

题意比较复杂,直接进题面看吧。

兔子越多限制越大,所以字典序最小就肯定是最后一组尽量多。从后往前枚举加兔子,如果矛盾就分组。
K=1直接对权值打标记、枚举矛盾边。
K=2需要判二分图,可以用带权并查集。标记是对权值的,所以需要考虑 2 a i = x 2 2a_i=x^2 2ai=x2的情况,这样的 a i a_i ai不能出现超过两次,如果有两次那么不能再有第三个数和它矛盾。

Code:

#include<bits/stdc++.h>
#define LL long long
#define maxn 135005
using namespace std;
char cb[1<<18],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<18,stdin),cs==ct)?0:*cs++)
template<class T>inline void read(T &a){
	char c;while(!isdigit(c=getc()));
	for(a=c-'0';isdigit(c=getc());a=a*10+c-'0');	
}
template<class T>inline void write(T x){
	if(x>=10) write(x/10);
	putchar(x%10+48);
}
int n,K,a[maxn],ans[maxn],f[maxn],m,vis[maxn];
bool pan[maxn],d[maxn];
int find(int x){
	if(x!=f[x]){
		int fa=f[x]; f[x]=find(f[x]);
		d[x]^=d[fa];
	}
	return f[x];
}
int main()
{
	read(n),read(K);
	for(int i=1;i<=n;i++) read(a[i]);
	const int Mx = *max_element(a+1,a+1+n);
	for(int i=2;i*i<=2*Mx;i+=2) pan[i*i/2]=1;
	ans[0]=n;
	if(K==1){
		for(int i=n;i>=1;i--){
			bool flg=0;
			for(int j=int(sqrt(a[i])+1);j*j<=Mx+a[i];j++) if(vis[j*j-a[i]]) {flg=1;break;}
			if(flg==1) {for(int j=i+1;j<=ans[m];j++) vis[a[j]]=0;ans[++m]=i;}
			vis[a[i]]=1;
		}
	}
	else{
		for(int i=1;i<=Mx;i++) f[i]=i,d[i]=0;
		for(int i=n,x,y;i>=1;i--){
			bool flg=0,same=0,other=0;
			for(int j=int(sqrt(a[i])+1),k;j*j<=Mx+a[i];j++) if(vis[k=j*j-a[i]]){
				if(vis[k]>1&&pan[k]) {flg=1;break;}
				if(k==a[i]) {same=1;continue;}
				other=1;
				if((x=find(a[i]))!=(y=find(k))) f[x]=y,d[x]=d[a[i]]^d[k]^1;
				else if(d[a[i]]==d[k]) {flg=1;break;}
			}
			if(flg||(same&other)) {for(int j=i+1;j<=ans[m];j++) vis[a[j]]=0,f[a[j]]=a[j],d[a[j]]=0;ans[++m]=i;}
			vis[a[i]]++;
		}
	}
	write(m+1),putchar('\n');
	for(int i=m;i>=1;i--) write(ans[i]),putchar(i==1?10:32);
}

总结:忘记了两个经典解法,动态开点线段树和带权并查集动态判二分图。第二题直接闷头闷脑就开始分块。。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值