2017/7/15 离线赛

来源:互联网 发布:java程序开发步骤 编辑:程序博客网 时间:2024/05/01 13:32

第一题 NOIP2016普及组复赛T3 海港

      题目很长,但是意思很明确。因为船到达的时间是递增的,所以直接按数据顺序模拟过来就好。我的做法是,按1~n的顺序枚举过来,把一艘艘船塞进队列,再把队列前面时间太早的船删去,即对于第i艘船,我们先把队列里时间小于等于ti86400的船删去,再把第i艘船塞进队列。剩下的问题就是如何存储数据了,先看一下数据范围:1n105ki3105,题目并没有告诉我们ki的范围,我们姑且把他当做3105,倘若我们要存下n艘船的信息,就要开个nki的数组,这显然超出了内存限制。所以我想到题目既然只给了ki,那我不妨就开一个ki大小的数组,把所有船的数据都存在同一个数组里。

struct node{    int t,k,L,R;}s[M];int n;int cnt[M],A[3*M];int main(){    Rd(n);    int tmp=0;    for(int i=1;i<=n;i++){        Rd(s[i].t);Rd(s[i].k);        s[i].L=tmp+1;s[i].R=tmp+s[i].k;        for(int j=s[i].L;j<=s[i].R;j++)Rd(A[j]);        tmp=s[i].R;    }    int res=0;    queue<node>que;    for(int i=1;i<=n;i++){        while(!que.empty()&&que.front().t<=s[i].t-86400){            node q=que.front();que.pop();            for(int j=q.L;j<=q.R;j++){                if(cnt[A[j]]==1)res--;                cnt[A[j]]--;            }        }        for(int j=s[i].L;j<=s[i].R;j++){            if(!cnt[A[j]])res++;            cnt[A[j]]++;        }        que.push(s[i]);        Pt(res);        putchar('\n');    }    return 0;}

第二题 NOIP2016普及组复赛T4 魔法阵

    刚看完这道题的时候,我想到其实本题只有A和差值k两个变量,因为知道这两个量其他几个数都可以确定了,但是后来发现CD的差值是不定,于是就没往下去想,先写了个O(n2logn2)的代码,只拿了85分(TLE了),具体做法是:先预处理出所有差值,即枚举两个数作差,再按照从小到大的顺序将各个差值组合排好,这样要用到O(n2logn2),因为要用到priority_queue来保证从小到大的顺序,剩下的就是先枚举A和B,求出满足条件的差值组合的后缀和(因为差值组合已经从小到大排序, 所以只要求B+3*(B-A)+1的后缀和就行了,这里我用了BIT,所以是O(n2logn)的复杂度),再枚举C和D,求出满足条件的差值组合的前缀和,因此整个算法复杂度是O(n2logn2),理论上是可以拿90分的,但是由于一些常数的问题没有卡过去。

struct node{    int x,y;    bool operator <(const node &tmp)const{        return x>tmp.x;    }};int BIT[2][N][N];priority_queue<node>que[N];int n,m;int A[M];int tmp[M];//有哪些数字,最多1~15000int tt[N];int cnt[N][4];//数字i在各个位置的次数void add(int k,int d,int x,int a){    int i=x;    while(i<=n){        BIT[k][d][i]+=a;        i+=(i&-i);    }}int sum(int k,int d,int x){    int i=x,res=0;    while(i>0){        res+=BIT[k][d][i];        i-=(i&-i);    }    return res;}int main(){    Rd(n);Rd(m);    for(int i=1;i<=m;i++){        Rd(A[i]);        tt[A[i]]++;        tmp[i]=A[i];    }    sort(tmp+1,tmp+m+1);    int k=unique(tmp+1,tmp+m+1)-tmp;//1~k-1;    for(int i=1;i<k;i++)    for(int j=i+1;j<k;j++)    que[tmp[j]-tmp[i]].push((node){tmp[i],tmp[j]});    for(int i=1;i<=n;i++)    while(!que[i].empty()){        node q=que[i].top();que[i].pop();        int x=q.x,y=q.y;        add(0,i,n-x+1,tt[x]*tt[y]);        add(1,i,y,tt[x]*tt[y]);    }    for(int i=1;i<k;i++)    for(int j=i+1;j<k;j++){        int t=tmp[j]-tmp[i];        if(t%2)continue;        int ttt=sum(0,t>>1,n-(tmp[j]+3*t+1)+1);        cnt[tmp[i]][0]+=ttt*tt[tmp[j]];        cnt[tmp[j]][1]+=ttt*tt[tmp[i]];    }    for(int i=1;i<k;i++)    for(int j=1;j<k;j++){        int t=tmp[j]-tmp[i];        if((t<<1)>=N)continue;        int ttt=sum(1,t<<1,tmp[i]-3*(t<<1)-1);        cnt[tmp[i]][2]+=ttt*tt[tmp[j]];        cnt[tmp[j]][3]+=ttt*tt[tmp[i]];    }    for(int i=1;i<=m;i++){        Pt(cnt[A[i]][0]);        for(int j=1;j<4;j++){            putchar(' ');            Pt(cnt[A[i]][j]);        }        putchar('\n');    }    return 0;}

    下面来说一下O(n29)的做法:跟我最初的想法一样,枚举Akk即为D-C),那么我们就能知道B=A+2k,CD即为C从A+8k+1开始以后所有差值为k的组合,我们只要处理出从i开始差值为k的组合数就行了,这样可以求出AB的次数,再枚举Dk,求出从D9k1往前所有差值为2k的组合。

