1. 程式人生 > >【清北學堂2018-刷題衝刺】Contest 8

【清北學堂2018-刷題衝刺】Contest 8

Task 1:關聯點

【問題描述】

 ⼆叉樹是⼀種常用的資料結構,⼀個⼆叉樹或者為空,或者由根節點、左⼦樹、右⼦樹構成,其中左⼦樹和右⼦樹都是⼆叉樹. 每個節點a 可以儲存⼀個值val.

 顯然,如果⼀個點a 的左⼦樹或右⼦樹內有⼀個點b,那麼存在唯⼀的路徑從a 出發,每次往左⼦樹或右⼦樹⾛,經過⼀系列節點訪問到b. 我們把從a 到b 經過除a 以外的節點數稱為節點a 到節點b 的距離.

 對於⼀個點a,定義:

 若點\(b\)\(a\) 的左⼦樹中,且\(a\)\(b\) 的距離為\(v[b]\),則稱\(b\)\(a\) 的左關聯點;

 若點\(b\)\(a\)
的右⼦樹中,且\(a\)\(b\) 的距離為\(v[b]\),則稱\(b\)\(a\) 的右關聯點.

 給定⼀個共有\(n\) 個節點的⼆叉樹,所有節點編號為\(1, 2, ...,n,\)其中1 為根節點. 給出每個節點\(a\) 儲存的值\(v[a]\),請輸出每個節點的左關聯點個數和右關聯點個數.

【輸入格式】

 輸⼊⽂件名為\(node.in\)

 第⼀⾏⼀個正整數\(n\),表示⼆叉樹的總節點數.

 第⼆⾏\(n\) 個由空格分隔的正整數\(v1,v2,..., vn\)\(i\) 個數\(vi\) 表示節點\(i\) 儲存的值.

 接下來\(n\) ⾏,第\(i\)
⾏為兩個由空格分隔的整數,分別表示編號為\(i\) 的左⼦樹的根節點(若左⼦樹為空則為0)和右⼦樹的根節點(若右⼦樹為空則為0).

【輸出格式】

 輸出⽂件名為\(node.out\)

 輸出\(n\) ⾏,第\(i\) ⾏為兩個整數,分別表示點\(i\) 的左關聯點個數和右關聯點個數.

node.in node.out
5 2 0
2 1 3 2 1 0 1
2 3 0 0
4 5 0 0
0 0 0 0
0 0
0 0

【樣例說明】

 節點1 的左關聯點有2 個:2 和4,沒有右關聯點.

 節點2 沒有左關聯點,右關聯點有1 個:5.

 除此之外,其它節點沒有關聯點.

