zzh的NOIP2021膜你赛

zzh的NOIP2021模拟赛

没zky的那么水

所以写挂情有可原

A Sequence

题意:给定两个1~n的排列,求最大前缀满足两排列在该前缀中任意区间的最小值位置相同

std维护了两个单减单调栈,\(O(n)\)扫了一遍,若栈顶元素位置不一样则输出\(i-1\)

想了想为什么这样是对的

单调栈常用来维护某个前缀的后缀的性质(然后扫一遍就是维护任意区间

比如说这个题维护的就是最小值

若该前缀合法,两个单调栈的栈顶无论在何时都应该是相同位置

码:

#include<bits/stdc++.h>
using namespace std;
const int N=500010;

int ans,st1[N],st2[N],n,a[N],b[N],top1=0,top2=0;

int main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++) cin>>b[i];
	
	for(int i=1;i<=n;i++){
		while(top1&&st1[top1]>=a[i]) --top1;
		while(top2&&st2[top2]>=b[i]) --top2;
		st1[++top1]=a[i],st2[++top2]=b[i];
		if(top1!=top2){ans=i-1;break;}
	}
	
	cout<<ans;
	
	return 0;
}

B Cave

题意:给定一棵完全二叉树,对其进行m次删边/加边操作,求每次操作后节点\(i,j\)连通的\((i,j)\)数对对数

删边意味着本来\(s\)个连通块变为\(s+1\)

加边意味着本来\(s\)个连通块变为\(s-1\)

不同连通块中的节点不连通

若一个连通块有\(d\)个节点,则\(\, ans+=\frac{d\times (d-1)}{2}\)

用siz[i]来维护以i为根节点的子树大小

用tag[i]来标记该点与父节点之间是否有边(1为有边 0为无边)

码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=100010;

int tag[N],n,m;
ll siz[N];

int main(){
	cin>>n>>m;
	for(int i=n;i>=1;--i){
		siz[i]++;
		siz[i/2]+=siz[i];
		tag[i]=1;
	}//完全二叉树建法 
	
	tag[1]=0;
	ll ans=1ll*n*(n-1)/2;
	int x;
	
	for(int i=1;i<=m;++i){
		cin>>x;
		if(tag[x]){
			int root=x,si=siz[x];
			while(tag[root]) siz[root/2]-=si,root/=2;
			ans-=1ll*siz[root]*siz[x];
			tag[x]=0;
		}else{
			tag[x]=1;
			int root=x,si=siz[x];
			while(tag[root]) siz[root/2]+=si,root/=2;
			ans+=1ll*(siz[root]-si)*si;
		}
		cout<<ans<<endl;
	} 
	
	return 0;
}

C Coin

题意:长度为\(L\)数轴上有\(N\)个关键点,第\(i\)个位置为\(x[i]\),数轴上任意一个点到\(s\)个关键点的距离之和不超过\(T\),求\(s\)的最大值

正解双指针,维护右区间定时左区间的最值

不断右移右指针并维护左区间最值,枚举答案

为什么标程那么麻烦

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=200009;

ll L,T,x[N];
int n;

int main(){
	cin>>n>>L>>T;
	for(int i=1;i<=n;i++){
		cin>>x[i];
	}
	int p=1,md=1;//p表示左指针,md表示区间中点
	ll nwans=0;//该区间的比较距离
	int ans=1;//最终答案
	
	for(int i=2;i<=n;i++){
		int nw=(i+p)>>1;//修改区间后的中点
    	if(nw>md) md=nw;//判断中点是否变化,如果变了那就改
    	nwans+=x[i]-x[md];//更新区间比较距离
		while(p<i&&nwans>T){
			int nw=(i+p+1)>>1;
			if(nw>md) md=nw;
			nwans-=x[md]-x[p];p++;
		}//左节点右移至满足条件
		ans=max(ans,i-p+1);
	}
	cout<<ans;
	
	
	return 0;
}

D Tree

题意:给出\(n\)个节点的连边方案,求构成不同的树中的安全点的总个数。(定义安全点为根节点或比自己父亲节点点权大的点)

做法是线段树/树状数组优化DP

首先我们把这道题变成一道计数题,计某个节点在能构成的所有树中是安全点的概率

\(f[i]\)为节点\(i\)是安全点的概率,则有:

\[f[i]=\frac{1}{r_i-l_i+1} \sum_{j=l_i}^{r_i}f[j](h[i]\geq h[j]) \]

发现这个转移是由一个区间转移到一个点,需要区间查询和单点修改,所以想到线段树和树状数组

发现转移方程里有判断,不爽,用数据结构处理不了,考虑优化DP顺序,按高度从小到大排序,高度相同按序号排

除法要用逆元

我们还需要所有情况的总数:

\[\prod_{i=2}^{n}(r_i-l_i+1) \]

最终答案是:

\[\prod_{i=2}^n(r_i-l_i+1)\cdot\sum_{i=1}^nf[i] \]

码:

#include<bits/stdc++.h>
#define N 100002
using namespace std;
typedef long long ll;
const int mod=998244353;
int n;
ll tr[N<<2],ni[N],ans;
struct node{
    int h,id,l,r;
    inline bool operator <(const node &b)const{
        if(h!=b.h)return h<b.h;
        return id<b.id;
    }
}a[N];
inline ll rd(){
    ll x=0;char c=getchar();bool f=0;
    while(!isdigit(c)){if(c=='-')f=1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x;
}
inline void MOD(ll &x){x=x>=mod?x-mod:x;}//奇奇怪怪的卡常技巧
inline ll power(ll x,ll y){//快速幂
    ll ans=1;
    while(y){
        if(y&1)ans=ans*x%mod;
        x=x*x%mod;
        y>>=1;
    }
    return ans;
}
void ins(int cnt,int l,int r,int x,ll y){//线段树单点插入
    if(l==r){
        MOD(tr[cnt]+=y);
        return;
    }
    int mid=(l+r)>>1;
    if(mid>=x)ins(cnt<<1,l,mid,x,y);
    else ins(cnt<<1|1,mid+1,r,x,y);
    MOD(tr[cnt]=tr[cnt<<1]+tr[cnt<<1|1]);
}
ll query(int cnt,int l,int r,int L,int R){//线段树区间查询
    if(l>=L&&r<=R)return tr[cnt];
    int mid=(l+r)>>1;
    ll ans=0;
    if(mid>=L)MOD(ans+=query(cnt<<1,l,mid,L,R));
    if(mid<R)MOD(ans+=query(cnt<<1|1,mid+1,r,L,R));
    return ans;
}
int main(){
    n=rd();
    for(int i=1;i<=n;++i)a[i].h=rd(),a[i].id=i;
    ll num=1;
    for(int i=1;i<=n;++i)ni[i]=power(i,mod-2);//求逆元
    for(int i=2;i<=n;++i){
        a[i].l=rd();a[i].r=rd();
        num=num*(a[i].r-a[i].l+1)%mod;//预处理所有区间的乘积
    }
    a[1].l=a[1].r=1;
    sort(a+1,a+n+1);//处理DP顺序
    for(int i=1;i<=n;++i){
        ll x=query(1,1,n,a[i].l,a[i].r)*ni[a[i].r-a[i].l+1]%mod;//算f[i]
        if(a[i].id==1)x=1;
        MOD(ans+=x);
        ins(1,1,n,a[i].id,x);//把f[i]插入线段树
    }
    printf("%lld\n",ans*num%mod);
    return 0;
}
posted @ 2021-02-25 19:21  wsy_jim  阅读(80)  评论(0编辑  收藏  举报