20211109NOIP模拟赛

前言:

2021.11.11 没想到我居然回来更新了!!

题外话:

考前一天晚上,由于过分装逼而被两位省二巨佬调戏

在寝室里面反复鞭尸,导致考前RP=INF

我方本着公平公正的原则意识,只为证明自己是某谷最菜绿名这一事实,宣告自己只会抄tj,但两位巨佬却罔顾事实只凭一面之词污蔑我方。

为了证明清白,我立下誓言,这次考试上50我【该内容已被删除】,上100我【该内容已被删除】
(为保护当事人隐私采取必要措施)

结果:110总之就是非常后悔

正题

T1:

得分:100

下面是我考场上的思路:
考场上,看着题,我先想了一个O(2n)的暴力应对n10的情况(写挂调了40分钟),然后左看看,右看看,感觉不会了(蒟蒻水平有目共睹)。

在看看数据,看到了k=1的情况感觉打表可以找规律。
10分钟后,表打出来了,这时已经开考一个小时了(蒟蒻水平可见一斑)。
显得蛋疼又把k=2的情况打出来了,发现了规律。50分到手。

上个厕所,蹲坑时突发奇想要不把所有的k都打一遍??
然后打着发现,n一定,k!=0时,都是一样的,而与n的值有关。
然后就开始想着,怎么构造序列,我的搜索有点奇怪,每次的输出有规律,然后我就按照规律来模拟,O(n)过了(跑的飞快)。

代码:

点击查看代码
#include<bits/stdc++.h>
#define in read()
#define int long long
using namespace std;
inline int read(){
	static char ch;
	int res=0,sign=1;
	while((ch=getchar())<'0'||ch>'9'){
		if(ch=='-'){
			sign=-1;
		}
	}
	res=ch-'0';
	while((ch=getchar())>='0'&&ch<='9'){
		res=res*10+ch-'0';
	}
	return res*sign;
}
int n,k;
int a[100001],ans;
int qz[100001];
int check(int a[]){
	int res=0;
	for(int i=1;i<=n;i++){
		qz[i]=qz[i-1]+a[i];
	}
	for(int i=1;i<=n;i++){
		for(int j=i;j<=n;j++){	
			if((qz[j]-qz[i-1])%2==1){
				res++; 
			}	
		}
	}
	return res;
}
int ans2[100001];
void dfs(int pos,int num,int *a){
	if(pos==n+1){
		if(num==k+1){
			int tmp=check(a);
			ans=max(ans,tmp);
			if(ans==tmp){
				for(int i=1;i<=n;i++){
					ans2[i]=a[i];
				}
			}
		}
		return ;
	}
	dfs(pos+1,num,a);
	a[pos]=1;
	dfs(pos+1,num+1,a);
	a[pos]=0;
}
void solve1(){
	ans=0;
	dfs(1,1,a);
	printf("%lld\n",check(ans2));
	for(int i=1;i<=n;i++){
		cout<<ans2[i]<<' ';
	}
	cout<<endl;
	return ;
}
signed main()
{
//	freopen("string.in","r",stdin);
//	freopen("string.out","w",stdout);
	n=in,k=in;
	if(k==0){
		cout<<0<<endl;
		return 0;
	}
	if(n<=10){
		solve1();
	}else if(k==1){
		if(n%2==0){
			printf("%lld\n",(n/2)*(n/2+1));
			a[n/2]=1;
			for(int i=1;i<=n;i++){
				cout<<a[i]<<' ';
			}
			cout<<endl;
		}else{
			printf("%lld\n",((n+1)/2)*((n+1)/2));
			a[n/2+1]=1;
			for(int i=1;i<=n;i++){
				cout<<a[i]<<' ';
			}
			cout<<endl;
		}
	}else if(k==2){
		if(n%2==0){
			printf("%lld\n",(n+2)*n/4);
			a[1]=1;
			a[n/2+1]=1;
			for(int i=1;i<=n;i++){
				cout<<a[i]<<' ';
			}
			cout<<endl;
		}else{
			printf("%lld\n",(n+1)*(n+3)/4-(n+1)/2);
			a[1]=1;
			a[n/2+2]=1;
			for(int i=1;i<=n;i++){
				cout<<a[i]<<' ';
			}
			cout<<endl;
		}
	}else{
		if(n%2==0){
			printf("%lld\n",(n/2)*(n/2+1));
			for(int i=1;i<=k-1;i++){
				a[i]=1;
			}
			if((n-k+1)%2==0){
				a[(n-k+1)/2+k-1]=1;
			}else{
				a[(n-k+1)/2+k]=1;
			}
			for(int i=1;i<=n;i++){
				cout<<a[i]<<' ';
			}
			cout<<endl;
		}else{
			printf("%lld\n",((n+1)/2)*((n+1)/2));
			for(int i=1;i<=k-1;i++){
				a[i]=1;
			}
			if((n-k+1)%2==0){
				a[(n-k+1)/2+k]=1;
			}else{
				a[(n-k+1)/2+k]=1;
			}
			for(int i=1;i<=n;i++){
				cout<<a[i]<<' ';
			}
			cout<<endl;
		}
	}
	return 0;
}

