noip模拟14[队长快跑·影魔·抛硬币]

noip模拟14 solutions

太傻啦,太傻啦

也就我自己傻到忘记写暴搜程序,直接丢掉40分啊啊啊气死啦

这次考试算是数据结构专题考试了吧,虽然我也用到了好多,但是只拿到了120pts,最高250pts

第一题算是比较成功的,自己搞出来一个与众不同的转移方程

第二题也还行,用数据结构优化了一下

第三题就比较惨,只有10pts,太着急了,忘记写暴力了

所以呢,以后要是考试,一定一定要先把所有的题看完,找到最好的开题顺序,先把暴力打一遍

·

这些题好像是\(HMOI_2017\)的原题目背景,所以

T1 队长快跑

我在考场上想出来了一个\(O(nklogn)\)的算法,其实还是挺快的,就是中间那个\(k\)\(1\)\(n\)之间不等

就直接退化到\(O(n^2logn)\),就直接拿到70pts,大体思路是这样:

·

先对啊\(a[i],b[i]\)进行离散化,因为树状数组只能维护前缀,所以我就吧\(a[i],b[i]\)变换了一下

    a[i]=n+1-a[i],b[i]=n+1-b[i];

为啥要这样?因为原题要求\(a[i]>b[i]\)而我们要维护前缀,所以我们就让\(a[i]<b[i]\)

我就开了一颗树状数组,然后维护两个值,\(tr[i],maxn[i]\)\(tr\)表示长度,\(maxn\)表示长度为\(tr[i]\)时的最小的\(a[i]\),这样我们就可以记录,

\([1,i]\)这个范围内,最大的长度为多少,以及这个长度所对应的最小的\(a[i]\);

然后有一个小问题,我们可能要用到长度较小但是最小的a很大的序列,所以我们就根据此时的\(maxn\)向前跳

找到\([1,maxn[i]-1]\)这样的正确性显然,比maxn大的地方还没有他长,一定不是最优解

所以我那个k就是从这里来的,注意跳的时候,小于此时\(a[i]\)的时候就不要跳了,没啥意义

70pts_树状数组
#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=1e5+5;
int n,a[N],b[N];
int lsh[N*2],lh;
struct seg_tree{
	int tr[N*2],maxn[N*2];
	int ret,rma;
	inline int lb(int x){return (x&(-x));}
	inline void ins(int x,int v){
		for(re i=x;i<=lh+5;i+=lb(i)){
			if(v>tr[i]){
				tr[i]=v;maxn[i]=x;
			}
		}
	}
	inline void query(int x){
		ret=0;
		for(re i=x;i>0;i-=lb(i)){
			if(ret<tr[i]){
				ret=tr[i];rma=maxn[i];
			}
		}
	}
}t;
int ans;
signed main(){
	scanf("%d",&n);
	for(re i=1;i<=n;i++){	
		scanf("%d%d",&a[i],&b[i]);
		lsh[2*i-1]=a[i];lsh[2*i]=b[i];
	}
	sort(lsh+1,lsh+2*n+1);
	lh=unique(lsh+1,lsh+2*n+1)-lsh-1;
	for(re i=1;i<=n;i++){
		a[i]=lower_bound(lsh+1,lsh+lh+1,a[i])-lsh;
		b[i]=lower_bound(lsh+1,lsh+lh+1,b[i])-lsh;
		a[i]=lh+2-a[i];
		b[i]=lh+2-b[i];
	}
	for(re i=1;i<=n;i++){
		for(re j=b[i]-1;j>0;j=t.rma-1){
			t.query(j);
			ans=max(ans,t.ret+1);
			t.ins(max(t.rma,a[i]),t.ret+1);
			if(t.rma<a[i]||t.ret==0)break;
		}
		t.ins(a[i],1);
	}
	printf("%d",ans);
}

·

所以下面是不是该讲讲正解啦??

对正解是线段树,对万能的线段树,所以以后我要是再写树状数组,我就把我面前这个电脑吃掉

刚才的那个向前跳的过程,在线段树中就直接一个区间修改+1,完美\(logn\)解决

AC_code