【資料規模與約定】

  • 對於30% 的資料,\(n <= 3\).
  • 對於60% 的資料,\(n <= 500\).
  • 對於100% 的資料,\(n <= 200000,1 <= vi <= 200000\),根節點1 到任意節點的距離不超過100000.

 題目本身不難理解,這裡給出四種寫法思路:

  • 暴力O(n^2)(60pts),直接模擬某個點\(u\)向上找\(val[ u ]\)的節點,記錄答案
  • 倍增O(nlogn),暴力基礎上的優化,以更快的速度向上尋找節點記錄答案
  • 堆O(nlogn),在向下搜尋的時候把該節點對應的父節點扔進堆裡維護,按dfn排序,到達時退棧即可
  • 棧O(n),由於搜尋本身就是入棧退棧的過程,只需要搜尋同時維護一個棧就可以O(1)找到答案。

 本蒻的程式碼裡寫的是第三種。後三種程式碼都是可以AC的,但棧的寫法才是最優秀的。比較簡單不做贅述。

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define MAXN 200010
using namespace std;
int n,arr[MAXN],ans_1[MAXN],ans_2[MAXN],dfn[MAXN];
struct Node{int ls,rs,fa;}node[MAXN];
struct Rec{
    int deep;
    bool operator<(const Rec &rhs)const{
        return deep<rhs.deep;//get deeper first
    }
};
priority_queue<Rec>que;
void pre(int u,int deep){
    dfn[u]=deep;
    if(node[u].ls!=0)pre(node[u].ls,deep+1);
    if(node[u].rs!=0)pre(node[u].rs,deep+1);
}
void dfs(int u){
    if(node[u].ls!=0)dfs(node[u].ls);
    if(node[u].rs!=0)dfs(node[u].rs);
    //backstack;
    que.push((Rec){dfn[u]-arr[u]});
//  printf("nod=%d deep=%d dfn=%d\n",u,que.top().deep,dfn[u]);
    while(que.top().deep==dfn[u]-1&&!que.empty()){
        if(u==node[node[u].fa].ls){
//          printf("l:fa=%d\n",node[u].fa);
            ans_1[node[u].fa]++;
        }else{
//          printf("r:fa=%d\n",node[u].fa);
            ans_2[node[u].fa]++;
        }
        que.pop();
    }
}
int main(){
    freopen("node.in","r",stdin);
    freopen("node.out","w",stdout);
    scanf("%d",&n);
    memset(node,0,sizeof(node));
    for(int i=1;i<=n;++i){
        scanf("%d",&arr[i]);
    }
    for(int i=1;i<=n;++i){
        scanf("%d",&node[i].ls);
        scanf("%d",&node[i].rs);
        node[node[i].ls].fa=i;
        node[node[i].rs].fa=i;//record father 
    }
    pre(1,0);
    dfs(1);
//  for(int i=1;i<=n;++i)printf("%d ",dfn[i]);
    for(int i=1;i<=n;++i){
        printf("%d %d\n",ans_1[i],ans_2[i]);
    }
}

Task 2:小奇的日程表

【問題描述】

 放暑假了,小奇準備了⼀個日程表來安排他的暑假⽣活.

 ⼀共有n 件事情,編號為\(1,2......n\),第\(i\) 件事情的難度為\(i\). 小奇將整個暑假劃分為m 個時刻,並設定了三個正整數\(a,b,c.\) 然後,小奇定義了⼀個數列\({ xi }\),滿⾜:

  $x[0] = 0 $

  \(x [i] = (a x[ i - 1 ] + b) \% 2nc (1 <= i <= m)\)

 即從x1 開始,數列的每⼀項等於上⼀項的a 倍加上b 以後除以2nc 的餘數.

 在暑假剛開始時,小奇的日程表是空的. 第i 個時刻前,小奇會根據xi 的值決定日程表的變化:

 若\(xi < nc\),則將編號為$⌊ xi / c ⌋ + 1 $的事件加⼊日程表,若日程表已有該事件則忽略;

 若\(xi >= nc\),則將編號為$⌊ xi / c ⌋ - n + 1 $的事件從日程表刪除,若日程表沒有該事件則忽略;

 第i 個時刻\((1 <= i <= m)\),小奇所做的事情就是該時刻日程表中的所有事件.

 對於每個時刻,小奇定義該時刻的⼯作量為該時刻做了⼏件事情,該時刻的疲勞度為該時刻做的所有事情的難度之和. 整個暑假小奇的⼯作量為所有時刻的⼯作量之和,疲勞度為所有時刻的疲勞度之和.

 請根據$n,m,a,b,c $計算小奇這個暑假的⼯作量和疲勞度.

【輸入格式】

 輸⼊⽂件名為\(schedule.in\)

 輸⼊⼀⾏,五個由空格分隔的正整數\(n,m,a,b,c\)

【輸出格式】

 輸出⽂件名為\(schedule.out\)

 ⼀⾏兩個由空格分隔的整數,第⼀個數為小奇這個暑假的⼯作量,第⼆個數為小奇這個暑假的疲勞度.

 由於答案可能很⼤,請輸出答案除以1000000007 的餘數.