T2:

得分:10

没办法,把T1做出来都10:30了,我还在那里手造数据测了半天。
看看题,想打一发主席树,但是过了一天就忘完了,只好打一个O(n3)的暴力水了10分;

正解:
有点不好讲:

主要是维护一个lst[],存储当前的值上一次出现的位置;
一个区间不重复只能是,该区间所有的lst的最大值小于左端点,这就变成了线段树单点修改,区间询问最大值了。蒟蒻太菜了.

还有一个重点就是,在更新一个节点后,要把他影响的所有区间都判断,这里就要对区间进行建树,把区间从大到小双关键字排序,然后存一个栈,枚举到一个区间就退栈找到他的父亲然后建立关系,这里排序前要存L,R,还要记录id,pos的映射关系。

在树上check时递归即可:

代码:

重口味:

点击查看代码
#include<bits/stdc++.h>
#define re register
using namespace std;
char rbuf[30000002];
int pt=-1;
inline int read(){
	re int t=0;re char v=rbuf[++pt];
	while(v<'0')v=rbuf[++pt];
	while(v>='0')t=(t<<3)+(t<<1)+v-48,v=rbuf[++pt];
	return t;
}
set<int>S[500002];
set<int>::iterator it;
int n,m,q,mn[2000002],X,Y,nxt[500002],lst[500002],c[500002],rd[500002],head[500002],cnt,fa[500002],tp,ans[500002],T,L[500002],R[500002],pos[500002],pre[500002];
struct node{int x,y,id;bool operator <(const node A)const{return x==A.x?y>A.y:x<A.x;};}p[500002],stk[500002];
inline void cg(re int p,re int l,re int r){
	if(l==r){mn[p]=Y;return;}
	re int mid=l+r>>1;
	if(X<=mid)cg(p<<1,l,mid);
	else cg(p<<1|1,mid+1,r);
	mn[p]=max(mn[p<<1],mn[p<<1|1]);
}
inline int ask(re int p,re int l,re int r,re int x,re int y){
	if(l>=x&&r<=y)return mn[p];
	re int s=0,mid=l+r>>1;
	if(x<=mid)s=max(s,ask(p<<1,l,mid,x,y));
	if(y>mid)s=max(s,ask(p<<1|1,mid+1,r,x,y));
	return s;
}
inline void build(re int p,re int l,re int r){
	if(l==r){mn[p]=lst[l];return;}
	re int mid=l+r>>1;
	build(p<<1,l,mid),build(p<<1|1,mid+1,r);
	mn[p]=max(mn[p<<1],mn[p<<1|1]);
}
inline void cg(re int x,re int y){
	if(nxt[x])X=nxt[x],Y=lst[x],cg(1,1,n);//单点修改 
	nxt[lst[x]]=nxt[x],lst[nxt[x]]=lst[x],S[c[x]].erase(x),c[x]=y;//对原来值的前后关系的修改 
	re int o=lst[x];
	it=S[y].upper_bound(x),lst[x]=nxt[x]=0;
	if(it!=S[y].end())X=*it,nxt[x]=X,Y=x,cg(1,1,n);//后面的y前缀修改 
	if(it!=S[y].begin())--it,lst[x]=*it;
	S[y].insert(x),lst[nxt[x]]=nxt[lst[x]]=x,X=x,Y=lst[x];
	if(o^Y)cg(1,1,n);//改现在这个点的前缀 
}
set<node>s;
set<node>::iterator it1;
inline void ck(re int x){
	if(ask(1,1,n,L[x],R[x])<L[x]){
		s.erase((node){L[x],R[x],x});
		ans[x]=T;
		if(fa[x]&&(!(--rd[fa[x]])))rd[fa[x]]=-1,s.insert(p[pos[fa[x]]]),ck(fa[x]);
	}
}
int main(){
		freopen("artist4.in","r",stdin); 
	fread(rbuf,1,30000000,stdin),n=read(),m=read(),q=read();
	for(re int i=1;i<=n;++i)c[i]=read();
	for(re int i=1;i<=n;++i){
		lst[i]=pre[c[i]],nxt[pre[c[i]]]=i;//lst记录c[i]上一次出现的位置 ,nxt是下一次出现的位置 
		pre[c[i]]=i,S[c[i]].insert(S[c[i]].end(),i);//set维护没个值出现的位置 
	}
	build(1,1,n);
	for(re int i=1;i<=m;++i)p[i].x=L[i]=read(),p[i].y=R[i]=read(),ans[i]=i+m,p[i].id=i;
	sort(p+1,p+m+1);//对边排序 
	for(re int i=1;i<=m;++i){//对区间建树 
		if(p[i].x==p[i].y||ask(1,1,n,p[i].x,p[i].y)<p[i].x){ans[p[i].id]=0,rd[p[i].id]=-1;continue;}//满足条件,所有在该区间出现的值的上一次都在区间外 
		pos[p[i].id]=i;//顺序 
		while(tp&&stk[tp].y<p[i].x)--tp;//退栈找到父亲 
		fa[p[i].id]=stk[tp].id,stk[++tp]=p[i],++rd[fa[p[i].id]];//父子建立关系 
	}
	for(re int i=1;i<=m;++i)if(!rd[p[i].id])s.insert(s.end(),p[i]);//放入set 
	for(re int i=1;i<=q;++i){//修改 
		re int x=read(),y=read();
		T=i,cg(x,y);//序列修改 
		it1=s.upper_bound((node){x,0,0});//找儿子 
		if(it1!=s.begin())--it1,ck((*it1).id);
	}
	re int s=0;
	for(re int i=1;i<=m;++i)s^=ans[i];
	printf("%d",s);
}

