BZOJ 2427 软件安装(强连通分量+树形背包)
题意:现在我们的手头有N个软件,对于一个软件i,它要占用Wi的磁盘空间,它的价值为Vi。我们希望从中选择一些软件安装到一台磁盘容量为M计算机上,使得这些软件的价值尽可能大(即Vi的和最大)。
但是现在有个问题:软件之间存在依赖关系,即软件i只有在安装了软件j(包括软件j的直接或间接依赖)的情况下才能正确工作(软件i依赖软件j)。幸运的是,一个软件最多依赖另外一个软件。如果一个软件不能正常工作,那么它能够发挥的作用为0。
# include <cstdio> # include <cstring> # include <cstdlib> # include <iostream> # include <vector> # include <queue> # include <stack> # include <map> # include <bitset> # include <set> # include <cmath> # include <algorithm> using namespace std; # define lowbit(x) ((x)&(-x)) # define pi acos(-1.0) # define eps 1e-8 # define MOD 2007 # define INF 1000000000 # define mem(a,b) memset(a,b,sizeof(a)) # define FOR(i,a,n) for(int i=a; i<=n; ++i) # define FO(i,a,n) for(int i=a; i<n; ++i) # define bug puts("H"); # define lch p<<1,l,mid # define rch p<<1|1,mid+1,r # define mp make_pair # define pb push_back typedef pair<int,int> PII; typedef vector<int> VI; # pragma comment(linker, "/STACK:1024000000,1024000000") typedef long long LL; int Scan() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } const int N=105; //Code begin... struct Edge{int p, next;}edge[N]; int head[N], cnt=1; int W[N], V[N], D[N], cost[N], val[N], dp[N][505], m; int Low[N], DFN[N], Stack[N], Belong[N], dee[N], Index, top, scc; bool Instack[N], vis[N][N]; VI E[N]; void add_edge(int u, int v){edge[cnt].p=v; edge[cnt].next=head[u]; head[u]=cnt++;} void Tarjan(int u){ int v; Low[u]=DFN[u]=++Index; Stack[top++]=u; Instack[u]=true; for (int i=head[u]; i; i=edge[i].next) { v=edge[i].p; if (!DFN[v]) { Tarjan(v); if (Low[u]>Low[v]) Low[u]=Low[v]; } else if (Instack[v]&&Low[u]>DFN[v]) Low[u]=DFN[v]; } if (Low[u]==DFN[u]) { ++scc; do{ v=Stack[--top]; Instack[v]=false; Belong[v]=scc; cost[scc]+=W[v]; val[scc]+=V[v]; }while (v!=u); } } void solve(int n){ mem(DFN,0); mem(Instack,false); Index=scc=top=0; FOR(i,1,n) if (!DFN[i]) Tarjan(i); } void dfs(int x){ FO(i,0,E[x].size()) { int v=E[x][i]; dfs(v); for (int j=m; j>=0; --j) FOR(k,cost[v],j) dp[x][j]=max(dp[x][j],dp[x][j-k]+dp[v][k]); } for (int i=m; i>=cost[x]; --i) dp[x][i]=dp[x][i-cost[x]]+val[x]; } int main () { int n; scanf("%d%d",&n,&m); FOR(i,1,n) scanf("%d",W+i); FOR(i,1,n) scanf("%d",V+i); FOR(i,1,n) { scanf("%d",D+i); if (D[i]) add_edge(D[i],i); } solve(n); FOR(i,1,n) { int u=Belong[i]; for (int j=head[i]; j; j=edge[j].next) { int v=Belong[edge[j].p]; if (u==v||vis[u][v]) continue; E[u].pb(v); vis[u][v]=true; ++dee[v]; } } FOR(i,1,scc) if (!dee[i]) E[0].pb(i); dfs(0); printf("%d\n",dp[0][m]); return 0; }
我们现在知道了软件之间的依赖关系:软件i依赖软件Di。现在请你设计出一种方案,安装价值尽量大的软件。一个软件只能被安装一次,如果一个软件没有依赖则Di=0,这时只要这个软件安装了,它就能正常工作。
依照依赖关系可以建一个图,这个图中每个点的入度至多1,不难发现,这是一些环加上树组成的森林,对于环,要么不选要么都选,于是可以把环缩点。
这样原图就变成了一个有向森林,对于每个根节点,我们建立一个虚拟节点连向这些节点,于是就变成了一颗树。
在树上做树形依赖背包即可,定义dp[x][v]表示x的子树占用了v的内存能产生的最大价值。转移方程很简单。
时间复杂度O(n^2*m).