【樣例輸入1】
​ 3 6 4 1 5
【樣例輸出1】
​ 8 13
【樣例輸入2】
​ 431942 2000000 324635 9496472 24439
【樣例輸出2】
​ 879995658 63186390

【樣例1 說明】

 由\(x0 = 0,xi = (4x[ i - 1 ] + 1) mod 30(1 <= i <= 6)\)

 可推得\(x1 = 1,x2 = 5,x3 = 21,x4 = 25,x5 = 11,x6 = 15\).

 第1 個時刻,日程表加⼊事件1,該時刻的事件有{ 1 },⼯作量為1,疲勞度為1.

 第2 個時刻,日程表加⼊事件2,該時刻的事件有{ 1,2 },⼯作量為2,疲勞度為1 + 2 = 3.

 第3 個時刻,日程表刪除事件2,該時刻的事件有{ 1 },⼯作量為1,疲勞度為1.

 第4 個時刻,日程表刪除事件3,該時刻的事件有{ 1 },⼯作量為1,疲勞度為1.

 第5 個時刻,日程表加⼊事件3,該時刻的事件有{ 1,3 },⼯作量為2,疲勞度為1 + 3 = 4.

 第6 個時刻,日程表刪除事件1,該時刻的事件有{ 3 },⼯作量為1,疲勞度為3.

 因此總⼯作量為1+2+1+1+2+1 = 8,總疲勞度為1+3+1+1+4+3 = 13.

【資料規模與約定】

  • \(對於40\% 的資料,n,m <= 10^3.\)
  • \(對於60\% 的資料,n,m <= 10^5.\)
  • \(另外有20\% 的資料,保證所有刪除均可忽略.\)
  • \(對於100\% 的資料,n <= 5*10^7,m <= 2*10^6,a <= 10^6,b <= 10^9,c <= 5*10^4.\)

 語文題,不用動腦子,動腦子會繞彎,題目讓幹啥你就幹啥就可以了。

 有些人想的是用set來儲存,但仔細觀察會發現實際上只需要一個bool陣列就可以了,還不會爆空間,多好啊。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define MAXM 2000010
#define MAXN 50000010
#define lint long long
using namespace std;
lint n,m,a,b,c,ans_1,ans_2,sum_1,sum_2,arr[MAXM],f[MAXM];
bool vis[MAXN];
int main(){
    freopen("schedule.in","r",stdin);
    freopen("schedule.out","w",stdout);
    cin>>n>>m>>a>>b>>c;
    const lint modd=n*c*2,cmp=n*c;
    for(register int i=1;i<=m;++i){
        f[i]=(a*f[i-1]+b)%modd;
        if(f[i]<cmp){
            int todo=f[i]/c+1;
            if(!vis[todo]){
                vis[todo]=true;
                sum_1+=1;
                sum_2+=todo;
            }
        }else{
            int todo=f[i]/c-n+1;
            if(vis[todo]){
                vis[todo]=false;
                sum_1-=1;
                sum_2-=todo;
            }
        }
        ans_1=(ans_1+sum_1)%1000000007;
        ans_2=(ans_2+sum_2)%1000000007;
    }
    printf("%lld %lld",ans_1,ans_2);
}

Task 3:送分題

【問題描述】

 給定⼀棵N 個節點的樹,每個節點上有⼀個權值。

 你要從中選出⼀些點使得權值和最⼤,任意2 個選出的節點之間的距離都要⼤於K。

【輸入格式】

 輸⼊⽂件名為score.in

 第⼀⾏兩個整數\(N,K\)

 接下來⼀⾏\(N\) 個整數,表示第\(i\) 個節點的權值

 接下來\(N - 1\) ⾏,每⾏2 個數\(a,b\),表示點\(a\)和點\(b\) 之間有邊相連

【輸出格式】

輸出⽂件名為\(score.out\)

輸出⼀⾏,表示最⼤的權值和。

