2019-8-5 考试总结

A. 矩阵游戏

是个水题,但是还是想了很长时间。

每个点对答案的贡献是$A[i][j]\times H[i]\times Z[j]$,

然后这个$sum$就是$\sum A[i][j]\times H[i]\times Z[j]$,

把$H[i]$提出来,也就是$\sum (H[i]\times \sum A[i][j]\times Z[j])$,

这个$A[i][j]$是有规律的,

这一行和上一行的差值就是$m\times \sum Z[j]$,

然后就可以逐行转移了。

最后是$O(n)$的。

丑陋的代码:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#define Maxn 1000050
#define Reg register
#define INF 0x7ffffff
#define int long long
#define mod 1000000007
using namespace std;
int n,m,k,sum=0,ans=0,H[Maxn],Z[Maxn],dp[Maxn];
string s;
signed main()
{
    scanf("%lld%lld%lld",&n,&m,&k);
    for(Reg int i=1;i<=n;++i) H[i]=Z[i]=1;
    for(Reg int i=1;i<=m;++i) H[i]=Z[i]=1;
    for(Reg int i=1,x,y;i<=k;++i)
    {
        cin>>s;
        scanf("%lld%lld",&x,&y);
        if(s[0]=='R') H[x]=(H[x]*y)%mod;
        else Z[x]=(Z[x]*y)%mod;
    }
    for(Reg int i=1;i<=m;++i)
    {
        dp[1]=(dp[1]+i*Z[i]%mod)%mod;
        sum=(sum+Z[i])%mod;
    }
    for(Reg int i=2;i<=n;++i)
        dp[i]=(dp[i-1]+m*sum%mod)%mod;
    for(Reg int i=1;i<=n;++i)
        ans=(ans+H[i]*dp[i]%mod)%mod;
    printf("%lld",ans);
    return 0;
}
View Code

 

B. 跳房子

正解:

看到$n$和$m$这么小,$k$这么大,这里面肯定有玄机。

所以就找循环节。。。

前$20$到$25$分可以暴力拿到。

后面的$50$到$60$分可以找循环节拿到。

正解用了分块的思想。

第一列跳$m$次又回到了第一列。

所以这样可以节省很多时间。

预处理出第一列的第$i$个点下一次到达的点$jump[i]$。

每次移动的时候只需先把棋子移动到第一列,然后再整块整块地移动。

这里也可以找循环节。

最后不满$m$步的要一步步地走。

修改的时候只需要第一列的$jump$即可。

咨询$Mr\_ zkt$大神,得到这么一个结论:到达一个点$(i,j)$的所有第一列的点一定是连续的。

稍微感性的理解一下。

假设现在有$1$个点,有$3$个点可以到达它,举个例子,

如果最上面的点和最下面的点都要走它的话,那么它在本列$5$个格子中是最大的,中间的格子肯定走它。

情况拓展到多个连续的格子。

所以我们找到要修改的点的左侧三个点。

对这三个点更改,找这三个点的上下界,然后各自更改成这个点到达的位置。

上下界要分开计算,最后会出现$2$中情况,

一种$max$在上,将$[1,min]$和$[max,n]$更改,

一种$max$在下,将$[min,max]$更改。

说起来简单,调代码。。。

丑陋的代码:

#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#define Maxn 2050
#define Reg register
#define INF 0x7ffffffffff
#define int long long
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
using namespace std;
bool vis[Maxn][Maxn];
int n,m,add,q,mx,mn,typ,jump[Maxn],A[Maxn][Maxn],stack[Maxn],vap[Maxn];
string s;
int get_next(int x,int y)
{
    int Nexy=(y+1)>m?1:(y+1);
    int Nex[3]={(x-1)<1?n:(x-1),x,(x+1)>n?1:(x+1)};
    int P[3]={A[Nex[0]][Nexy],A[Nex[1]][Nexy],A[Nex[2]][Nexy]};
    if(P[0]>P[1]&&P[0]>P[2]) return Nex[0];
    else if(P[1]>P[2]&&P[1]>P[0]) return Nex[1];
    else return Nex[2];
}
int dfs(int x,int y,int cnt)
{
    if(x==0) return 0;
    if(y==1&&cnt!=0) return x;
    return dfs(get_next(x,y),(y+1)>m?1:(y+1),cnt+1);
}
void move(int &x,int &y,int cnt)
{
    while(y!=1&&cnt>=1)
    {
        x=get_next(x,y),y=(y+1)>m?1:(y+1);
        --cnt;
    }
    while(cnt>=m)
    {
        stack[0]=0;
        int p=0;
        while(cnt>=m&&!vap[x])
        {
            vap[x]=p;
            stack[++stack[0]]=x;
            x=jump[x];
            cnt-=m; ++p;
        }
        if(cnt>=m)
        {
            cnt-=m,++p; x=jump[x];
            cnt%=(p*m-vap[x]*m);
        }
        for(Reg int i=1;i<=stack[0];++i) vap[stack[i]]=0;
        stack[0]=0;
    }
    while(cnt>=1)
    {
        x=get_next(x,y),y=(y+1)>m?1:(y+1);
        --cnt;
    }
    return;
}
bool get_pre(int x,int y,int opt)
{
    #define judge(x,y,z) (get_next(x,y)==z)
    if(y==1)
    {
        if(opt==1) mx=max(mx,x);
        else mn=min(mn,x);
        return 1;
    }
    int Prey=(y-1)<1?m:(y-1);
    int Pre[3]={(x-1)<1?n:(x-1),x,(x+1)>n?1:(x+1)};
    if(opt==1)
    {
        if(judge(Pre[2],Prey,x)) {if(get_pre(Pre[2],Prey,opt)) return 1;}
        if(judge(Pre[1],Prey,x)) {if(get_pre(Pre[1],Prey,opt)) return 1;}
        if(judge(Pre[0],Prey,x)) {if(get_pre(Pre[0],Prey,opt)) return 1;}
    }
    else
    {
        if(judge(Pre[0],Prey,x)) {if(get_pre(Pre[0],Prey,opt)) return 1;}
        if(judge(Pre[1],Prey,x)) {if(get_pre(Pre[1],Prey,opt)) return 1;}
        if(judge(Pre[2],Prey,x)) {if(get_pre(Pre[2],Prey,opt)) return 1;}
    }
    return 0;
}
void change(int x,int y,int z)
{
    int Prey=(y-1)<1?m:(y-1);
    int Pre[3]={(x-1)<1?n:(x-1),x,(x+1)>n?1:(x+1),};
    int O[3];
    for(Reg int i=0;i<3;++i) O[i]=dfs(Pre[i],Prey,0);
    A[x][y]=z;
    
    for(Reg int i=0,k;i<3;++i)
    {
        mx=-INF,mn=INF;
        k=dfs(Pre[i],Prey,0);
        if(k==O[i]) continue;
        get_pre(Pre[i],Prey,0);
        get_pre(Pre[i],Prey,1);
        if(mx==-INF||mn==INF) continue;
        if(mx<mn)
        {
            for(Reg int j=1;j<=mx;++j) jump[j]=k;
            for(Reg int j=mn;j<=n;++j) jump[j]=k;
        }
        else for(Reg int j=mn;j<=mx;++j) jump[j]=k;
    }
    return;
}
signed main()
{
    scanf("%lld%lld",&n,&m);
    for(Reg int i=1;i<=n;++i)
        for(Reg int j=1;j<=m;++j)
            scanf("%lld",&A[i][j]);
    for(Reg int i=1;i<=n;++i) jump[i]=dfs(i,1,0);
    scanf("%lld",&q);
    int sx=1,sy=1;
    memset(vap,0,sizeof(vap));
    for(Reg int i=1;i<=q;++i)
    {
        cin>>s;
        if(s=="move")
        {
            int k; scanf("%lld",&k);
            move(sx,sy,k);
            printf("%lld %lld\n",sx,sy);
        }
        else
        {
            int x,y,z; scanf("%lld%lld%lld",&x,&y,&z);
            change(x,y,z);
        }
    }
    return 0;
}
View Code

