【2023.11.13】NOIP2023模拟试题-33
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\) 的每个节点的加入其实就是从子树往根节点构建树,也就是新增一条链或者给子树当根节点的过程(因为倒序,所以一定是根节点)。那么就有
为什么要从子树往根构建而不从根往子树构建呢?
因为考虑所有子树,我们只需要枚举根节点,
而若考虑所有根节点,我们就需要枚举子树。
显然枚举根节点更方便。
接下来我们考虑题目的限制条件。题目更改的条件是
如果第 \(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]\) ( 新建根节点 )
\(f[i+1][j][k-1]+=f[i][j][k]\times k\) ( 填补 \(k\) 个待定位中的任意一个 )
若 \(l=1\)
显然可能新增左或右 \(2\) 种待定节点。
因此:
\(f[i+1][j][k]+=f[i][j][k] \times 2\) ( 根节点 + 创建左或右待定子节点 )
\(f[i+1][j][k-1]+=f[i][j][k]\times k\times 2\) ( 填补待定位 + 创建待定子节点 )
若 \(l=2\)
显然可能会新增 \(1\) 种待定节点。
\(f[i+1][j+1][k+2]+=f[i][j][k]\) ( 根节点 + 创建待定子节点 )
\(f[i+1][j][k+1]+=f[i][j][k]\times k\) ( 填补根节点 + 创建待定子节点 )
\(f[i+1][j-1][k]+=f[i][j][k]\times (j-1) \times k \times 2\) ( 填补待定点 + 引入(所填补的待定点所在链以外)任意一条不保证序号大于自己的链放于左或右儿子 + 创建待定点 )
\(f[i+1][j][k+1]+=f[i][j][k]\times j \times 2\) ( 引入任意链 + 创建待定点 )
参考代码
/*
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 是我能到达的地方吗?