#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=1e5+5;
int n,a[N],b[N];
int lsh[N*2],lh;
struct XDS{
	#define ls x<<1
	#define rs x<<1|1
	int sum[N*8],maxn[N*8];
	int laz[N*8];
	inline void pushup(int x){
		maxn[x]=max(maxn[ls],maxn[rs]);
	}
	inline void pushdown(int x,int l,int r){
		if(!laz[x])return ;
		int mid=l+r>>1;
		laz[ls]+=laz[x];maxn[ls]+=laz[x];
		laz[rs]+=laz[x];maxn[rs]+=laz[x];
		laz[x]=0;
	}
	void ins1(int x,int l,int r,int pos,int v){
		if(l==r){
			maxn[x]=max(maxn[x],v);
			return ;
		}
		pushdown(x,l,r);
		int mid=l+r>>1;
		if(pos<=mid)ins1(ls,l,mid,pos,v);
		else ins1(rs,mid+1,r,pos,v);
		pushup(x);return ;
	}
	void ins2(int x,int l,int r,int ql,int qr,int v){
		if(ql<=l&&r<=qr){
			laz[x]+=v;
			maxn[x]+=v;
			return ;
		}
		pushdown(x,l,r);
		int mid=l+r>>1;
		if(ql<=mid)ins2(ls,l,mid,ql,qr,v);
		if(qr>mid)ins2(rs,mid+1,r,ql,qr,v);
		pushup(x);return ;
	}
	int query(int x,int l,int r,int ql,int qr){
		if(ql>qr)return 0;
		if(ql<=l&&r<=qr)return maxn[x];
		pushdown(x,l,r);
		int mid=l+r>>1,ret=0;
		if(ql<=mid)ret=max(ret,query(ls,l,mid,ql,qr));
		if(qr>mid)ret=max(ret,query(rs,mid+1,r,ql,qr));
		pushup(x);return ret;
	}
	#undef ls
	#undef rs
}xds;
int ans;
signed main(){
	scanf("%d",&n);
	for(re i=1;i<=n;i++){
		scanf("%d%d",&a[i],&b[i]);
		lsh[i*2-1]=a[i];
		lsh[i*2]=b[i];
	}
	sort(lsh+1,lsh+2*n+1);
	lh=unique(lsh+1,lsh+2*n+1)-lsh-1;
	for(re i=1;i<=n;i++){
		a[i]=lower_bound(lsh+1,lsh+lh+1,a[i])-lsh;
		b[i]=lower_bound(lsh+1,lsh+lh+1,b[i])-lsh;
		if(a[i]<=b[i]){
			int tmp=xds.query(1,1,lh,b[i]+1,lh)+1;
			ans=max(ans,tmp);
			xds.ins1(1,1,lh,a[i],tmp);
		}
		else{
			int tmp=xds.query(1,1,lh,a[i]+1,lh)+1;
			ans=max(ans,tmp);
			int ji=tmp;
			tmp=xds.query(1,1,lh,b[i]+1,a[i])+1;
			ans=max(ans,tmp);
			xds.ins2(1,1,lh,b[i]+1,a[i],1);
			xds.ins1(1,1,lh,a[i],ji);
		}
	}
	printf("%d",ans);
}

这里还有一个事情,最好在线段树中每一个函数都加上\(pushdown和pushup\)我因为这个调了快半个小时

这里是官方题解,注意这里的转移是\(O(n^2)\)的,而\(O(n^3)\)的算法,是不继承上一个i的值,然后暴力循环n

·

T2 影魔

说实话,看到这个题目之后我吓了一跳,虽然说之前我做过这个题,但是我不保证现在还记得

读完题面之后,我直接想到树套树,可是这里有点不一样,他要维护三个值,深度,dfn,种类

于是我第一时间写了dfs序,树上区间问题转为序列区间问题,

再想想,维护三个值,我靠,这要树套树套树啊,不行不行,根本套不下

我就退而求其次,我去那部分分,看到有\(d+dep[u]>最大深度\)的情况,

我就直接一个线段树合并,解决了这种问题,剩下的就直接暴力扫,

考完之后一想,发现,向下搜索一下的话,还有一些子树的深度也很小,也可以用线段树维护出来,

要是那样写一下,起码得有70pts,而我只有40pts

