10.12离线赛

来源:互联网 发布:淘宝二手镜头店铺推荐 编辑:程序博客网 时间:2024/06/04 18:36

一、神经网络
数据:对于50%的数据,n∈[1,100]
对于100%的数据,n∈[1,200]

很简单的一道题,但是只看题目会有点误解。题目中说神经若处于兴奋状态,下一秒向后发消息。我理解成这个要按照时间来,用完后就有成平静态。这样的话U[i]就多减了,而且有问题,只有20分。
实际上是一个点的Ci要把所有他的输入的Cj全部算出来才行。

这样就有两种方法:
1、按照拓扑序,一层一层算过来
2、反向,从输出层往前,用一下记忆化搜索。

我写的是记忆化搜索。这个比较简单,只需反向存一下边就行了。

二、最大字典序排列
数据:对于60%的数据,n∈[1,2000]
对于80%的数据,n∈[1,20000]
对于100%的数据,n∈[1,100000]

不看题就猜这是到dp题,这是惯例。然后猜错了。幸好没仔细想。

正解是贪心,每次从后面选出一个数值最大并且可以交换到这个位子的数。直接实现的话类似于冒泡排序,一位一位交换上来,这样是n^2。但这道题直接这样算不对,这样写有80分。

然后就要写N log N 的了。log N 的不多,这里用的是线段树。
线段树里装的是在[L,R]这个区间里的最大值和这个区间里还有几个数没被用过(即被交换到前面)。不用记录最大值的位置吗?因为是1-N的排列,只要起初记录一下,然后再删除就行了。

Code:

#include<bits/stdc++.h>using namespace std;int A[100005],G[100005],Q[100005];//原数组,每个数的位置,哪些数已输出struct node{int L,R,mx,cnt;}tree[400005];//左右,最大值,有多少个数 void Up(int p){    tree[p].mx=max(tree[p*2].mx,tree[p*2+1].mx);    tree[p].cnt=tree[p*2].cnt+tree[p*2+1].cnt;}void Build(int L,int R,int p){    tree[p].L=L;tree[p].R=R;    if(L==R){        tree[p].mx=A[L];        tree[p].cnt=1;        return;    }    int mid=(L+R)/2;    Build(L,mid,p*2);Build(mid+1,R,p*2+1);    Up(p);}int Delete(int x,int p){//一边删除,一边计算移动步数     if(tree[p].L==tree[p].R){        tree[p].mx=tree[p].cnt=0;        return 0;    }    int mid=(tree[p].L+tree[p].R)/2;    int del;    if(mid>=x)del=Delete(x,p*2);    else del=Delete(x,p*2+1)+tree[p*2].cnt;    Up(p);    return del;}int Query(int K,int p){    if(K<0)return 0;    if(tree[p].cnt<=K)return tree[p].mx;//大区间符合,直接返回    int Lr=Query(K,p*2);//先左边,移动的步数少,    if(tree[p*2].cnt<K)return max(Lr,Query(K-tree[p*2].cnt,p*2+1));//行的话右边,K要减一下是因为右边的也要交换经过左边    return Lr;} int main(){    int n,k;    scanf("%d%d",&n,&k);    for(int i=1;i<=n;i++){        scanf("%d",&A[i]);        G[A[i]]=i;Q[i]=1;//记录位置,初始状态    }    Build(1,n,1);    for(int i=1;i<=n;i++){        if(k<=0)break;        int res=Query(k+1,1);//单点查询        printf("%d\n",res);        Q[G[res]]=0;        int del=Delete(G[res],1);        k-=del;//减去res的交换步数    }    for(int i=1;i<=n;i++)        if(Q[i])printf("%d\n",A[i]);//最后没输出的输出    return 0;}

三、航线规划
数据:对于 30%的数据,n 的范围[2,100],m 的范围[1,500],Q 的范围[1,100]
对于 60%的数据,n 的范围[2,10000],m 的范围[1,30000],Q 的范围[1,20000],数据中没有删边操作
对于 100%的数据,n 的范围[2,30000],m 的范围[1,100000],Q 的范围[1,40000],数据保证任何时候图都是连通,且没有重边和自环

又一道图论神题。兼用前几次考试最后一题的部分思想。
理一下知识点:
1、线段树(可以用BIT代替)
2、路径压缩(并查集实现)
3、LCA(倍增)
4、dfs序(造树)
5、求两点之间的路径上的值

这道题与前面的不一样的地方是,建树时随便建的,反正没用的边迟早会被标记掉,关键路径永远不会被标记掉,而且肯定在所造的树上(否则不用这条边就没办法连通了,反证法,想想很简单的)。然后是题目要删边,干脆先把边删完,最后反着一条一条加回去,这样就一样了。

