1. 程式人生 > >10.12離線賽

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;
}

被第一題坑了,卡了一個多小時,哎……