还有一种做法叫置换(蒟蒻不会)。

跑起来很快。

 

C. 优美序列

一开始想的莫队(为啥他们都先想到分治),

想到可以向左右拓展,所以就朝莫队的方向想了。

最后发现,其实莫队还不如直接暴力。

开$2$颗线段树,一个是区间内的编号的最值,一个是一段编号的位置的最值。

每个询问直接找最远的两个值,向左向右拓展。

最后得到$80$分$TLE$。

丑陋的代码:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#define int long long
#define Maxn 100050
#define Reg register
#define INF 0x7ffffff
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
using namespace std;
int n,m,ansm,ansn,maxx,minn,A[Maxn],pos[Maxn];
struct Tree {Tree *lch,*rch; int mx,mn;};
Tree *New()
{
    Tree *p=new Tree;
    p->lch=p->rch=NULL;
    p->mx=-INF,p->mn=INF;
    return p;
}
Tree *rot1=New(),*rot2=New();
void up(Tree *p)
{
    p->mx=-INF,p->mn=INF;
    if(p->lch!=NULL)
    {
        p->mx=max(p->lch->mx,p->mx);
        p->mn=min(p->lch->mn,p->mn);
    }
    if(p->rch!=NULL)
    {
        p->mx=max(p->rch->mx,p->mx);
        p->mn=min(p->rch->mn,p->mn);
    }
    return;
}
void build(Tree **p,int l,int r)
{
    if(*p==NULL) *p=New();
    if(l==r)
    {
        (*p)->mx=(*p)->mn=pos[l];
        return;
    }
    int mid=(l+r)>>1;
    if(l<=mid) build(&((*p)->lch),l,mid);
    if(mid+1<=r) build(&((*p)->rch),mid+1,r);
    up(*p);
    return;
}
void bup(Tree **p,int l,int r)
{
    if(*p==NULL) *p=New();
    if(l==r)
    {
        (*p)->mx=(*p)->mn=A[l];
        return;
    }
    int mid=(l+r)>>1;
    if(l<=mid) bup(&((*p)->lch),l,mid);
    if(mid+1<=r) bup(&((*p)->rch),mid+1,r);
    up(*p);
    return;
}
void ask(Tree *p,int l,int r,int sol,int sor)
{
    if(p==NULL||(p->mx<=ansm&&p->mn>=ansn)) return;
    if(sol<=l&&r<=sor)
    {
        ansm=max(ansm,p->mx);
        ansn=min(ansn,p->mn);
        return;
    }
    int mid=(l+r)>>1;
    if(sol<=mid) ask(p->lch,l,mid,sol,sor);
    if(mid+1<=sor) ask(p->rch,mid+1,r,sol,sor);
    return;
}
void query(Tree *p,int l,int r,int sol,int sor)
{
    if(p==NULL||(p->mx<=maxx&&p->mn>=minn)) return;
    if(sol<=l&&r<=sor)
    {
        maxx=max(maxx,p->mx);
        minn=min(minn,p->mn);
        return;
    }
    int mid=(l+r)>>1;
    if(sol<=mid) query(p->lch,l,mid,sol,sor);
    if(mid+1<=sor) query(p->rch,mid+1,r,sol,sor);
    return;
}
signed main()
{
//    freopen("text.in","r",stdin);
    scanf("%lld",&n);
    for(Reg int i=1;i<=n;++i)
    {
        scanf("%lld",&A[i]);
        pos[A[i]]=i;
    }
    build(&rot1,1,n); bup(&rot2,1,n);
    scanf("%lld",&m);
    for(Reg int i=1,l,r;i<=m;++i)
    {
        maxx=-INF,minn=INF;
        scanf("%lld%lld",&l,&r);
        query(rot2,1,n,l,r);
        int ln=l,rn=r;
        while(1)
        {
            if(maxx-minn==rn-ln||(ln==1&&rn==n))
            {
                printf("%lld %lld\n",ln,rn);
                break;
            }
            ansm=-INF,ansn=INF;
            ask(rot1,1,n,minn,maxx);
            ln=ansn,rn=ansm;
            query(rot2,1,n,ln,rn);
        }
    }
    return 0;
}
View Code

