题解 P2515 【[HAOI2010]软件安装】

KevinYu

2018-11-28 21:12:22

Solution

这一题真是无限次苦苦挣扎得出的产物啊。 我们来一步步地分析题面: 1.这一道题乍一看像是一个树上背包的问题,我们于是得出程序的主要框架:树形dp。 我们先建图,用```up[i]```来存储i的先决条件(注意:x,i不要搞反了)。 ```cpp for(int i=1;i<=n;i++) { int x; scanf("%d",&x); up[i]=x; if(x!=0)addedge(x,i); } ``` 但是,题目描述中并没有说这个东西一定是一棵树,它有可能是一张带环图,也可能是一个森林。 例如,考虑这样的一个依赖关系: ```cpp 4 9 3 3 3 1 2 3 4 6 2 3 1 1 ``` 我们发现:这个图建完后是一个带环图。1,2,3号节点互为依赖条件。一旦有其中任何一个点没有被选择,整个环里面的所有内容都无法被使用。 我们重新观察这张图,我们发现1,2,3号节点的共性:我们可以对它整个选择,也可以对它整个不选择,对其中的任何一个真子集都没有意义。 对于环中点有共性的图,我们考虑用缩点来解决问题(本题中以tarjan算法为例)。 2.缩点过程(会tarjan的可以跳过): tarjan算法基于时间戳与dfs实现,我们将一个点被发现的时间存入dfn数组中,然后将一个点够追溯到的最早的栈中节点的次序计入low数组中。 我们来过一遍算法流程: 1.初始化: ```cpp void tarjan(int u) { low[u]=dfn[u]=++now; hep[++top]=u;vis[u]=1; ``` 我们可以发现,tarjan的初始化中要完成2个工作: ①.更新时间戳与low数组 ②.将节点压栈并打上标记 2.tarjan主过程: ```cpp for(int i=head[u];i!=-1;i=a[i].next) { int v=a[i].to; if(!dfn[v]){tarjan(v);low[u]=min(low[u],low[v]);} else if(vis[v])low[u]=min(low[u],dfn[v]); } ``` 我们来慢慢分析它: 我们首先遍历每一条边,然后对能到达的点进行访问: ```cpp for(int i=head[u];i!=-1;i=a[i].next) { int v=a[i].to; ``` 我们需要分出三种v点: 1.从没访问过的 对于这个点,我们将它作为下一个点,递归地进行tarjan过程。 在tarjan结束后,更新它的low数组。 你可以把更新的过程理解成从tarjan过程里回传来了它的low值,我们将它的low值与现在这个节点的low值比较,并取较小值。 ```cpp if(!dfn[v]) { tarjan(v); low[u]=min(low[u],low[v]); } ``` 2.访问过,并在栈里的 这种节点是可以到达当前节点的,我们发现了这样一个点,就是发现了一个强连通分量,于是我们对当前点的low值进行更新 ```cpp else if(vis[v])low[u]=min(low[u],dfn[v]); ``` 3.访问过,但不在栈里的 都不能到达你,跟你有什么关系吗,直接忽视掉就行了。 3.退栈: ```cpp if(dfn[u]==low[u]) { ++tot; vis[u]=0; while(hep[top+1]!=u) { fa[hep[top]]=tot; vis[hep[top--]]=0; } } } ``` 整个退栈过程就是记录强连通分量的过程,下面我就来详细解释一下。 退栈的条件是```dfn[u]==low[u]```,代表着我们的遍历过程已经触底,并且回溯回来了。 在我们当前点上方的都是什么点呢?都是在同一个强连通分量中的点。所以我们将其全部取出,并记录进一个强连通分量中(一般管这个叫“染色”)。 tarjan缩点过程完整代码: ```cpp void tarjan(int u) { low[u]=dfn[u]=++now; hep[++top]=u;vis[u]=1; for(int i=head[u];i!=-1;i=a[i].next) { int v=a[i].to; if(!dfn[v]) { tarjan(v); low[u]=min(low[u],low[v]); } else if(vis[v])low[u]=min(low[u],dfn[v]); } if(dfn[u]==low[u]) { ++tot; vis[u]=0; while(hep[top+1]!=u) { fa[hep[top]]=tot; vis[hep[top--]]=0; } } } ``` 3.重新建图: 我们通过缩点,将这个图变成一个有向无环图(树或森林),我们需要重新建一个图(注意起点和终点的顺序): ```cpp for(int i=1;i<=n;i++) { val[fa[i]]+=v[i];cost[fa[i]]+=w[i]; if(fa[i]!=fa[up[i]]&&up[i]!=0) { addag(fa[up[i]],fa[i]); usd[fa[i]]++; } } ``` 我们为了将其转换成一棵树,将这个图每一个根节点都连到一个点上: ```cpp int s=tot+1; for(int i=1;i<=tot;i++)if(!usd[i])addag(s,i); cost[s]=0;val[s]=0; ``` 4.树形dp: 我们用dfs来描述dp过程 规定dfs(u)表示进行到了当前节点,```f[i][j]```代表当前节点是i,还剩j的空间。 算法流程如下: 1.初始化 ```cpp for(int i=cost[u];i<=m;i++)f[u][i]=val[u]; ``` 我们将每一个大于当前点花费的f数组初始化为u的价值。 2.遍历每一条边: ```cpp for(int i=hag[u];i!=-1;i=dag[i].next) { int v=dag[i].to; ``` 3.根据转移方程完成算法: 我们取了下一个点,并意识到:我们可以选当前点,也可以不选当前点,若不选当前点,其所有子节点都无法被选择。 依此我们得到状态转移方程: ```cpp f[u][j+cost[u]]={max(f[u][j+cost[u]],f[u][j+cost[u]-q]+f[v][q]);} ``` 就是取子节点中的最优解,并加上当前节点的权值。 ```cpp dfs(v); for(int j=m-cost[u];j>=0;j--) { for(int q=0;q<=j;q++) { f[u][j+cost[u]]=max(f[u][j+cost[u]],f[u][j+cost[u]-q]+f[v][q]); } } } } ``` dfs过程就此结束。 完整代码: ```cpp #include<cstdio> #include<cstring> #include<cmath> #include<cstdlib> #include<climits> #include<ctime> #include<algorithm> #include<complex> #include<iostream> #include<map> #include<queue> #include<vector> #define INF 0x3f3f3f3f #define ll long long using namespace std; struct edge { int to,next; }a[1010],dag[1010]; int hag[1010]; int head[1010]; int v[1010]; int w[1010]; int cost[1010]; int val[1010]; int f[1010][5050]; int low[1010]; int dfn[1010]; int now(0); int hep[1010]; int top(0); int vis[1010]; int fa[1010]; int up[1010]; int usd[1010]; int cnt(0); int cal(0); int tot(0); int n,m; void addedge(int xi,int yi) { a[cnt].to=yi; a[cnt].next=head[xi]; head[xi]=cnt++; } void addag(int xi,int yi) { dag[cal].to=yi; dag[cal].next=hag[xi]; hag[xi]=cal++; } void tarjan(int u) { low[u]=dfn[u]=++now; hep[++top]=u;vis[u]=1; for(int i=head[u];i!=-1;i=a[i].next) { int v=a[i].to; if(!dfn[v]) { tarjan(v); low[u]=min(low[u],low[v]); } else if(vis[v])low[u]=min(low[u],dfn[v]); } if(dfn[u]==low[u]) { ++tot; vis[u]=0; while(hep[top+1]!=u) { fa[hep[top]]=tot; vis[hep[top--]]=0; } } } void dfs(int u) { for(int i=cost[u];i<=m;i++)f[u][i]=val[u]; for(int i=hag[u];i!=-1;i=dag[i].next) { int v=dag[i].to; dfs(v); for(int j=m-cost[u];j>=0;j--) { for(int q=0;q<=j;q++) { f[u][j+cost[u]]=max(f[u][j+cost[u]],f[u][j+cost[u]-q]+f[v][q]); } } } } int main() { memset(head,-1,sizeof(head)); memset(hag,-1,sizeof(hag)); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++)scanf("%d",&w[i]); for(int i=1;i<=n;i++)scanf("%d",&v[i]); for(int i=1;i<=n;i++) { int x; scanf("%d",&x); up[i]=x; if(x!=0)addedge(x,i); } for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i); for(int i=1;i<=n;i++) { val[fa[i]]+=v[i];cost[fa[i]]+=w[i]; if(fa[i]!=fa[up[i]]&&up[i]!=0) { addag(fa[up[i]],fa[i]); usd[fa[i]]++; } } int s=tot+1; for(int i=1;i<=tot;i++)if(!usd[i])addag(s,i); cost[s]=0;val[s]=0; dfs(s); printf("%d",f[s][m+cost[s]]); return 0; } ```