zoukankan      html  css  js  c++  java
  • HNOI2017

    单旋

    Description

    给你一棵单旋splay,让你维护执行某些操作之后节点的深度信息。

    单旋splay就是说在祖父-父亲-我,三点一线的时候,仍坚持旋转我两次。

    双旋就是先旋父亲一次,再旋我一次。

    Solution

    题目只要求旋转最小值和最大值到根(其实旋转别的似乎也差不多??)。手模一个三层以上的splay可以发现执行完操作后,相当于把该节点移到根,其儿子过继给父亲,树的其他部分形态都不变。

    这说明每次操作深度发生变化的点都很少。比如说旋转最小值(x)。那么它深度变成1,其余部分除了它儿子深度不变以外,深度都加+1。把点权离散化,因为保证了权值两两不同,直接用其离散化以后的值作为编号,那么深度+1的点就是([fa[x],maxn])这段区间。区间修改,上线段树直接搞定。

    旋转最大值同理。

    而删除操作,就是所有节点深度-1,也直接线段树就行。

    最后还剩下插入操作。直接模拟显然是不行的。考虑找到(x)的前驱和后继(前驱是小于x的中最大的,后继是大于(x)的中最小的)。显然只有深度较深的那个没有儿子,把(x)接上去就可以啦。

    怎么找到前驱后继?并行维护一个set就可以啦。

    所以每次操作,维护节点的父亲和儿子,维护set,维护线段树。

    Code

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    typedef set<int>::iterator sit;
    inline int read(){//be careful for long long!
        register int x=0,f=1;register char ch=getchar();
        while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
        while(isdigit(ch)){x=x*10+(ch^'0');ch=getchar();}
        return f?x:-x;
    }
    
    const int N=1e5+10;
    int n,m,fa[N],ch[N][2],a[N],rt;
    struct s_query{int op,x;}q[N];
    set<int> s;
    
    namespace Segment_Tree{
    #define ls(p) p<<1
    #define rs(p) p<<1|1
        int tr[N<<2],tag[N<<2];
        inline void pushdown(int p){
    	if(!tag[p])return;
    	tag[ls(p)]+=tag[p],tag[rs(p)]+=tag[p];
    	tr[ls(p)]+=tag[p],tr[rs(p)]+=tag[p];
    	tag[p]=0;
        }
        inline void Set(int p,int l,int r,int pos,int v){
    	if(l==r){tr[p]=v;return;}
    	pushdown(p);int mid=(l+r)>>1;
    	if(pos<=mid)Set(ls(p),l,mid,pos,v);
    	else Set(rs(p),mid+1,r,pos,v);
        }
        inline void Modify(int p,int l,int r,int L,int R,int v){
    	if(L<=l&&r<=R){tr[p]+=v,tag[p]+=v;return;}
    	pushdown(p);int mid=(l+r)>>1;
    	if(L<=mid)Modify(ls(p),l,mid,L,R,v);
    	if(mid<R)Modify(rs(p),mid+1,r,L,R,v);
        }
        inline int Query(int p,int l,int r,int pos){
    	if(l==r)return tr[p];
    	pushdown(p);int mid=(l+r)>>1;
    	if(pos<=mid)return Query(ls(p),l,mid,pos);
    	else return Query(rs(p),mid+1,r,pos);
        }
    }using namespace Segment_Tree;
    
    inline sit Pre(sit it){return --it;}
    inline void Insert(int x){
        if(!s.size()){s.insert(x);Set(1,1,m,x,1);rt=x;puts("1");return;}
        int pre=(*Pre(s.lower_bound(x))),nxt=(*s.upper_bound(x));
        int d_pre=Query(1,1,m,pre),d_nxt=Query(1,1,m,nxt),d_x;
        if(s.lower_bound(x)!=s.begin()&&!ch[pre][1]){ch[pre][1]=x;fa[x]=pre;d_x=d_pre+1;}
        if(!fa[x]){ch[nxt][0]=x;fa[x]=nxt;d_x=d_nxt+1;}
        Set(1,1,m,x,d_x);s.insert(x);
        printf("%d
    ",d_x);
    }
    inline void Splay(int type){
        if(!type){
    	int p=(*s.begin());printf("%d
    ",Query(1,1,m,p));
    	if(p==rt)return;
    	Set(1,1,m,p,1);Modify(1,1,m,fa[p],m,1);
    	ch[fa[p]][0]=ch[p][1];fa[ch[p][1]]=fa[p];
    	fa[rt]=p;fa[p]=0;ch[p][1]=rt;rt=p;
        }
        else{
    	int p=(*Pre(s.end()));printf("%d
    ",Query(1,1,m,p));
    	if(p==rt)return;
    	Set(1,1,m,p,1);Modify(1,1,m,1,fa[p],1);
    	ch[fa[p]][1]=ch[p][0];fa[ch[p][0]]=fa[p];
    	fa[rt]=p;fa[p]=0;ch[p][0]=rt;rt=p;
        }
    }
    inline void Pop(int type){
        Splay(type);s.erase(rt);
        Modify(1,1,m,1,m,-1);
        rt=ch[rt][type^1];fa[rt]=0;
    }
    
    int main(){
    //    freopen("splay5.in","r",stdin);freopen("out.out","w",stdout);
        n=read();
        for(int i=1;i<=n;++i){q[i].op=read();if(q[i].op==1)q[i].x=a[++m]=read();}
        sort(a+1,a+m+1);
        for(int t=1;t<=n;++t){
    	switch(q[t].op){
    	case 1:{
    	    int x=lower_bound(a+1,a+m+1,q[t].x)-a;
    	    Insert(x);break;
    	}
    	case 2:Splay(0);break;
    	case 3:Splay(1);break;
    	case 4:Pop(0);break;
    	case 5:Pop(1);break;
    	}
        }
        return 0;
    }
    
    

    影魔

    Solution

    暴力是st表预处理出区间最大值,然后( ext O(n^2))枚举(i,j)计算贡献,没有什么优化空间,考虑换一种枚举方式。

    每一对((i,j))做贡献,其实也可以看作中间的最大值(k_i)的贡献。

    所以我们尝试从枚举(k_i)的角度入手。先求出从(k_i)向左走,第一个比它大的位置,记为(L_i);还有向右走第一个比它大的位置,记为(R_i)。如果区间再往两边延伸,(k_i)就不是区间的最大值了,所以([L_i,R_i])就是它的“影响范围”。所有的(L,R)可以用一个单调栈从左往右扫一遍求出。

    对于每一个(k_i),我们考虑一下它的贡献:

    • (L_i,R_i)作为左右端点。有(p_1)的贡献。
    • (L_i)作为左端点,([i+1,R_i-1])作为右端点。每一对有(p_2)的贡献。
    • ([L_i+1,i-1])作为左端点,(R_i)作为右端点。每一对有(p_2)的贡献。

    发现第二种和第三种点对的贡献,有某一个端点是一段区间。可以把贡献挂链,挂在另一个孤立的端点处。这样每次计算贡献,扫到那个孤立端点时,区间增加贡献即可。

    例如第二种,可以把([i+1,R_i-1])的区间放到(L_i),当扫到(L_i)时,把贡献加上。第三种同理。

    而对于第一种,就是单点修改,我们看作把([R_i,R_i])挂在(L_i)处,或者([L_i,L_i])挂在(R_i)处都行。

    现在做法显然了。

    用一棵线段树维护贡献。从左往右枚举端点,每扫到一个点(i),把挂在其下的贡献加到线段树上。

    询问考虑差分解决。对于一个询问([l,r]),拆成两个询问(l-1)(r)

    扫到(l-1)时,记([l,r])的贡献和为(sum1);扫到(r)时,记([l,r])贡献和为(sum2)。那么答案就是(sum2-sum1)

    Code

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    inline int read(){//be careful for long long!
        register int x=0,f=1;register char ch=getchar();
        while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
        while(isdigit(ch)){x=x*10+(ch^'0');ch=getchar();}
        return f?x:-x;
    }
    
    const int N=2e5+10;
    int n,m,p1,p2,a[N],L[N],R[N],stk[N],tp,tot;
    ll ans[N];
    struct s_node{int l,r,p,v,id;inline bool operator <(const s_node &a)const{return p<a.p;}}op[N*3],q[N<<1];
    
    namespace Segment_Tree{
        ll tr[N<<2],tag[N<<2];
    #define ls(p) p<<1
    #define rs(p) p<<1|1
        inline void pushdown(int p,int l,int r){
    	if(!tag[p])return;
    	tag[ls(p)]+=tag[p],tag[rs(p)]+=tag[p];
    	int mid=(l+r)>>1;
    	tr[ls(p)]+=tag[p]*(mid-l+1),tr[rs(p)]+=tag[p]*(r-mid);
    	tag[p]=0;
        }
        inline void update(int p){tr[p]=tr[ls(p)]+tr[rs(p)];}
        inline void Modify(int p,int l,int r,int L,int R,int v){
    	if(L<=l&&r<=R){tr[p]+=1ll*(r-l+1)*v;tag[p]+=v;return;}
    	pushdown(p,l,r);int mid=(l+r)>>1;
    	if(L<=mid)Modify(ls(p),l,mid,L,R,v);
    	if(R>mid)Modify(rs(p),mid+1,r,L,R,v);
    	update(p);
        }
        inline ll Query(int p,int l,int r,int L,int R){
    	if(L<=l&&r<=R)return tr[p];
    	pushdown(p,l,r);int mid=(l+r)>>1;ll ans=0;
    	if(L<=mid)ans+=Query(ls(p),l,mid,L,R);
    	if(R>mid)ans+=Query(rs(p),mid+1,r,L,R);
    	return ans;
        }
    }using namespace Segment_Tree;
    
    int main(){
        n=read(),m=read();p1=read(),p2=read();
        for(int i=1;i<=n;++i)a[i]=read();
        a[n+1]=n+1;
        for(int i=1;i<=n+1;++i){
    	while(tp&&a[stk[tp]]<=a[i])R[stk[tp]]=i,--tp;
    	L[i]=stk[tp];stk[++tp]=i;
        }
        for(int i=1;i<=n;++i){
    	if(1<=L[i]&&R[i]<=n)op[++tot]=(s_node){L[i],L[i],R[i],p1,0};
    	if(L[i]<i-1&&R[i]<=n)op[++tot]=(s_node){L[i]+1,i-1,R[i],p2,0};
    	if(R[i]>i+1&&L[i]>=1)op[++tot]=(s_node){i+1,R[i]-1,L[i],p2,0};
        }
        sort(op+1,op+tot+1);
        for(int i=1;i<=m;++i){
    	int l=read(),r=read();
    	ans[i]+=1ll*(r-l)*p1;//(l,r)==empty;
    	q[i]=(s_node){l,r,l-1,-1,i};
    	q[m+i]=(s_node){l,r,r,1,i};
        }
        sort(q+1,q+m+m+1);
        int o_t=1,q_t=1;while(!q[q_t].p&&q_t<=m+m)++q_t;
        for(int i=1;i<=n;++i){
    	while(op[o_t].p<=i&&o_t<=tot)Modify(1,1,n,op[o_t].l,op[o_t].r,op[o_t].v),++o_t;
    	while(q[q_t].p<=i&&q_t<=m+m)ans[q[q_t].id]+=Query(1,1,n,q[q_t].l,q[q_t].r)*q[q_t].v,++q_t;
        }
        for(int i=1;i<=m;++i)printf("%lld
    ",ans[i]);
        return 0;
    }
    
    

    礼物

    Solution

    给其中一个手环增加亮度,相当于给第一个手环增加c的亮度,c可以是负数。

    [egin{align} sumlimits_{i=1}^n(x_i+c-y_i)^2&=sumlimits_{i=1}^nx_i^2+y_i^2+c^2+2c(x_i-y_i)-2x_iy_i\ &=ncdot c^2+2ccdot(sumlimits_{i=1}^n x-sumlimits_{i=1}^n y)+sumlimits_{i=1}^n (x_i^2+y_i^2)-2cdot sumlimits_{i=1}^nx_iy_i end{align} ]

    前三项是关于c的一元二次函数,( ext O(1))求最值。

    最后一项是变量。我们要式子值最小,那么 (sumlimits_{i=1}^nx_iy_i) 要最大。

    (y) 翻转一下,那式子就是(sumlimits_{i=1}^nx_iy'_{n-i+1})。可以发现就是一个普通的卷积。答案在(n+1)出取到。

    因为题目要求旋转手环过程中的最大值。所以把(y')倍长,答案就是(n+1,n+2dots 2n-1)中 的最大值。

    现在来看这道题还是很套路的,像把(y)翻转化成卷积,在成七也才刚考过一次。我还是太cai了,想半天才想到。希望这次印象能深刻一点。

    然后平移的话要想到倍长之后在后面取这个套路。

    Code

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    inline int read(){//be careful for long long!
        register int x=0,f=1;register char ch=getchar();
        while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
        while(isdigit(ch)){x=x*10+(ch^'0');ch=getchar();}
        return f?x:-x;
    }
    
    const int N=2.7e5+10,mod=998244353;
    int n,m,a[N],b[N],trans[N];
    int f[N],g[N],sum;ll ans;
    inline int power(int base,int n){int ans=1;for(;n;n>>=1,base=1ll*base*base%mod)if(n&1)ans=1ll*ans*base%mod;return ans;}
    inline int F(int x){return m*x*x-2*x*sum;}
    
    inline void NTT(int *a,int n,int type){
        static unsigned long long f[N];
        for(int i=0;i<n;++i)f[i]=a[i];
        for(int i=0;i<n;++i)if(i<trans[i])swap(f[i],f[trans[i]]);
        for(int len=2;len<=n;len<<=1){
    	int e=power(3,(mod-1)/len),d=len>>1;if(type)e=power(e,mod-2);
    	for(int p=0;p<n;p+=len)
    	    for(int i=p,nw=1;i<p+d;++i,nw=1ll*nw*e%mod){
    		unsigned long long t=f[i+d]*nw%mod;
    		f[i+d]=f[i]+mod-t;f[i]+=t;
    	    }
        }
        for(int i=0;i<n;++i)a[i]=f[i]%mod;
    }
        
    int main(){
    //    freopen("in.in","r",stdin);
        m=read(),n=read();
        for(int i=0;i<m;++i)a[i]=read();
        for(int i=0;i<m;++i)b[i]=read();
        for(int i=0;i<m;++i)f[i]=a[i],g[m+m-i-1]=g[m-i-1]=b[i],sum+=a[i]-b[i],ans+=a[i]*a[i]+b[i]*b[i];
        ans+=min(F(floor((double)sum/m)),F(ceil((double)sum/m)));
        for(n=1;n<m+m+m;n<<=1);
        for(int i=0;i<n;++i)trans[i]=(trans[i>>1]>>1)|(i&1?(n>>1):0);
        NTT(f,n,0),NTT(g,n,0);
        for(int i=0;i<n;++i)f[i]=1ll*f[i]*g[i]%mod;
        NTT(f,n,1);int inv=power(n,mod-2);
        for(int i=0;i<n;++i)f[i]=1ll*f[i]*inv%mod;
        int res=0;
        for(int i=m-1;i<=2*m-2;++i)res=max(res,f[i]);
        printf("%lld
    ",ans-res*2);
        return 0;
    }
    

    大佬

    Solution

    首先发现我能做的操作中只有一个是可以回复自己的自信值的,其他操作都和自信值无关。所以维持自己的自信值和攻击大佬可以分开考虑。

    首先考虑维持自己的自信值非负。可以设(f_{i,j})表示前(i)天,维持血量为(j)之余剩下的天数。转移很简单。然后对所有的(f_{i,j})(max),就得到了最大的可操作天数。

    ps:刚开始想的是对(f_{n,j})(max),但是其实并不是这么回事。一个(f_{i,j}),它代表的是整局游戏只进行(i)天,除了维持生命值以外剩下的(f_{i,j})天就可以用来攻击大佬。游戏并非一定要进行(n)天。可能不进行到(n)天,所能提供的操作天数还更多。

    然后考虑攻击大佬。我们发现实际造成伤害的只有怼大佬和平a(还嘴)。所以只需考虑用几次怼(不超过两次)和什么时候怼。只和天数、嘲讽值有关。

    可以先把所有的天数、嘲讽值的方案算出来。可以用(bfs+hash表去重)。复杂度与状态数有关。因为嘲讽值增长很快,题目又要求大佬生命值不能爆负,所以状态数不会太多。

    然后把所有方案按照嘲讽值排序。

    不怼和只怼一次很好算,接下来考虑怼两次的。设两次怼大佬(花费天数,嘲讽值)分别是((d1,f1),(d2,f2))。总共可以怼(X)天。

    那么有:

    [f1+f2leq C\ f1+f2+(X-d1-d2)geq C ]

    双指针扫一下就可以了。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    typedef pair<int,int> PII;
    inline int read(){//be careful for long long!
        register int x=0,f=1;register char ch=getchar();
        while(!isdigit(ch)){if(ch=='-')f=0;ch=getchar();}
        while(isdigit(ch)){x=x*10+(ch^'0');ch=getchar();}
        return f?x:-x;
    }
    
    const int N=1e2+10,_=1e6+10;
    int n,m,mc,a[N],w[N],f[N][N],c[N],mxc;
    
    inline void Cal_day(){
        memset(f,-1,sizeof(f));f[0][mc]=0;
        for(int i=0;i<n;++i)
    	for(int j=a[i+1];j<=mc;++j)
    	    if(~f[i][j]){
    		f[i+1][j-a[i+1]]=max(f[i+1][j-a[i+1]],f[i][j]+1);
    		f[i+1][min(j-a[i+1]+w[i+1],mc)]=max(f[i+1][min(j-a[i+1]+w[i+1],mc)],f[i][j]);
    	    }
        int mx=0;
        for(int i=1;i<=n;++i)for(int j=0;j<=mc;++j)mx=max(mx,f[i][j]);
        n=mx;
    }
    
    struct Hash{
        int mod=1000003,base=131;
        int head[1000003],tot;
        struct line{int x,y,next;}a[_];
        inline void Insert(int x,int y){
    	int name=(1ll*x*base+y)%mod;
    	a[++tot]=(line){x,y,head[name]};head[name]=tot;
        }
        inline bool Find(int x,int y){
    	int name=(1ll*x*base+y)%mod;
    	for(int i=head[name];i;i=a[i].next)if(a[i].x==x&&a[i].y==y)return 1;
    	return 0;
        }
    }H;
    
    struct node{int d,f,l;};
    PII stk[_];int tp;
    
    inline void Bfs(){
        queue<node> q;
        q.push((node){1,1,0});
        while(q.size()){
    	node nw=q.front();q.pop();
    	if(nw.d==n)continue;
    	q.push((node){nw.d+1,nw.f,nw.l+1});
    	if(nw.l<=1)continue;//直接还嘴
    	if(1ll*nw.f*nw.l>1ll*mxc)continue;//julao血量爆负
    	if(H.Find(nw.f*nw.l,nw.d+1))continue;//去重剪枝
    	H.Insert(nw.f*nw.l,nw.d+1);
    	stk[++tp]=make_pair(nw.f*nw.l,nw.d+1);
    	q.push((node){nw.d+1,nw.f*nw.l,nw.l});
        }
    }
    
    int main(){
        n=read(),m=read(),mc=read();
        for(int i=1;i<=n;++i)a[i]=read();
        for(int i=1;i<=n;++i)w[i]=read();
        for(int i=1;i<=m;++i)c[i]=read(),mxc=max(mxc,c[i]);
        Cal_day();
        Bfs();
        sort(stk+1,stk+tp+1);
        #define w first
        #define d second
        for(int t=1;t<=m;++t){
    	if(c[t]<=n){puts("1");continue;}//不需要怼
    	int flg=0;
    	for(int i=tp,j=1,mn=0x3f3f3f3f;i;--i){//双指针
    	    while(stk[i].w+stk[j].w<=c[t]&&j<=tp)mn=min(mn,stk[j].d-stk[j].w),++j;
    	    if(stk[i].w<=c[t]&&stk[i].w+n-stk[i].d>=c[t]){flg=1;break;}//怼一次
    	    if(stk[i].w-stk[i].d>=c[t]+mn-n){flg=1;break;}//Twice
    	}
    	printf("%d
    ",flg);
        }
        #undef w
        #undef d
        return 0;
    }
    
  • 相关阅读:
    状态线程
    C++编译 C # 调用方法
    图像算法集合
    openmp 和 thread 性能实测
    RGB转YUV 各种库的性能比较
    ipp 实现图像空间的转换
    Eigen 学习笔记
    线性代数笔记
    凸优化 笔记
    Windows系统服务器中安装Redis服务
  • 原文地址:https://www.cnblogs.com/fruitea/p/12093164.html
Copyright © 2011-2022 走看看