正解:

这个区间是优美序列的条件是$max-min=r-l$,

然后就是玄学的操作,

把每两个数值相差$1$的元素称作一个二元组。

那么一个区间就会有$r-l$个二元组。

枚举右端点,维护一个后缀和$val[i]$表示$i$节点到$r$节点的二元组个数。

每次向右拓展$r$,那么相应的$num[r]-1$和$num[r]+1$的二元组个数会增加$1$。

那么我们维护后缀和,

如果$num[r]-1$和$num[r]+1$在这个序列里,就把$1$到它们的位置上的数值加一。

最后如果$val[i]+l=r$,那么$i$到$r$的序列就是合法的。

还要维护一个区间最大值的位置。

 

对于每个询问,离线操作,对它们按照$l$从大到小排序,存入一个优先队列里。

每次更新答案的时候一直取$top$。

如果从$1$到$l$中的最大的$val$值为$i$,则更新答案,否则直接$break$掉。

正确性?

因为这个询问按照$l$从大到小的顺序排列,

如果区间$[1,r](1<=r<=n)$不能更新区间$[i,j]$,那么:

对于区间$[i',j'](i'<i;j'<j)$,也就是说在此之前它并没有被更新,

假设现在存在一个合法区间满足题目要求,对于之前的$r$,它并没有被更新。

那么这个合法区间的右端点$j''$一定大于等于$j$,它肯定可以更新右侧区间$[i,j]$。

也就是说有一个合法区间可以更新$[i,j]$,与假设肯定不符。

其他的可以类推。