卡了很久,错了三个地方:
1、LCA手贱把 step=dep[y]-dep[x] 写成 step=dep[x]-dep[y] ,这样step就是负数了,不知道哪里来的60分(全部询问输出0就有60分,这数据……)
2、合并路径时,x和y要先指向自己的父亲节点,否则会重复更新。之前那道题没有加,是因为那个更新不会造成影响,而这个不一样。
3、数组开小了,按照数据一直90,后来把数组扩大5倍对了,然后仔细想发现山区的边可能会重复问,这样就要开两倍,两倍也是对的。(之前开了个1.5倍的,错了)

Code(小长):

#include<bits/stdc++.h>#define M 100005using namespace std;vector<int>edge[M];set<int>edge1[M];int n,m,len,cas;int fa[20][M],fa2[M],ans[M],dep[M],op[M],Q[M];struct node1{int x,y;}G[M*2],G1[M*2];int Lt[M],Rt[M],dfsline[M],T;void f(int x,int fa1){//随机把图变成一棵树    Q[x]=1;    Lt[x]=++T;dfsline[T]=x;    fa[0][x]=fa1;dep[x]=dep[fa1]+1;    for(int i=0;i<(int)edge[x].size();i++){        int y=edge[x][i];        if(y==fa1)continue;        if(edge1[x].find(y)!=edge1[x].end())continue;//要删除的边不能放进去        if(Q[y]){G1[++len]=(node1){x,y};continue;}        f(y,x);    }    Rt[x]=T;}struct Tree{//线段树部分    struct node{int L,R,sum;}tree[4*M];    void Build(int L,int R,int p){        tree[p].L=L;tree[p].R=R;tree[p].sum=0;        if(L==R){tree[p].sum=dep[dfsline[L]];return;}        int mid=(L+R)/2;        Build(L,mid,p*2);Build(mid+1,R,p*2+1);    }    int Query(int x,int p){        if(tree[p].L==tree[p].R)return tree[p].sum;        int mid=(tree[p].L+tree[p].R)/2;        if(mid>=x)return tree[p].sum+Query(x,p*2);        else return tree[p].sum+Query(x,p*2+1);    }    void Change(int L,int R,int p){        if(tree[p].L==L&&tree[p].R==R){            tree[p].sum--;return;        }        int mid=(tree[p].L+tree[p].R)/2;        if(mid>=R)Change(L,R,p*2);        else if(mid<L)Change(L,R,p*2+1);        else Change(L,mid,p*2),Change(mid+1,R,p*2+1);    }}tree;struct Lca{//LCA部分    void Init(){        for(int j=1;j<20;j++)            for(int i=1;i<=n;i++)                fa[j][i]=fa[j-1][fa[j-1][i]];    }    int LCA(int x,int y){        if(dep[x]>dep[y])swap(x,y);        int step=dep[y]-dep[x];//一开始这里错了        for(int i=0;i<20;i++)            if(step&(1<<i))y=fa[i][y];        if(x==y)return x;        for(int i=19;i>=0;i--)            if(fa[i][x]!=fa[i][y])                x=fa[i][x],y=fa[i][y];        return fa[0][x];    }}LCA;int Find(int x){return x==fa2[x]?x:fa2[x]=Find(fa2[x]);}void Hebing(int x,int y){    x=Find(x),y=Find(y);//这两句一开始没加,然后运行错,要是不加,下面的程序就会执行两次,Tree.Change就会用两次    while(x!=y){        if(dep[x]>=dep[y]){            tree.Change(Lt[x],Rt[x],1);            fa2[x]=fa[0][x];            x=Find(x);        }        else{            tree.Change(Lt[y],Rt[y],1);            fa2[y]=fa[0][y];            y=Find(y);        }    }}int main(){     scanf("%d%d%d",&n,&m,&cas);    for(int i=1;i<=m;i++){        int x,y;        scanf("%d%d",&x,&y);        edge[x].push_back(y);        edge[y].push_back(x);    }     for(int i=1;i<=cas;i++){        scanf("%d%d%d",&op[i],&G[i].x,&G[i].y);        if(op[i]==0)edge1[G[i].x].insert(G[i].y),edge1[G[i].y].insert(G[i].x);//把删除的边存下来    }    f(1,0);//造树    LCA.Init();//LCA预处理    tree.Build(1,n,1);//建线段树    for(int i=1;i<=n;i++)fa2[i]=i;    for(int i=1;i<=len;i++)Hebing(G1[i].x,G1[i].y);    for(int i=cas;i>=1;i--){        if(op[i]==0)Hebing(G[i].x,G[i].y);        else {//这里更以前一样            int lca=LCA.LCA(G[i].x,G[i].y);            int Lres=tree.Query(Lt[G[i].x],1),Rres=tree.Query(Lt[G[i].y],1),LCAres=tree.Query(Lt[lca],1);            ans[i]=Lres+Rres-LCAres*2;        }    }    for(int i=1;i<=cas;i++)        if(op[i])printf("%d\n",ans[i]);    return 0;}

被第一题坑了,卡了一个多小时,哎……

原创粉丝点击