洛谷 P2146 [NOI2015]軟件包管理器 樹鏈剖分
題面
題目描述
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]軟件包管理器 樹鏈剖分