丑陋的代码:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<vector>
#include<queue>
#define Maxn 100050
#define Reg register
#define INF 0x7ffffff
#define New() new Tree
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
using namespace std;
int n,m,mxx,maxx,pos[Maxn],num[Maxn];
struct Tree {Tree *ch[2]; int val,flag,pm;};
struct Node {int l,r,pos,al,ar;} Q[Maxn];
struct cmp
{
    bool operator () (int a,int b)
    {
        if(Q[a].l!=Q[b].l) return Q[a].l<Q[b].l;
        else return Q[a].r>Q[b].r;
    }
};
vector<vector<int> > p(Maxn);
priority_queue<int,vector<int>,cmp> stack;
Tree *root;
bool comp(Node a,Node b) {return a.pos<b.pos;}
void up(Tree *p)
{
    if(p==NULL) return;
    if(p->ch[0]==NULL&&p->ch[1]==NULL) return;
    if(p->ch[0]!=NULL&&p->ch[1]!=NULL)
    {
        if(p->ch[0]->val>p->ch[1]->val) p->pm=p->ch[0]->pm;
        else if(p->ch[0]->val<p->ch[1]->val) p->pm=p->ch[1]->pm;
        else p->pm=max(p->ch[0]->pm,p->ch[1]->pm);
        p->val=max(p->ch[0]->val,p->ch[1]->val);
    }
    else if(p->ch[0]!=NULL||p->ch[1]!=NULL)
    {
        p->val=p->ch[0]==NULL?p->ch[1]->val:p->ch[0]->val;
        p->pm=p->ch[0]==NULL?p->ch[1]->pm:p->ch[0]->pm;
    }
    return;
}
void down(Tree *p)
{
    if(p->ch[0]!=NULL)
    {
        p->ch[0]->val+=p->flag;
        p->ch[0]->flag+=p->flag;
    }
    if(p->ch[1]!=NULL)
    {
        p->ch[1]->val+=p->flag;
        p->ch[1]->flag+=p->flag;
    }
    p->flag=0; up(p);
    return;
}
void build(Tree **p,int l,int r)
{
    if(*p==NULL) *p=New();
    if(l==r)
    {
        (*p)->flag=(*p)->val=0;
        (*p)->pm=l;
        scanf("%d",&num[l]);
        pos[num[l]]=l;
        return;
    }
    int mid=(l+r)>>1;
    if(l<=mid) build(&((*p)->ch[0]),l,mid);
    if(mid+1<=r) build(&((*p)->ch[1]),mid+1,r);
    up(*p);
    return;
}
void add(Tree *p,int l,int r,int sol,int sor,int num)
{
    if(p==NULL) return;
    if(sol<=l&&r<=sor)
    {
        p->val+=num;
        p->flag+=num;
        return;
    }
    down(p);
    int mid=(l+r)>>1;
    if(sol<=mid) add(p->ch[0],l,mid,sol,sor,num);
    if(mid+1<=sor) add(p->ch[1],mid+1,r,sol,sor,num);
    up(p);
    return;
}
void serch(Tree *p,int l,int r,int sol,int sor)
{
    if(p==NULL||p->pm<=mxx) return;
    if(sol<=l&&r<=sor)
    {
        if(p->val>=maxx)
        {
            if(p->val==maxx) mxx=max(mxx,p->pm);
            else maxx=p->val,mxx=p->pm;
        }
        return;
    }
    down(p);
    int mid=(l+r)>>1;
    if(sol<=mid) serch(p->ch[0],l,mid,sol,sor);
    if(mid+1<=sor) serch(p->ch[1],mid+1,r,sol,sor);
    up(p);
    return;
}
int main()
{
    scanf("%d",&n);
    build(&root,1,n);
    scanf("%d",&m);
    for(Reg int i=1;i<=m;++i)
    {
        scanf("%d%d",&Q[i].l,&Q[i].r);
        p[Q[i].r].push_back(i);
        Q[i].pos=i;
    }
    for(Reg int i=1,len;i<=n;++i)
    {
        add(root,1,n,i,i,i);
        if(pos[num[i]-1]<=i) add(root,1,n,1,pos[num[i]-1],1);
        if(pos[num[i]+1]<=i) add(root,1,n,1,pos[num[i]+1],1);
        len=p[i].size();
        for(Reg int j=0;j<len;++j) stack.push(p[i][j]);
        while(!stack.empty())
        {
            int k=stack.top();
            mxx=maxx=-INF;
            serch(root,1,n,1,Q[k].l);
            if(maxx<i||mxx==-INF) break;
            Q[k].al=mxx,Q[k].ar=i;
            stack.pop();
        }
    }
    sort(Q+1,Q+m+1,comp);
    for(Reg int i=1;i<=m;++i)
        printf("%d %d\n",Q[i].al,Q[i].ar);
    return 0;
}
View Code

 

总结:

$T1$乍一看没什么思路,

刚开始一直在想怎么用线段树。

不管怎样都是$O(nm)$的复杂度。

然后就弃掉了。

最后码完$T3$有回来看$T1$,想了一会儿。

式子写下来,发现其实很水。逐行转移就可以了。

开始看$T2$,也没什么思路。

码了一个暴力(最后还打错了),先拿到这暴力分再说。

$T3$好像做过的样子,想到那个莫队专题里的$permu$,心态就炸了。

因为我没做那个题。

静下心来好好想,结果码了$2$颗线段树,然后水到$80$分。

最后回去看$T1$和$T2$,$T1$打完确实心态就稳了。

然后$T2$还是炸了,暴力分都没拿到。

所以最后$100+5+80=185$。

没什么水平。。。

posted @ 2019-08-05 11:53  Milk_Feng  阅读(159)  评论(1编辑  收藏  举报