小清新:

点击查看代码
#include<bits/stdc++.h>
#define in read()
#define re register
using namespace std;
int n,m,q,X,Y;
int maxn[500002<<2];
int nxt[500002],lst[500002],c[500002],rd[500002],head[500002],cnt,fa[500002],tp,ans[500002],T,L[500002],R[500002],pos[500002],pre[500002];
inline int read(){
	static char ch;
	int res=0;
	while((ch=getchar())<'0'||ch>'9'){
	}
	res=ch-'0';
	while((ch=getchar())>='0'&&ch<='9'){
		res=res*10+ch-'0';
	}
	return res;
}
struct node{
	int l,r,id;
	bool operator <(const node A)const{return l==A.l?r>A.r:l<A.l;};
}p[500002],stk[500002];
//                                  线段树 
//==============================================================================
void build(int k,int l,int r){
	if(l==r){
		maxn[k]=lst[l];
		return ;
	}
	re int mid=l+r>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	maxn[k]=max(maxn[k<<1],maxn[k<<1|1]);
}

inline void change(int k,int l,int r){
	if(l==r){
		maxn[k]=Y;
		return ;
	}
	re int mid=l+r>>1;
	if(X<=mid){
		change(k<<1,l,mid);
	}else{
		change(k<<1|1,mid+1,r);
	}
	maxn[k]=max(maxn[k<<1],maxn[k<<1|1]);
}

inline int query(int k,int l,int r,int x,int y){
	if(l>=x&&r<=y){
		return maxn[k];
	}
	re int res=-1;
	re int mid=l+r>>1;
	if(x<=mid){
		res=max(res,query(k<<1,l,mid,x,y));
	}
	if(mid<y){
		res=max(res,query(k<<1|1,mid+1,r,x,y));
	}
	return res;
}
//==============================================================================


set<node> s;//叶子区间set 
set<node>::iterator it1;
set<int> S[500002];
set<int>::iterator it;//每一个值 

inline void cg(int x,int y){
	if(nxt[x]){
		X=nxt[x],Y=lst[x];
		change(1,1,n);
	}//单点修改
	lst[nxt[x]]=lst[x];
	nxt[lst[x]]=nxt[x];//对原来值的前后关系的修改
	S[c[x]].erase(x);
	c[x]=y;
	re int o=lst[x];
	it=S[y].upper_bound(x),lst[x]=nxt[x]=0;
	if(it!=S[y].end()) X=*it,nxt[x]=X,Y=x,change(1,1,n);//后面的y前缀修改,更新现在的后缀 
	if(it!=S[y].begin()) --it,lst[x]=*it;//更新现在的前缀 
	S[y].insert(x);
	lst[nxt[x]]=nxt[lst[x]]=x;//前后缀 
	X=x,Y=lst[x];
	if(Y^o) change(1,1,n);//改现在这个点的前缀
}

