1. 程式人生 > >洛谷 P2146 [NOI2015]軟件包管理器 樹鏈剖分

洛谷 P2146 [NOI2015]軟件包管理器 樹鏈剖分

並且 linu ins 輸出格式 manage 不依賴 change Nid sum

題面

題目描述

Linux用戶和OSX用戶一定對軟件包管理器不會陌生。通過軟件包管理器,你可以通過一行命令安裝某一個軟件包,然後軟件包管理器會幫助你從軟件源下載軟件包,同時自動解決所有的依賴(即下載安裝這個軟件包的安裝所依賴的其它軟件包),完成所有的配置。Debian/Ubuntu使用的apt-get,Fedora/CentOS使用的yum,以及OSX下可用的homebrew都是優秀的軟件包管理器。

你決定設計你自己的軟件包管理器。不可避免地,你要解決軟件包之間的依賴問題。如果軟件包A依賴軟件包B,那麽安裝軟件包A以前,必須先安裝軟件包B。同時,如果想要卸載軟件包B,則必須卸載軟件包A。現在你已經獲得了所有的軟件包之間的依賴關系。而且,由於你之前的工作,除0號軟件包以外,在你的管理器當中的軟件包都會依賴一個且僅一個軟件包,而0號軟件包不依賴任何一個軟件包。依賴關系不存在環(若有m(m≥2)個軟件包A1,A2,A3,?,Am,其中A1依賴A2,A2依賴A3,A3依賴A4,……,A[m-1]依賴Am,而Am依賴A1,則稱這m個軟件包的依賴關系構成環),當然也不會有一個軟件包依賴自己。

現在你要為你的軟件包管理器寫一個依賴解決程序。根據反饋,用戶希望在安裝和卸載某個軟件包時,快速地知道這個操作實際上會改變多少個軟件包的安裝狀態(即安裝操作會安裝多少個未安裝的軟件包,或卸載操作會卸載多少個已安裝的軟件包),你的任務就是實現這個部分。註意,安裝一個已安裝的軟件包,或卸載一個未安裝的軟件包,都不會改變任何軟件包的安裝狀態,即在此情況下,改變安裝狀態的軟件包數為0。

輸入輸出格式

輸入格式:

從文件manager.in中讀入數據。

輸入文件的第1行包含1個整數n,表示軟件包的總數。軟件包從0開始編號。

隨後一行包含n?1個整數,相鄰整數之間用單個空格隔開,分別表示1,2,3,?,n?2,n?1號軟件包依賴的軟件包的編號。

接下來一行包含1個整數q,表示詢問的總數。之後q行,每行1個詢問。詢問分為兩種:

install x:表示安裝軟件包x

uninstall x:表示卸載軟件包x

你需要維護每個軟件包的安裝狀態,一開始所有的軟件包都處於未安裝狀態。

對於每個操作,你需要輸出這步操作會改變多少個軟件包的安裝狀態,隨後應用這個操作(即改變你維護的安裝狀態)。

輸出格式:

輸出到文件manager.out中。

輸出文件包括q行。

輸出文件的第i行輸出1個整數,為第i步操作中改變安裝狀態的軟件包數。

輸入輸出樣例

輸入樣例#1:

7
0 0 0 1 1 5
5
install 5
install 6
uninstall 1
install 4
uninstall 0

輸出樣例#1:

3
1
3
2
3

輸入樣例#2:

10
0 1 2 1 3 0 0 3 2
10
install 0
install 3
uninstall 2
install 7
install 5
install 9
uninstall 9
install 4
install 1
install 9

輸出樣例#2:

1
3
2
1
3
1
1
1
0
1

說明

【樣例說明1】

一開始所有的軟件包都處於未安裝狀態。

安裝5號軟件包,需要安裝0,1,5三個軟件包。

之後安裝6號軟件包,只需要安裝6號軟件包。此時安裝了0,1,5,6四個軟件包。

卸載1號軟件包需要卸載1,5,6三個軟件包。此時只有0號軟件包還處於安裝狀態。

之後安裝4號軟件包,需要安裝1,4兩個軟件包。此時0,1,4處在安裝狀態。最後,卸載0號軟件包會卸載所有的軟件包。

【數據範圍】

對於10%數據,n=5000,q=5000;

對於另外10%數據,n=100000,q=100000,且數據不包含卸載操作;

對於另外20%數據,n=100000,q=100000,且編號為i的軟件包所依賴的軟件包在[0,i-1]內均勻隨機,每次執行操作的軟件包編號在[0,i-1]內均勻隨機

對於另外60%數據,n=100000,q=100000

【時限1s,內存512M】

思路

題目好長,先整理下題意。可參考下面的題意理解

給定一棵n個結點的樹,根節點為0,每個點的點權為0或1。初始時所有點點權為0,基本操作如下:

install x 查詢x的深度與x到根節點路徑上的點的點權和之差,並將這些點的點權全部變為1

uninstall x 查詢以x為根結點的子樹內所有點的點權和,並將這些的點的點權全部變為0

為什麽可以這樣轉化呢?

首先每個點的點權代表是否被安裝(其中1代表安裝)。