int n,m;int A[M],cnt[N],res[N][4];int main(){    Rd(n);Rd(m);    for(int i=1;i<=m;i++){        Rd(A[i]);        cnt[A[i]]++;    }    for(int k=1;k<=n;k++){        int sum=0;        for(int i=n-9*k-1;i>=1;i--){            sum+=cnt[i+(8*k+1)]*cnt[i+(9*k+1)];            res[i][0]+=sum*cnt[i+2*k];            res[i+2*k][1]+=sum*cnt[i];        }        sum=0;        for(int i=2+9*k;i<=n;i++){            sum+=cnt[i-(9*k+1)]*cnt[i-(7*k+1)];            res[i-k][2]+=sum*cnt[i];            res[i][3]+=sum*cnt[i-k];        }    }    for(int i=1;i<=m;i++){        Pt(res[A[i]][0]);        for(int j=1;j<4;j++){            putchar(' ');            Pt(res[A[i]][j]);        }        putchar('\n');    }    return 0;}

第三题 AHOI2005 航线规划

    我一开始先敲了个暴力,就是枚举每条边,再跑一次dfs,这样只能拿30分。假如没有删边操作的话,就可以敲个Tarjan了,求出桥边,再预处理出每个点到根的路径中有多少条桥边就OK了。既然有删边操作,我们就要思考思考了,倘若我们顺着题目的意思将边一条一条删去,那么有可能就删到之前建立树边了,这样我们又要重新建一棵树,假如每次删的边都是树边,那么复杂度就是O(nm)了。那么正着不行,我们就反着搞,先把所有删的边都删掉,然后再一个一个加回来,这样就能保证最初的树边都不会是删掉的边了,然后我们用BIT存下x到根节点的路径中有多少条桥边就OK了。具体做法是:先把所有树边都当成桥边,再遍历非树边,这样可以找到一个环,环上的树边肯定不是桥边,于是在BIT里删去这些边。

struct edge{    int v,nxt;}e[M<<1];int n,m,q,cnt,tim;int head[N],dep[N],sz[N],pa[N],fa[N][Max],dfn[N];int op[Q],A[Q],B[Q],BIT[N],ans[Q];set<int>no[N];void add_edge(int u,int v){    e[++cnt].v=v;e[cnt].nxt=head[u];head[u]=cnt;}void Init(){    for(int k=0;k<Max;k++)    for(int i=1;i<=n;i++)    if(~fa[i][k])fa[i][k+1]=fa[fa[i][k]][k];}void add(int x,int a){    int i=x;    while(i<=n){        BIT[i]+=a;        i+=i&-i;    }}int sum(int x){    int res=0,i=x;    while(i){        res+=BIT[i];        i-=i&-i;    }    return res;}int getfa(int v){    if(pa[v]==v)return v;    return pa[v]=getfa(pa[v]);}void merge(int x,int y){    x=getfa(x);    y=getfa(y);    while(x!=y){        if(dep[x]<dep[y])swap(x,y);        add(dfn[x],-1);        add(dfn[x]+sz[x],1);        pa[x]=fa[x][0];        x=getfa(x);    }}void dfs(int x,int t){    dfn[x]=++tim;    sz[x]=1;    if(~t)dep[x]=dep[t]+1;    else dep[x]=0;    fa[x][0]=t;    for(int i=head[x];~i;i=e[i].nxt){        int v=e[i].v;        if(dfn[v]||no[x].count(v))continue;        dfs(v,x);        sz[x]+=sz[v];    }    add(dfn[x],1);    add(dfn[x]+sz[x],-1);}void dfs1(int x,int t){    for(int i=head[x];~i;i=e[i].nxt){        int v=e[i].v;        if(v==t||no[x].count(v))continue;        if(fa[v][0]==x)dfs1(v,x);        else merge(x,v);    }}int LCA(int u,int v){    if(dep[u]<dep[v])swap(u,v);    int c=dep[u]-dep[v];    for(int k=0;k<Max;k++)    if(c&(1<<k))u=fa[u][k];    if(u==v)return u;    for(int k=Max-1;k>=0;k--)    if(fa[u][k]!=fa[v][k]){        u=fa[u][k];        v=fa[v][k];    }    return fa[u][0];}int main(){    memset(fa,-1,sizeof(fa));    memset(head,-1,sizeof(head));    int a,b;    Rd(n);Rd(m);Rd(q);    for(int i=1;i<=m;i++){        Rd(a);Rd(b);        add_edge(a,b);        add_edge(b,a);    }    for(int i=1;i<=q;i++){        Rd(op[i]);Rd(A[i]);Rd(B[i]);        if(!op[i]){            no[A[i]].insert(B[i]);            no[B[i]].insert(A[i]);        }    }    for(int i=1;i<=n;i++)pa[i]=i;    dfs(1,-1);    Init();    dfs1(1,-1);    for(int i=q;i>=1;i--){        int a=A[i],b=B[i];        if(op[i]){            int lca=LCA(a,b);            ans[i]=sum(dfn[a])+sum(dfn[b])-2*sum(dfn[lca]);        }else merge(a,b);    }    for(int i=1;i<=q;i++)    if(op[i])Pt(ans[i]),putchar('\n');    return 0;}

总之呢,还需努力,还有好多算法没学呢。_(:зゝ∠)_