【樣例輸入1】
​ 3 1
​ 1 1 1
​ 1 2
​ 1 3
【樣例輸出1】
​ 2
【樣例輸入2】
​ 3 2
​ 1 1 1
​ 1 2
​ 1 3
【樣例輸出2】
​ 1

【資料範圍】

\(n∈[1,10000],k∈[1,100]\)

 這是一道送命題。

 狀態可以很容易就想到:設\(f[ u ][ j ]\)為當前位於點\(u\),子樹裡最近的選中節點離自身距離大於等於\(j\)

 但是狀態的轉移設計起來就相當麻煩,或者說,說起來容易寫起來難。思路如下:

  • 對於\(j*2>k\)的時候,我們可以隨便選著轉移。(直接累加求\(max\)即可)
  • 對於\(j*2<=k\)的時候,我們需要這樣考慮:

    • 選出一個點距離為\(j\)
    • 其它點的距離都應該大於\(k-j\)
    • 顯然除了有一個點距離為j,其它點距離都是k-j
    • 考慮預先累加求一定距離時的權值和,需要開一個輔助陣列tmp
    • 轉移結束後需要維護一下字尾最大值,讓f[ u ][ j ]是子樹點離u點距離>=j的最大權值
    • 其餘的就是常規的樹上DP了

 不寫一遍你不會知道這個看起來簡單的思路寫起來有多難受,至少狀態轉移方程的推導真的讓人無從下手。思路我最開始都能想出來,但是實現不了。結果看題解的程式碼,完全就是有機會要上,沒機會創造機會也要硬上。。我果然還是Naive啊。。

​ Code:程式碼裡註釋很清晰

#include<cstdio>
#include<cstring>
#define MAXN 10010
int n,dis,cnt,tmp[110],v[MAXN],g[MAXN][110],head[MAXN];
struct edge{
    int nxt;
    int to;
}e[MAXN<<1];

inline void add(int from,int to){
    e[++cnt].nxt=head[from];
    e[cnt].to=to;
    head[from]=cnt;
} 
inline int max(int x,int y){return x>y?x:y;}
inline int min(int x,int y){return x<y?x:y;}

void dfs(int u,int fa){
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(v!=fa){
            dfs(v,u);
        }
    }//先到葉節點,回溯處理
    memset(tmp,0,sizeof(tmp));//提前清空輔助陣列 
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to; 
        g[u][0]+=g[v][dis-1];
    }
    g[u][0]+=v[u];
    //選中本身的操作和其它點的距離dis對應 
    tmp[0]=g[u][0];
    for(int k=0;k<=dis;++k){    
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to;
            tmp[k+1]+=g[v][k];
        }//tmp輔助儲存到子樹距離為[0,dis]情況的權值和 
    }

    for(int j=0;j<=dis;++j){
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to;
            if(dis>=(j<<1)){
                g[u][j]=max(g[u][j],tmp[dis-j]-g[v][dis-j-1]+g[v][j-1]);
            }//若j不足以直接多次選擇,本次點又要考慮選距離為j的情況 
            //就通過這種方法選擇 
        }
        if((j<<1)>=dis){
            g[u][j]=max(g[u][j],tmp[j]);
        }//反之,如果可以直接處理,事情就變得相當簡單。 
    }
    for(int j=dis;j>=0;j--){
        g[u][j]=max(g[u][j],g[u][j+1]);
    }//維護一個字尾最大值 
}
int main(){
    freopen("score.in","r",stdin);
    freopen("score.out","w",stdout);
    scanf("%d%d",&n,&dis);dis++;//輸入時把dis+1便於計算 
    for(int i=1;i<=n;++i){
        scanf("%d",&v[i]);//輸入每個點的權值 
    }
    for(int i=1;i<n;++i){
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v);//建邊 
        add(v,u);
    }
    dfs(1,0);//基於dfs的樹上DP 
    int ans=0;
    for(int i=0;i<=dis;++i){
        ans=max(ans,g[1][i]);//獲取答案 
    }
    printf("%d\n",ans);
}