bzoj 2427 软件安装 - Tarjan - 树形动态规划
2016-07-18 21:24
176 查看
题目描述
现在我们的手头有N个软件,对于一个软件i,它要占用Wi的磁盘空间,它的价值为Vi。我们希望从中选择一些软件安装到一台磁盘容量为M计算机上,使得这些软件的价值尽可能大(即Vi的和最大)。但是现在有个问题:软件之间存在依赖关系,即软件i只有在安装了软件j(包括软件j的直接或间接依赖)的情况下才能正确工作(软件i依赖软件j)。幸运的是,一个软件最多依赖另外一个软件。如果一个软件不能正常工作,那么它能够发挥的作用为0。
我们现在知道了软件之间的依赖关系:软件i依赖软件Di。现在请你设计出一种方案,安装价值尽量大的软件。一个软件只能被安装一次,如果一个软件没有依赖则Di=0,这时只要这个软件安装了,它就能正常工作。
输入
第1行:N, M (0<=N<=100, 0<=M<=500)第2行:W1, W2, ... Wi, ..., Wn (0<=Wi<=M )
第3行:V1, V2, ..., Vi, ..., Vn (0<=Vi<=1000 )
第4行:D1, D2, ..., Di, ..., Dn(0<=Di<=N, Di≠i )
输出
[b]一个整数,代表最大价值。[/b]样例输入
3 105 5 6
2 3 4
0 1 1
样例输出
5来源
Day2(转自http://www.lydsy.com/JudgeOnline/problem.php?id=2427)
这道题看起来像是树归,首先来判断一下,一个软件只能依赖于一个软件,正如树,一个节点只有一个父节点(除了根节点)(把所有节点都连接到0号节点下),但是来构思一组数据:
D: 2 3 1
像这样,1依赖于2,2依赖于3.,3依赖于1,构成了一个环(强连通分量),明显不符合树的性质,但是仔细想想,要么全选要么全不选
就可以把它当成一个节点来看待。
至于缩点。。。这个也比较简单,和codevs上"爱在心中"差不多,首先给belong数组赋初值:
for(int i = 0;i <= n;i++) belong[i] = i;
再Tarjan一次,将元素弹出栈时要将对应belong数组中这个强连通分量的值全部设成这中间任意元素的值(但必须一样)
接着for循环扫描一次,凡是belong[i] != i的像这样处理一下:
w[belong[i]] += w[i]; v[belong[i]] += v[i];
如果出现访问d[i]就像这样访问:
d[belong[i]]......
是不是十分方便快捷?(除了Tarjan算法的代码复杂度)
下面思考一下树归方程,感觉如果玩多叉树的话状态很多,就转成二叉树,为了代码简洁,有这么两种方法
第一种方法是记录每个节点添加进的"兄弟"的地址,就加一个指针变量,添加一个"儿子"或者"兄弟"时,就访问
这个对应的指针变量,先正常加入,然后把指针指向它
for(int i = 1;i <= n;i++){ for(int j = head[i];j;j = edge[j].next){ if(belong[i] == i && belong[i] != belong[edge[j].end]){ if(node[i]->left != NULL) node[i]->left->bro = &node[edge[belong[j]].end]; else node[i]->left = &node[edge[belong[j]].end];; } } }
是不是显得有点麻烦?而且严重牺牲了可读性,在看了某大神的代码后,我知道了这种方法:
用bro[i]储存i节点的右子树(原先的"兄弟"),用son[i]储存i节点的左子树(原先的"子节点"),
![](https://images2015.cnblogs.com/blog/989955/201607/989955-20160718211128185-1522020845.png)
然后:
![](https://images2015.cnblogs.com/blog/989955/201607/989955-20160718211329622-281813733.png)
最后:
![](https://images2015.cnblogs.com/blog/989955/201607/989955-20160718211453138-1936927238.png)
代码实现也比较简单:
for(int i = 1;i <= n;i++){ for(int j = head[i];j;j = edge[j].next){ if(belong[i] == i && belong[i] != belong[edge[j].end]){ bro[i] = son[belong[edge[j].end]]; son[belong[edge[j].end]] = i; } } }
另外关于左右子树出现空的时候,如果多加几个if语句的话,可能树形DP的代码就和Tarjan算法有的一拼了,可以把
它的左右子树的位置设成一个值为0的节点,这就相当于一个空节点,但并不影响计算结果
写出树归方程:
f[index][i] = max(f[index][i],v[index] + f[left][j] + f[right][limit - j]);
(index是第index个节点,limit是i - w[i],left = son[index],right = bro[index])
最后附上我超级不简洁的代码(如果想要速度更快,可以把cin,cout改成scanf和printf)
Code:
/**
* bzoj
* Problem#2427
* Accepted
* Time:220ms
* Memory:1492k
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<stack>
#include<vector>
#define _min(a,b) ((a)<(b))?(a):(b)
using namespace std;
typedef bool boolean;
typedef class Edge{
public:
int end;
int next;
Edge():end(0),next(0){}
Edge(int end,int next):end(end),next(next){}
}Edge;
Edge *edge;
int n,m;
int *w; //软件大小
int *v; //价值
int *d;
int *head;
int top;
inline void addEdge(int from,int end){
top++;
edge[top].next=head[from];
edge[top].end=end;
head[from]=top;
}
boolean *visited;
int *visitID;
int *exitID;
int entryed;
stack<int> sta;
int *belong;
boolean *inStack;
int *bro;
int *son;
void getSonMap(int end){
int now=-1;
int exits=0;
while(now!=end){
now=sta.top();
belong[now]=end;
inStack[now]=false;
exits++;
sta.pop();
}
}
void Tarjan(const int pi){
int index=head[pi];
visitID[pi]=++entryed;
exitID[pi]=visitID[pi];
visited[pi]=true;
inStack[pi]=true;
sta.push(pi);
while(index!=0){
if(!visited[edge[index].end]){
Tarjan(edge[index].end);
exitID[pi]=_min(exitID[pi],exitID[edge[index].end]);
}else if(inStack[edge[index].end]){
exitID[pi]=_min(exitID[pi],visitID[edge[index].end]);
}
index=edge[index].next;
}
if(exitID[pi]==visitID[pi]){
getSonMap(pi);
}
}
void rebuild(){
vector<int> indexs;
for(int i=1;i<=n;i++){
if(belong[i] != i){
v[belong[i]] += v[i];
w[belong[i]] += w[i];
indexs.push_back(belong[i]);
}
}
for(int i = 0;i < indexs.size();i++)
edge[head[indexs[i]]].end = 0;
}
void create(){
bro = new int[(const int)(n + 1)];
son = new int[(const int)(n + 1)];
for(int i = 0;i <= n;i++){
son[i] = n + 1;
bro[i] = n + 1;
}
for(int i = 1;i <= n;i++){ for(int j = head[i];j;j = edge[j].next){ if(belong[i] == i && belong[i] != belong[edge[j].end]){ bro[i] = son[belong[edge[j].end]]; son[belong[edge[j].end]] = i; } } }
}
int f[102][501];
void solve(int index){
if(index > n) return ;
solve(son[index]);
solve(bro[index]);
int left = son[index];
int right = bro[index];
for(int i = 1;i <= m;i++){
f[index][i] = max(f[index][i],f[right][i]);
int limit = i - w[index];
for(int j = 0;j <= limit;j++){
f[index][i] = max(f[index][i],v[index] + f[left][j] + f[right][limit - j]);
}
}
}
int main(){
cin>>n>>m;
head=new int[(const int)(n+1)];
edge=new Edge[(const int)(n+1)];
visited=new boolean[(const int)(n+1)];
visitID=new int[(const int)(n+1)];
exitID =new int[(const int)(n+1)];
belong =new int[(const int)(n+1)];
inStack=new boolean[(const int)(n+1)];
memset(head,0,sizeof(int)*(n+1));
memset(visited,false,sizeof(boolean)*(n+1));
memset(inStack,false,sizeof(boolean)*(n+1));
w = new int[(const int)(n + 1)];
v = new int[(const int)(n + 1)];
d = new int[(const int)(n + 1)];
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++){
scanf("%d",&d[i]);
addEdge(i, d[i]);
}
for(int i=0;i<=n;i++) belong[i]=i;
for(int i=1;i<=n;i++){
if(!visited[i])
Tarjan(i);
}
delete[] visited;
delete[] inStack;
rebuild();
delete[] exitID;
delete[] visitID;
create();
w[0] = 0;
v[0] = 0;
solve(0);
printf("%d",f[0][m]);
return 0;
}
相关文章推荐
- 算法日记(Java实现)第20160718(4)期——POJ1007
- 怎样在项目中开启JDBC事物控制
- shiro集成CAS实现单点登出
- ros中kobuki(turtlebot)+rplidar 跑gmapping
- 史上最全IO集合框架九(数据流:数据字节流)
- 韩星点兵
- Leetcode解题笔记(Linked List)
- 阿里云服务器centos6.5安装java运行环境
- HDU4609——3-idiots(FFT求卷积,留着以后学)
- hdu 1176
- JavaScript的onunload()方法在关闭页面时不执行
- 【谷歌市场安装】Google Play 闪退问题解决
- Java提高篇之BIO、NIO、AIO
- java网络编程基础
- 斐波那契数
- RecyclerView实现条目Item拖拽排序与滑动删除
- Qt之窗口动画(下坠、抖动、透明度)(还有好多相关帖子)
- 备战延迟退休,还债计划生育
- 使用JSON实现简单的城市级联查询
- Unity重新编译mono实现热更新