10.12离线赛

一、神经网络
数据:对于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 100005
using 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;
}

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值