zoukankan      html  css  js  c++  java
  • 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 imes (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)cdotsum_{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
    ",ans*num%mod);
        return 0;
    }
    
  • 相关阅读:
    浅析 Java 中的 final 关键字
    谷歌Java编程风格指南
    分布式事务之两阶段提交协议(2PC)and 使用事件和消息队列实现分布式事务
    零和博弈与木桶定律
    Executors类创建四种常见线程池
    软件设计的原则&101个设计模式-2011年04月25日 陈皓
    编程中的命名设计那点事-陈皓
    从面向对象的设计模式看软件设计- 2013年02月01日 陈皓
    SQL语句
    分布式事务
  • 原文地址:https://www.cnblogs.com/wsyunine/p/14448833.html
Copyright © 2011-2022 走看看