8.10 NOIP模拟测试16 Blue+Weed+Drink

T1 Blue

贪心,每次跳得时候跳能跳到的最远的地方,跳过的就把他设为0,每次二分找到位置,一直跳就行,如果能跳到的位置就是当前位置或比当前位置还小(数组里现在呆着的这一块石头,二分得到的就是当前位置,-1就比当前位置小了。但由于0的影响,while回退很慢,所以改用支持earse操作的set,可以水过他(这是T40和AC的区别!!!)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<set>
#include<algorithm>
using namespace std;
int T,n,m,d,l;
set<int>st;
set<int>::iterator it;
int read()
{
    int aa=0,bb=1;char cc=getchar();
    while(cc>'9'||cc<'0'){if(cc=='-') bb=-1;cc=getchar();}
    while(cc<='9'&&cc>='0'){aa=aa*10+cc-'0';cc=getchar();}
    return aa*bb;
}
int main()
{
    T=read();
    while(T--){
        st.clear();
        n=read();m=read();d=read(),l=read();
        bool flag=0,flag1=0;
        for(int i=1;i<=n;i++){
            st.insert(read());
        }
        st.insert(l);st.insert(0);
        if(d==l){
            puts("Excited");
            continue;
        }
        for(int i=1;i<=m;i++){
            int pos=0;
            while(pos<l){
                if(pos+d>=l) break;
                it=st.upper_bound(pos+d);
                if(*(--it)<=pos){
                    flag=1;
                    break;
                }
                pos=*it;
                st.erase(it);
            }
            if(flag){
                printf("%d\n",i-1);
                flag1=1;
                break;
            }
        }
        if(flag1) continue;
        puts("Excited");
    }
    return 0;
}
blue

T2 Weed

树袋熊学长的课件里讲过,然而我并不会。

又是一道神奇的线段树,用一个神奇的cal函数,我们就可以愉快的拿到100分的好成绩!

以时间为下标,建一棵线段树,树中维护4个值sum,del,cnt,las

sum:当前区间内所有操作添 删之后剩下金克拉的总和  ans=t[1].sum

del:当前区间还要向前(当前区间的前一个区间)删除多少层

cnt:当前区间被自己区间删完后还剩多少层

las:左儿子被右儿子用del删除后还剩多少(只对左儿子维护)

cal函数的作用就是计算las的值,update的时候维护x信息时,左儿子会被右儿子删除,为了维护区间信息的正确性,需要用右儿子的del删除左儿子的一些值,删除等操作我们只是保存在区间信息里,而不对下面的节点做修改(因为这是区间信息)。左儿子的cnt和右儿子的del有下面几种关系,分别处理即可

1.l.cnt<r.del 左儿子不够删,那就直接当作左儿子不存在,计算剩下的值得到x的sum值

2.r.del==0 右儿子中不存在删除操作,直接合并

3.l.r.cnt>r.del 左儿子的右儿子就够右儿子删了,return l.las+cal(l.r,r.del)

4.l.r.cnt<r.del 左儿子的右儿子不够右儿子不够删,那就继续去左儿子的左儿子里删 return cal(l.l,r.del-l.r.cnt+l.r.del) 记得加上左儿子的右儿子对左儿子的左儿子的del

5.l.r.cnt==r.del 左儿子的右儿子刚好够删,直接return l.las

修改操作实际上就是线段树的单点修改操作,改动一个值,其他区间的信息会被update上去,最后t[1].sum即为答案。

/*sum:区间内删完后的总和
del:当前区间要往前删多少个
cnt:当前区间删完后还有多少个加的操作
las:左儿子被右儿子删完后的总和*/
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
struct node
{
    int l,r,sum,del,cnt,las;
}t[800100];
int n,m,opt,v;
int read()
{
    int aa=0,bb=1;char cc=getchar();
    while(cc>'9'||cc<'0'){if(cc=='-') bb=-1;cc=getchar();}
    while(cc<='9'&&cc>='0'){aa=aa*10+cc-'0';cc=getchar();}
    return aa*bb;
}
int cal(int x,int num)
{
    if(t[x*2+1].cnt>num) return t[x*2].las+cal(x*2+1,num);
    if(t[x*2+1].cnt<num) return cal(x*2,num-t[x*2+1].cnt+t[x*2+1].del);
    if(t[x*2+1].cnt==num) return t[x*2].las;
}
void update(int x)
{
    if(!t[x*2+1].del){
        t[x].sum=t[x*2].sum+t[x*2+1].sum;
        t[x].del=t[x*2].del;
        t[x].cnt=t[x*2].cnt+t[x*2+1].cnt;
        t[x*2].las=t[x*2].sum;
        return;
    }
    if(t[x*2].cnt<=t[x*2+1].del){
        t[x].sum=t[x*2+1].sum;
        t[x].del=t[x*2].del+t[x*2+1].del-t[x*2].cnt;
        t[x].cnt=t[x*2+1].cnt;
        t[x*2].las=0;
        return;
    }
    t[x*2].las=cal(x*2,t[x*2+1].del);
    t[x].sum=t[x*2].las+t[x*2+1].sum;
    t[x].del=t[x*2].del;
    t[x].cnt=t[x*2+1].cnt+t[x*2].cnt-t[x*2+1].del;
}
void build(int x,int l,int r)
{
    t[x].l=l;t[x].r=r;
    if(l==r){
        opt=read();
        if(opt==0){
            t[x].sum=read();
            t[x].del=0;
            t[x].cnt=1;
            t[x].las=0;
        }
        else{
            t[x].sum=0;
            t[x].del=read();
            t[x].cnt=0;
            t[x].las=0;
        }
        return;
    }
    int mid=(l+r)>>1;
    build(x*2,l,mid);
    build(x*2+1,mid+1,r);
    update(x);
}
void change(int x,int pos)
{
    if(t[x].l==t[x].r&&t[x].l==pos){
        if(opt==0){
            t[x].sum=v;
            t[x].del=0;
            t[x].cnt=1;
            t[x].las=0;
        }
        else{
            t[x].sum=0;
            t[x].del=v;
            t[x].cnt=0;
            t[x].las=0;
        }
        return;
    }
    int mid=(t[x].l+t[x].r)>>1;
    if(pos<=mid) change(x*2,pos);
    else change(x*2+1,pos);
    update(x);
}
int main()
{
    n=read();m=read();
    build(1,1,n);
    for(int i=1,j;i<=m;i++){
        j=read();opt=read();v=read();
        change(1,j);
        printf("%d\n",t[1].sum);
    }
    return 0;
}
Weed

T3 Drink

一圈一圈的先存下来,然后转后应在的地方“铺”上去,用队列存T30,用数组存并且用char类型存储表格(只有1~9)可以到T60,调整一下循环顺序T80,register删掉,for改while可以卡到T90,最后把快读稍改,就A了

就一句话,疯狂卡常可以A。

然而正解并不长这样。。。具体长啥样我也不知道

以下是正解:

Drink:

看惯了罗马音的小朋友们都会知道rl的音,题目名:D Link.

每次修改都会改变O( N ^ 2 )位置的值,二维平面上没有一个确定的正方向,因此也无法使用传统数据结构。

什么东西的变化量是O( N )级别的呢?

如果把每个点都看作一个人,他的头面向某个方向分别记录这个人的方向上的前后左右分别是谁,那么每次旋转改变的只是正方形边缘的值,以及所有点的方向。

这样来看至少我们发现了变化量为O( N )级别的东西啦虽然方向的该变量还是O( N ^ 2 )

注意到,我们并不需要真的知道每个点的方向,我们只需要正确维护前后左右四个值即可因为每个点的方向是可以由已知方向的相邻点算出来的!

每个点确实是有方向的但是我们不用纪录也不用直接更改它,每次只要正确修改边界上的值,整个矩形的方向就会自动改变。

解决啦!

复杂度O( Q * N ), 常数稍大。

#include<iostream>
#include<cstdio>
const int L=1<<20|1;
char buffer[L],*S,*T;
#define getchar() ((S==T&&(T=(S=buffer)+fread(buffer,1,L,stdin),S==T))?EOF:*S++)
using namespace std;
char a[2010][2010],c,b[8010];
int read()
{
    int aa=0,bb=1;char cc=getchar();
    while(cc>'9'||cc<'0'){if(cc=='-') bb=-1;cc=getchar();}
    while(cc<='9'&&cc>='0'){aa=(aa<<3)+(aa<<1)+(cc^48);cc=getchar();}
    return aa*bb;
}
int main()
{
    int n,m,qq,num;
    n=read(),m=read(),qq=read();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            c=getchar();
            while(c<'0'||c>'9')  c=getchar();
            a[i][j]=c;
        }
    int x,y,l,i=1,j;
    while(i<=qq){
        x=read();y=read();l=read();
        while(l>=2){
            num=0;
            j=0;while(j<l) b[++num]=a[x][y+j],++j;
            j=1;while(j<l) b[++num]=a[x+j][y+l-1],++j;
            j=l-2;while(j>=0) b[++num]=a[x+l-1][y+j],--j;
            j=l-2;while(j>=1) b[++num]=a[x+j][y],--j;
            
            j=l-2;while(j>=1) a[x][y+j]=b[num--],--j;
            j=0;while(j<=l-2) a[x+j][y]=b[num--],++j;
            j=0;while(j<=l-2) a[x+l-1][y+j]=b[num--],++j;
            j=l-1;while(j>=0) a[x+j][y+l-1]=b[num--],--j;
            x++,y++;l-=2;
        }
        i++;
    }
    i=1;
    while(i<=n){
        j=1;
        while(j<=m){
            putchar(a[i][j]),putchar(' ');
            j++;
        }
        puts("");
        i++;
    }
    return 0;
}
Drink(暴力+卡常)

总之这道题的测试点还是很水的,T1T3暴力都可以水过,但样例给的太灭绝人性,把电脑卡崩3 4次。

猜你喜欢

转载自www.cnblogs.com/jrf123/p/11333106.html
今日推荐