安裝x時,需要查詢x到0路徑上有幾個沒有被安裝,即有幾個為0。x到根節點的結點個數即x的深度,其中1的個數即為x到根節點路徑上的點的點權和,作差可得其中0的個數,即未安裝的個數。然後再將這些點均標記為已安裝,即標為1。

卸載x時,由於依賴x的安裝包都要被卸載,即以x為根節點的子樹內所有點都要變為0。查詢時只需要查詢這些點中有多少個點權為1,即這些點的點權和。最後將他們全部變為0。

這樣,就基本是一道樹剖板子題了吧。不過,在線段樹上還要略做改變

在下傳時,+=可以改為=。並且,懶標記如果直接就是0和1,那麽下傳時不清楚這是由於本身就是0還是懶標記打上了0,所以我定義懶標記是1和2,不用下傳時懶標記是0

AC代碼

#include<bits/stdc++.h>
const int maxn=100010;
using namespace std;

int n,m;
int tot,to[maxn<<1],nxt[maxn<<1],head[maxn];
int fa[maxn],son[maxn],dep[maxn],len[maxn];
int cnt,top[maxn],nid[maxn],nw[maxn];
struct SegmentTree
{
    int l,r,sum,tag;
    #define l(a) tree[a].l
    #define r(a) tree[a].r
    #define m(a) ((l(a)+r(a))>>1)
    #define len(a) (r(a)-l(a)+1)
    #define s(a) tree[a].sum
    #define t(a) tree[a].tag
}tree[maxn<<2];

void dfs1(int u,int f,int d)
{
    dep[u]=d;fa[u]=f;len[u]=1;
    int maxson=-1;
    for(int i=head[u];i;i=nxt[i])
    {
        int v=to[i];
        if(v==f) continue;
        dfs1(v,u,d+1);
        len[u]+=len[v];
        if(len[v]>maxson) maxson=len[v],son[u]=v;
    }
}

void dfs2(int p,int t)
{
    nid[p]=++cnt;
    top[p]=t;
    if(!son[p]) return;
    dfs2(son[p],t);
    for(int i=head[p];i;i=nxt[i])
    {
        int v=to[i];
        if(v==fa[p] || v==son[p]) continue;
        dfs2(v,v);
    }
}

void BuildTree(int p,int l,int r)
{
    l(p)=l;r(p)=r;
    if(l==r) return;
    BuildTree(p<<1,l,m(p));
    BuildTree(p<<1|1,m(p)+1,r);
}

void PushDown(int p) 
{
    if(t(p)==1)
    {
        s(p<<1)=len(p<<1);s(p<<1|1)=len(p<<1|1);
        t(p<<1)=t(p<<1|1)=1;
        t(p)=0;
    }
    else if(t(p)==2)
    {
        s(p<<1)=0;s(p<<1|1)=0;
        t(p<<1)=t(p<<1|1)=2;
        t(p)=0;
    }
}

void Change1(int p,int l,int r,int k)
{
    if(l<=l(p) && r>=r(p))
    {
        if(k==1) s(p)=len(p);
        else if(k==2) s(p)=0;
        t(p)=k;
        return;
    }
    PushDown(p);
    if(l<=m(p)) Change1(p<<1,l,r,k);
    if(r>m(p)) Change1(p<<1|1,l,r,k);
    s(p)=s(p<<1)+s(p<<1|1);
}

int Ask1(int p,int l,int r)
{
    if(l<=l(p) && r>=r(p)) return s(p);
    PushDown(p);
    int ans=0;
    if(l<=m(p)) ans+=Ask1(p<<1,l,r);
    if(r>m(p)) ans+=Ask1(p<<1|1,l,r);
    return ans;
}

void Change2(int p)
{
    while(top[p]!=1)
    {
        Change1(1,nid[top[p]],nid[p],1);
        p=fa[top[p]];
    }
    Change1(1,1,nid[p],1);
}

int Ask2(int p)
{
    int ans=0;
    while(top[p]!=1)
    {
        ans+=Ask1(1,nid[top[p]],nid[p]);
        p=fa[top[p]];
    }
    ans+=Ask1(1,1,nid[p]);
    return ans;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<n;i++) 
    {
        int v;scanf("%d",&v);
        to[++tot]=v+1;nxt[tot]=head[i+1];head[i+1]=tot;
        to[++tot]=i+1;nxt[tot]=head[v+1];head[v+1]=tot;
    }
    dfs1(1,1,1);
    dfs2(1,1);
    BuildTree(1,1,n);
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        string way;
        cin>>way;
        if(way=="install")
        {
            int p;scanf("%d",&p);p++;
            printf("%d\n",dep[p]-Ask2(p));
            Change2(p);
        }
        if(way=="uninstall")
        {
            int p;scanf("%d",&p);p++;
            printf("%d\n",Ask1(1,nid[p],nid[p]+len[p]-1));
            Change1(1,nid[p],nid[p]+len[p]-1,2);
        }
    }
    return 0;
}

總結與拓展

主要是要讀清楚題,將其本質剖析出來,想明白涉及了哪些修改和查詢方式

洛谷 P2146 [NOI2015]軟件包管理器 樹鏈剖分