【2023.11.13】NOIP2023模拟试题-33.md

发布时间 2023-11-13 21:22:36作者: DZhearMins

T1

贪心地找到和最大的组的较大数删除是最优选择,因此开线段树维护全局最大数,并单点更新指定位置的值。

参考代码

展开代码
#include<bits/stdc++.h>
using namespace std;
#define fi(l,r) for(int i=l;i<=r;++i)
#define ff(i,l,r) for(int i=l;i<=r;++i)
#define ll long long
#define ly __int128
#define lp ll
#define lson(x) ((x)<<1)
#define rson(x) (lson(x)|1)
#define Lson(x) tr[lson(x)]
#define Rson(x) tr[rson(x)]
#define mod %P
#define N 500005
struct Info{
	int sum,pos;
	bool operator < (const Info b)const{return sum<b.sum;}
};
int n,m,tid,a[N],nxt[N],pre[N];
struct node{int l,r,sum,mxi;}tr[N*30];//全局查询+单点修改最大的 a[mxi]+a[nxt[mxi]]
#define valof(x) (a[x]+a[nxt[x]])
void pushup(int x){
	if(tr[x].l==tr[x].r)tr[x].mxi=tr[x].l;
	else{
		if(valof(Lson(x).mxi)>valof(Rson(x).mxi))tr[x].mxi=Lson(x).mxi;
		else tr[x].mxi=Rson(x).mxi;
	}
	return;
}
void build(int x,int l,int r){
	tr[x].l=l,tr[x].r=r;
	if(l==r){
		tr[x].sum=a[l],tr[x].mxi=l;
		return;
	}
	int mid=(l+r)>>1;
	build(lson(x),l,mid);
	build(rson(x),mid+1,r);
	pushup(x);
	return;
}
void update(int x,int pos){
	if(tr[x].l==tr[x].r){
		tr[x].sum=a[pos];
		return;
	}
	if(pos<Rson(x).l)update(lson(x),pos);
	else update(rson(x),pos);
	pushup(x);
	return;
}
int main(){
	freopen("necklace.in","r",stdin);
	freopen("necklace.out","w",stdout);
	scanf("%d %d %d",&tid,&n,&m);
	fi(1,n){
		nxt[i]=i+1;pre[i]=i-1;
		scanf("%d",&a[i]);
	}
	nxt[n]=1,pre[1]=n;
	build(1,1,n);
	ff(t,1,n-m){
		int x=tr[1].mxi;
		if(a[x]<a[nxt[x]])x=nxt[x];//找到和最大的一对里面值最大的一个
		nxt[pre[x]]=nxt[x];
		pre[nxt[x]]=pre[x];
		a[x]=0;//删除这个点
		update(1,x);
		update(1,pre[x]);
	}
	printf("%d",valof(tr[1].mxi));
	return 0;
}

T2

啊 $\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ $ 又是计数 dp 题

我们先考虑问题的弱化版:儿子的个数在 \([l_i,r_i]\) 之间,并且儿子序号的最\(k_i > i\)

如果是这样的话,我们只需要开一个二维 dp \(f[i][j]\) 代表 第 \(i\) 到第 \(n\) 个点组成了 \(j\) 棵树的的组合方式。那么我们从 \(n\)\(1\) 的每个节点的加入其实就是从子树往根节点构建树,也就是新增一条链或者给子树当根节点的过程(因为倒序,所以一定是根节点)。那么就有

\[\forall l \in [l_i,r_i] \Rightarrow \begin{cases} f[i-1][j+1]&+=f[i][j] &,\;l=0 &\;\;\text{( 新增链 )} \\ f[i-1][j]&+=f[i][j] &,\;l=1 &\text{( 新增一颗子树根节点 )} \\ f[i-1][j-1]&+=f[i][j] &,\;l=2 &\text{( 新增两个子树的共同根节点 )} \end{cases} \]

为什么要从子树往根构建而不从根往子树构建呢?

因为考虑所有子树,我们只需要枚举根节点,

而若考虑所有根节点,我们就需要枚举子树。

显然枚举根节点更方便。

接下来我们考虑题目的限制条件。题目更改的条件是

如果第 \(i\) 个节点不为叶子,那么其儿子的最大编号 \(k_i > i\)

其实就是从子树往根构建的过程中,除了新建子树以外,还可以添加到子树的非根节点上作为子树的子树。但是我们要从 \(1\)\(n\) 顺序枚举,所以不用考虑成为根节点的情况(不然不能保证子节点与父节点的大小关系)