40pts_线段树合并
#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=1e5+5;
int n,m,R,c[N];
int to[N*2],nxt[N*2],head[N],rp;
void add_edg(int x,int y){
	to[++rp]=y;
	nxt[rp]=head[x];
	head[x]=rp;
}
int dfn[N],idf[N],dfm[N],cnt;
int rt[N];
int dep[N],des[N];
struct XDS{
	int ls[N*80],rs[N*80];
	int siz[N*80],sum[N*80];
	int seg;
	void pushup(int x){
		siz[x]=0;sum[x]=0;
		if(ls[x])siz[x]+=siz[ls[x]],sum[x]+=sum[ls[x]];
		if(rs[x])siz[x]+=siz[rs[x]],sum[x]+=sum[rs[x]];
	}
	void ins(int &x,int l,int r,int pos){
		if(!x)x=++seg;
		if(l==r){
			siz[x]++;sum[x]=1;
			return ;
		}
		int mid=l+r>>1;
		if(pos<=mid)ins(ls[x],l,mid,pos);
		else ins(rs[x],mid+1,r,pos);
		pushup(x);return ;
	}
	void merge(int &x,int y,int l,int r){
		if(!y)return ;
		if(!x)x=++seg;
		siz[x]+=siz[y];
		if(l==r){
			sum[x]=1;
			return ;
		}
		int mid=l+r>>1;
		merge(ls[x],ls[y],l,mid);
		merge(rs[x],rs[y],mid+1,r);
		pushup(x);return ;
	}
}xds;
void dfs_first(int x){
	dfn[x]=++cnt;idf[cnt]=x;
	des[x]=dep[x];
	for(re i=head[x];i;i=nxt[i]){
		int y=to[i];
		dep[y]=dep[x]+1;
		dfs_first(y);
		des[x]=max(des[x],des[y]);
		xds.merge(rt[x],rt[y],1,R);
	}
	xds.ins(rt[x],1,R,c[x]);
	dfm[x]=cnt;
}
bool vis[N];
int ans,ji[N],tot;
signed main(){
	//freopen("b.ans","w",stdout);
	scanf("%d%d",&n,&m);
	for(re i=1;i<=n;i++)scanf("%d",&c[i]),R=max(R,c[i]);
	for(re i=2,x;i<=n;i++){
		scanf("%d",&x);
		add_edg(x,i);
	}
	dfs_first(1);
	for(re i=1,u,d;i<=m;i++){
		scanf("%d%d",&u,&d);
		if(d>=des[u]-dep[u])printf("%d\n",xds.sum[rt[u]]);
		else{
			tot=0;ans=0;
			for(re j=dfn[u];j<=dfm[u];j++){
				int x=idf[j];
				if(dep[x]<=dep[u]+d){
					if(!vis[c[x]])ans++,ji[++tot]=c[x];
					vis[c[x]]=true;
				}
			}
			for(re j=1;j<=tot;j++)vis[ji[j]]=false;
			printf("%d\n",ans);
		}
	}
}

·

于是乎我马上就要说正解啦

其实要是找到一个规律之后,就很简单了

比如说,现在我们有一颗树,每个节点都有一个种类,要求这颗树的种类数

我们可以将每种种类放到一起,按照dfn排序,所有这个种类的点在树上的权值+1,相邻点的lca-1

这样我们直接求这个树的权值和就是这个树的种类数,这样比原来就好统计多了,

当然,对于每一个节点的子树内的种类数就是这个子树内的权值和

所以我们对这个题的做法就是主席树,每个深度为一个历史版本

统计的时候,就直接在相应的深度的树内查询对应dfs序区间上的权值和就好了

首先对所有节点按照深度排序,插入时,用set来维护这个节点在这个种类内的前趋,后继

设前趋为\(pre\) ,后继为\(nxt\)

    i              +1
    lca(pre,i)     -1
    lca(nxt,i)     -1
    lca(nxt,pre)   +1

这样就维护好了上面所说的那种权值

还有一个需要注意的地方,此时的主席树,不可以每次都继承上一个版本的信息

因为在一个版本中可能会插入好多值,我的做法是,在向下搜索时,将儿子赋值为0

AC_code