void ck(int x){//递归查 
	if(query(1,1,n,L[x],R[x])<L[x]){//该区间的左右端点 
		s.erase((node){L[x],R[x],x});
		ans[x]=T;
		if(fa[x]&&(!(--rd[fa[x]]))) rd[fa[x]]=-1,s.insert(p[pos[fa[x]]]),ck(fa[x]);//儿子查完了,父亲变成儿子再查 
	}
}
signed main(){
	//freopen("artist4.in","r",stdin); 
	n=in,m=in,q=in;
	for(re int i=1;i<=n;i++){
		c[i]=in;
	}
	for(re int i=1;i<=n;i++){
		lst[i]=pre[c[i]];
		nxt[pre[c[i]]]=i;
		pre[c[i]]=i;
		S[c[i]].insert(S[c[i]].end(),i);
	}
	build(1,1,n);
	for(re int i=1;i<=m;i++){
		p[i].l=L[i]=in;
		p[i].r=R[i]=in;
		p[i].id=i;
		ans[i]=i+m;
	}
	sort(p+1,p+m+1);
	for(re int i=1;i<=m;i++){
		if(p[i].l==p[i].r||query(1,1,n,p[i].l,p[i].r)<p[i].l){//开始时就有序 
			ans[p[i].id]=0;
			rd[p[i].id]=-1;
			continue;
		}
		pos[p[i].id]=i;
		while(tp&&stk[tp].r<p[i].l) tp--;
		fa[p[i].id]=stk[tp].id;
		stk[++tp]=p[i];
		++rd[fa[p[i].id]];
	}
	for(re int i=1;i<=m;i++){
		if(rd[p[i].id]!=-1){
			s.insert(s.end(),p[i]);
		}
	}
	int x,y;
	for(re int i=1;i<=q;i++){
		x=in,y=in;
		T=i;
		cg(x,y);
		it1=s.upper_bound((node){x,0,0});//该顶点影响的区间 
		if(it1!=s.begin()){
			--it1;
			ck((*it1).id);
		}
	}
	int s=0;
	for(re int i=1;i<=m;++i)s^=ans[i];
	printf("%d",s);
	return 0;
}

T3:

咕咕咕~~

不咕咕咕了!!QWQ

对一整颗树来说,先找出他的直径还有每个点到直径两个端点的两个距离。

用快速幂来求整棵树的情况种数,枚举答案d。注意d如果是某个点到一个端点的最短距离则退出,因为他作为答案不合法了。

枚举到一个答案,减去出现改答案的情况数,由于减去的情况可以反色所以要加一后快速幂。

先放代码:

点击查看代码
#include<bitsdc++.h>
using namespace std;
#define ll long long
const int N=1e6+10,mod=1e9+7;
int dis[N][2],n;
vector<int>g[N];
int x,y;
inline void Dfs(int u,int f,int ki){//搜索整棵树 
	dis[u][ki]=dis[f][ki]+1;
	for(int i=0;i<g[u].size();i++)if(f^g[u][i])Dfs(g[u][i],u,ki);
}
int c[N];
bool vis[N];
inline int ksm(int a,int b){//快速幂 
	int res=1;
	while(b){
		if(b&1)res=(ll)res*a%mod;
		a=(ll)a*a%mod;
		b>>=1;
	}
	return res;
}
int main(){
	scanf("%d",&n);
	int u,v;
	for(int i=1;i<n;i++)scanf("%d%d",&u,&v),g[u].push_back(v),g[v].push_back(u);
	dis[0][0]=dis[0][1]=-1;
	Dfs(1,0,0);//找到直径的一个端点 
	int mx=0;
	for(int i=1;i<=n;i++)
		if(dis[i][0]>mx){
			x=i;
			mx=dis[i][0];
		}
	Dfs(x,0,0);//找到直径的另一个端点 
	mx=0;
	for(int i=1;i<=n;i++)
		if(dis[i][0]>mx){
			y=i;
			mx=dis[i][0];
		}
	Dfs(y,0,1);//记录每个点到两个端点的距离 
	for(int i=1;i<=n;i++)
	  for(int ki=0;ki<2;ki++)
	    c[dis[i][ki]]++;//每个距离出现的次数 
	for(int i=1;i<=n;i++)vis[min(dis[i][0],dis[i][1])]=true; //最小距离 
	int sum=ksm(2,n),now,rest=n+1;//sum总情况数,rest是每一步减去的情况数,由于可以反色所加一 
	int mxd=dis[x][1];//上界 
	int ans=0;
	for(int d=mxd;~d;d--){
		if(vis[d]){//d的下界,再往下d就不合法,就不是最大距离了 
			ans=(ans+(ll)sum*d)%mod;
			cout<<ans;
			return 0;
		}
		rest-=c[d];//减去该长度的次数 
		now=ksm(2,rest);//需要减去的范围情况 
		ans=(ans+((ll)(sum-now+mod)%mod)*d%mod)%mod;//加上外围的确定的情况 
		sum=now;
	}
	return 0;
}
posted @   SSZX_loser_lcy  阅读(23)  评论(2编辑  收藏  举报
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示