因此,我们只需要新记录一维需要填补的空缺儿子位置个数 \(k\) 就可以了。当我们从 \(1\)\(n\) 顺序枚举的时候,显然,待定节点的序号一定大于父亲节点。对于 \(\forall l \in [l_i,r_i]\),我们分类讨论,只需要保证 \(l=1\)\(2\) 的节点要有待定子节点就行了 :

\(l=0\)

显然只能当做新子树或者子树的子树。

因此:

\(f[i+1][j+1][k]+=f[i][j][k]\) ( 新建根节点 )

1

\(f[i+1][j][k-1]+=f[i][j][k]\times k\) ( 填补 \(k\) 个待定位中的任意一个 )

2

\(l=1\)

显然可能新增左或右 \(2\) 种待定节点。

因此:

\(f[i+1][j][k]+=f[i][j][k] \times 2\) ( 根节点 + 创建左或右待定子节点 )

3

\(f[i+1][j][k-1]+=f[i][j][k]\times k\times 2\) ( 填补待定位 + 创建待定子节点 )

4

\(l=2\)

显然可能会新增 \(1\) 种待定节点。

\(f[i+1][j+1][k+2]+=f[i][j][k]\) ( 根节点 + 创建待定子节点 )

5

\(f[i+1][j][k+1]+=f[i][j][k]\times k\) ( 填补根节点 + 创建待定子节点 )

6

\(f[i+1][j-1][k]+=f[i][j][k]\times (j-1) \times k \times 2\) ( 填补待定点 + 引入(所填补的待定点所在链以外)任意一条不保证序号大于自己的链放于左或右儿子 + 创建待定点 )

7

\(f[i+1][j][k+1]+=f[i][j][k]\times j \times 2\) ( 引入任意链 + 创建待定点 )

8

参考代码

/*
  bug:1.还要钦定这个节点下方待补节点的左右位置
 */
#include<bits/stdc++.h>
using namespace std;
#define fi(l,r) for(int i=l;i<=r;++i)
#define ff(i,l,r) for(int i=l;i<=r;++i)
#define ll long long
#define ly __int128
#define lp ll
#define lson(x) ((x)<<1)
#define rson(x) (lson(x)|1)
#define Lson(x) tr[lson(x)]
#define Rson(x) tr[rson(x)]
#define mod %P
#define P 1000000007
#define N 305
int tid,T,n,ql[N],qr[N];
ll f[N][N][N<<1];
void work(){
	memset(f,0,sizeof f);
	scanf("%d",&n);
	fi(1,n)scanf("%d %d",&ql[i],&qr[i]);
	f[0][0][0]=1;											//别忘了初始化
	fi(0,n-1){
		ff(j,0,i){
			ff(k,0,i<<1){
				if(f[i][j][k]==0)continue;
				f[i][j][k]%=P;
				if(ql[i+1]==0){
					f[i+1][j+1][k]+=f[i][j][k];				//新建根节点
					if(k)f[i+1][j][k-1]+=f[i][j][k]*k;		//填补k个待定位中的任意一个
				}
				if(ql[i+1]<=1&&qr[i+1]>=1){
					f[i+1][j+1][k+1]+=f[i][j][k]*2;			//根节点 + 创建左或右待定子节点	//bug #1 : 同时还要钦定这个节点下的待补节点位置
					f[i+1][j][k]+=f[i][j][k]*k*2;			//填补待定位 + 创建待定子节点
				}
				if(qr[i+1]==2){
					f[i+1][j+1][k+2]+=f[i][j][k];			//根节点+创建待定子节点
					f[i+1][j][k+1]+=f[i][j][k]*k;			//填补根节点+创建待定子节点
					if(j)f[i+1][j-1][k]+=f[i][j][k]*(j-1)*k*2;//填补待定点 + 引入(所填补的待定点所在链以外)任意一条不保证序号大于自己的链放于左或右儿子 + 创建待定点
					f[i+1][j][k+1]+=f[i][j][k]*j*2;			//引入任意链 + 创建待定点
				}
			}
		}
	}
	printf("%lld\n",f[n][1][0]%P);							//点统毕,只单链,无待定点
	return;
}
int main(){
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	scanf("%d %d",&tid,&T);
	while(T-->0)work();
	return 0;
}

T3 T4

什么?T3 T4 是我能到达的地方吗?