20190803 NOIP模拟测试12「斐波那契(fibonacci)· 数颜色 · 分组 」

164分 rank11/64

  这次考的不算太差,但是并没有多大的可能性反超(只比一小部分人高十几分而已),时间分配还是不均,T2两个半小时,T1半个小时,T3~额十几分钟吧

  然额付出总是与回报成反比的,T1切了,T2超时60分,T3拿到了4分的好成绩,但某位不愿透露姓名的王鹤松说,他花了将近俩小时在T1上,20分钟T2,结果T1没有切掉,T2  比我高十分(QWQ)

  这场考试下来发现自己还是有点儿紧张,中间去了好几次WC

  对于这场考试,遗憾的是时间分配不均,没有拿到足够高的分数来给自己上保险,不过稍稍宽慰而又难受的是T2我想到了正解,就是没有排序,结果TLE 60,否则我会切掉他,拿到最好的一次成绩

T1 斐波那契 没啥可说的,一遍过

#include<iostream>
#include<cstdio>
#include<bits/stdc++.h>
using namespace std;
int n;
long long f[11000];
long long search(long long x){
    int l=1,r=60,mid,ans;
    while(l<r){
        mid=(l+r)>>1;
        if(f[mid]>=x) r=mid;
        else l=mid+1;
    }
    return x-f[r-1];
}
long long lca(long long x,long long y){
    while(x!=y){
        while(x<y) y=search(y);
        while(x>y) x=search(x);
    }
    return x;
}
int main(){
    scanf("%d",&n);
    f[1]=f[2]=f[3]=1;
    for(int i=4;;i++){
        f[i]=f[i-1]+f[i-2];
        if(f[i]>=(long long)1e12) break;
    }
    long long x,y;
    while(n--){
        scanf("%lld%lld",&x,&y);
        long long w=lca(x,y);
        printf("%lld
",w);
    }
}
View Code

T2 数颜色

  线段树套数状数组,分块,带修莫队,树套树……都可以拿到分甚至AC

  我是想到了另一个正解,就是维护颜色的前缀和,比如 i 点颜色为 j ,那么一个数组记录 f[i]=tmp[j]  ,tmp是一个桶记录到目前为止j的个数,然后查询时只需要找到 l~r 区间的后一个颜色为j的 点 ,以及从 l 往前的第一个颜色为j的点,二者f[]差值即为该区间的颜色为j的个数,而修改时只需要把i和i+1 颜色互换,且如果颜色不一样,交换f[]值(因为如果颜色一样,f[i],f[i+1]不变)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int n,m,a[310000],tmp[310000],f[310000];
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        tmp[a[i]]++;
        f[i]=tmp[a[i]];
    }
    int x,l,r,c;
    while(m--){
        scanf("%d",&x);
        if(x==1){
            scanf("%d%d%d",&l,&r,&c);
            while(r>=l&&a[r]!=c) r--;
            if(r<l) printf("0
");
            else{    
                l--;
                while(l&&a[l]!=c) l--;
                printf("%d
",f[r]-f[l]); 
            } 
        }
        else{
            scanf("%d",&l);
            r=l+1;
            swap(a[l],a[r]);
            if(a[l]!=a[r]){
                swap(f[l],f[r]);
            }
            
        }
    }
}
TLE 60

  而优化的算法就是将 数组以颜色为第一要素,点的位置为第二要素二元组排序,在待查询的颜色所在的区间二分查找位置在l~r之间的点,这种点的个数就是答案

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
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++)
int n,m,to[310000],s[310000],t[310000];
struct node{
    int a,id;
}f[310000];
inline int read(){
    register int ret;
    register char r;
    while(r=getchar(),r<'0'||r>'9');ret=r-48;
    while(r=getchar(),r>='0'&&r<='9')ret=ret*10+r-48;
    return ret;
}
inline int cmp(node x,node y){
    return (x.a==y.a)?x.id<y.id:x.a<y.a;
}
inline int search(int L,int R,int c){
    int l=s[c],r=t[c],mid,ansl,ansr;
    if(f[l].id>R||f[r].id<L) return 0;
    while(l<=r){
        mid=(l+r)/2;
        if(f[mid].id>=L) r=mid-1,ansl=mid;
        else l=mid+1;
    }
    l=s[c],r=t[c];
    while(l<=r){
        mid=(l+r)/2;
        if(f[mid].id<=R) l=mid+1,ansr=mid;
        else r=mid-1; 
    }
    if(ansl>ansr) return 0;
    return ansr-ansl+1;
}
int main(){
    n=read(),m=read();
    for(register int i=1;i<=n;i++){
        f[i].a=read();
        f[i].id=i;
    }
    sort(f+1,f+n+1,cmp);
    for(register int i=1;i<=n;i++){
        if(f[i].a!=f[i-1].a){
            t[f[i-1].a]=i-1;
            s[f[i].a]=i;
        }
        to[f[i].id]=i;
    }
    t[f[n].a]=n;
    int x,l,r,c;
    while(m--){
        x=read();
        if(x==1){
            l=read(),r=read(),c=read();
            printf("%d
",search(l,r,c)); 
        }
        else{
            l=read();
            r=l+1;
            if(f[to[l]].a!=f[to[r]].a){
                f[to[l]].id=r;
                f[to[r]].id=l;
                swap(to[l],to[r]);
            }
        }
    }
}
1000多ms

T3 分组

  因为要求出组数最少字典序最小的,所以倒着贪心求,能苟在这个小组了就苟着,实在不能苟了,再和他们分开另建一小组

  对于k=1,直接暴力枚举位置大于该点的点的颜色与该点颜色相加判断是否是平方数,来判断该兔子能否加入当前的小组,不能的话就在以这个兔子为最右的端点再开一个小组,  直到到头

  当然,这样做必定TLE,换一种判断方法,a[]最大131072,两个a[]相加最大值为262144,是512的平方,可以枚举1~512的平方,看x^2-a[i] 有没有在之前出现过,出现过则证  明会有矛盾,如此40分到手  

  对于k=2,只要把这个小组分成两个集合,且同一集合中没有互相矛盾的兔子就成立,用到了二分图的判定(交叉染色法),倒着枚举,然后把该点和与他矛盾的点连边,走一遍  DFS染色判断是否是二分图,不是的话把它当作新小组的右端点并记录,继续走

  PS:不要忘记清空颜色

  PSS: 判断时,图不一定联通,所以一开始我是for循环看那一个点没颜色,就从该点进入再跑一遍,结果TLE,后来我把for删了,直接从i点进入走一遍判断

  证明: 在走i点之前,设x点(位置在i右侧,已走过)所联通的那一块与另一块没有相连,而既然已经走到了 i,这就意味着x点所联通的与主体部分不联通的那一块是二分图,

  所以不需要在for 重新跑一遍,到i点时,唯一不确定是否是二分图的只有与i相连的那一块

#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
using namespace std;
int n,K,a[140000],st[140000],v[280000],ms[280000],color[280000];
vector<int>son[140000];
int dfs(int x,int r,int l,int pre){
    for(int i=0;i<son[x].size();i++){
        int y=son[x][i];
        if(y==pre||y<l||y>r) continue;
        if(!color[y]){
            color[y]=color[x]^1;
            if(!dfs(y,r,l,x)) return 0;
        }
        else if(color[y]==color[x]) return 0;
    }
    return 1;
}
int judge(int r,int l){
    color[l]=2;
    if(!dfs(l,r,l,0)) return 0;
    return 1;
}
int main(){
    scanf("%d%d",&n,&K);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    if(K==1){
        int la=n;
        for(int i=1;i<=512;i++)    ms[i]=i*i;
        for(int i=n;i;i--){
            int flag=0;
            for(int j=1;j<=512;j++){
                if(ms[j]<a[i]||!v[ms[j]-a[i]]) continue;
                flag=1;
                break;
            }
            if(flag==1){
                for(int j=la;j>i;j--) v[a[j]]=0;
                st[++st[0]]=i,la=i;
            }
            v[a[i]]=1;
        }
        printf("%d
",st[0]+1);
        for(int i=st[0];i;i--) printf("%d ",st[i]);
        cout<<endl;
    }
    else{
        int la=n;
        for(int i=1;i<=512;i++) ms[i*i]=1;
        for(int i=n;i;i--){
            for(int j=la;j>i;j--){
                if(!ms[a[i]+a[j]]) continue;
                son[i].push_back(j);
                son[j].push_back(i);
            }
            if(!judge(la,i)){
                st[++st[0]]=i;
                la=i;
                son[i].clear();
            }
            for(int j=la;j>=i;j--) color[j]=0;
        }
        printf("%d
",st[0]+1);
        for(int i=st[0];i;i--) printf("%d ",st[i]);
        cout<<endl;
    }
}
View Code
原文地址:https://www.cnblogs.com/heoitys/p/11296128.html