#include<bits/stdc++.h>
using namespace std;
#define re register int
const int N=1e5+5;
int n,m;
struct POT{
	int c,dep,dfn,dfm,id;
	bool operator < (POT x)const{
		return x.dfn>dfn;
	}
}sca[N];
set<POT> st[N];
int cnt;
int to[N*2],nxt[N*2],head[N],rp;
void add_edg(int x,int y){
	to[++rp]=y;
	nxt[rp]=head[x];
	head[x]=rp;
}
int fa[N][25];
int dp[N],ds[N],dn[N],dm[N];
void dfs_fir(int x){
	sca[x].dfn=++cnt;
	ds[x]=sca[x].dep;
	for(re i=head[x];i;i=nxt[i]){
		int y=to[i];
		sca[y].dep=sca[x].dep+1;
		fa[y][0]=x;
		for(re i=1;i<=20;i++)fa[y][i]=fa[fa[y][i-1]][i-1];
		dfs_fir(y);
		ds[x]=max(ds[x],ds[y]);
	}
	sca[x].dfm=cnt;
}
int LCA(int x,int y){
	if(dp[x]<dp[y])swap(x,y);
	for(re i=20;i>=0;i--)
		if(dp[fa[x][i]]>=dp[y])
			x=fa[x][i];
	if(x==y)return x;
	for(re i=20;i>=0;i--)
		if(fa[x][i]!=fa[y][i])
			x=fa[x][i],y=fa[y][i];
	return fa[x][0];
}
bool cmp(POT x,POT y){
	if(x.dep!=y.dep)return x.dep<y.dep;
	return x.dfn<y.dfn;
}
int rt[N];
struct pst_tree{
	int ls[N*80],rs[N*80];
	int siz[N*80];
	int seg;
	inline void pushup(int pre,int x){
		siz[x]=0;
		if(ls[x])siz[x]+=siz[ls[x]];
		if(rs[x])siz[x]+=siz[rs[x]];
		return ;
	}
	inline void ins(int pre,int &x,int l,int r,int pos,int v){
		if(!x){x=++seg;siz[x]=siz[pre];ls[x]=ls[pre];rs[x]=rs[pre];}
		if(l==r){siz[x]+=v;return ;}
		int mid=l+r>>1;
		if(pos<=mid){
			if(ls[x]==ls[pre])ls[x]=0;
			ins(ls[pre],ls[x],l,mid,pos,v);
		}
		else {
			if(rs[x]==rs[pre])rs[x]=0;
			ins(rs[pre],rs[x],mid+1,r,pos,v);
		}
		pushup(pre,x);return ;
	}
	inline int query(int x,int l,int r,int ql,int qr){
		if(!x)return 0;
		if(ql<=l&&r<=qr){return siz[x];}
		int mid=l+r>>1,ret=0;
		if(ql<=mid)ret+=query(ls[x],l,mid,ql,qr);
		if(qr>mid)ret+=query(rs[x],mid+1,r,ql,qr);
		return ret;
	}
}pst;
signed main(){
	scanf("%d%d",&n,&m);
	for(re i=1;i<=n;i++)scanf("%d",&sca[i].c),sca[i].id=i;
	for(re i=2,x;i<=n;i++){
		scanf("%d",&x);
		add_edg(x,i);
	}
	sca[1].dep=1;
	dfs_fir(1);
	for(re i=1;i<=n;i++){
		dp[i]=sca[i].dep;
		dn[i]=sca[i].dfn;
		dm[i]=sca[i].dfm;
	}
	sort(sca+1,sca+n+1,cmp);
	for(re i=1;i<=n;i++){
		pst.ins(rt[sca[i].dep-1],rt[sca[i].dep],1,n,sca[i].dfn,1);
		st[sca[i].c].insert(sca[i]);
		int lca;
		POT nu;nu.id=0;
		POT pre=st[sca[i].c].find(sca[i])==st[sca[i].c].begin()?nu:*--st[sca[i].c].find(sca[i]);
		POT nxt=((++st[sca[i].c].find(sca[i]))==st[sca[i].c].end())?nu:*++st[sca[i].c].find(sca[i]);
		if(pre.id){
			lca=LCA(sca[i].id,pre.id);
			pst.ins(rt[sca[i].dep-1],rt[sca[i].dep],1,n,dn[lca],-1);
		}
		if(nxt.id){
			lca=LCA(sca[i].id,nxt.id);
			pst.ins(rt[sca[i].dep-1],rt[sca[i].dep],1,n,dn[lca],-1);
		}
		if(pre.id&&nxt.id){
			lca=LCA(pre.id,nxt.id);
			pst.ins(rt[sca[i].dep-1],rt[sca[i].dep],1,n,dn[lca],1);
		}
	}
	for(re i=1,u,d;i<=m;i++){
		scanf("%d%d",&u,&d);
		printf("%d\n",pst.query(rt[min(dp[u]+d,ds[u])],1,n,dn[u],dm[u]));
	}
}

·

T3 抛硬币

这个好像是这次考试中最最最简单的题了

主要是这数据范围非常的迷人 3000 , 我还是不敢用\(O(n^2)\)的算法

正解就是非常非常慢的\(O(n^2)\)算法

直接看官方题解吧,这次题解给的极其善良

AC_code
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int N=3e3+5;
const ll mod=998244353;
char a[N];
int l,len;
ll dp[N][N],las[30];
ll ans;
signed main(){
	scanf("%s",a+1);
	len=strlen(a+1);
	scanf("%d",&l);
	dp[0][0]=1;
	for(re i=1;i<=len;i++){
		dp[i][0]=1;
		for(re j=1;j<=l&&j<=i;j++){
			dp[i][j]=(dp[i-1][j-1]+dp[i-1][j])%mod;
			if(las[a[i]-'a']!=0)dp[i][j]=(dp[i][j]+mod*101-dp[las[a[i]-'a']-1][j-1])%mod;
		}
		las[a[i]-'a']=i;
	}
	printf("%lld",dp[len][l]);
}

这里还有一个毒瘤的地方,

一般来说,\(dp[len][l]\)比任何的\(dp[i][l]\)都大,所以求取max是没有问题的

但是这里取模啦啊啊啊啊啊啊啊

posted @ 2021-07-14 12:09  fengwu2005  阅读(59)  评论(0编辑  收藏  举报