P1305选课
题目描述:
学校实行学分制。每门的必修课都有固定的学分,同时还必须获得相应的选修课程学分。学校开设了N(N<300)门的选修课程,每个学生可选课程的数量M是给定的。学生选修了这M门课并考核通过就能获得相应的学分。你的任务是为自己确定一个选课方案,使得你能得到的学分最多,并且必须满足先修课优先的原则。假定课程之间不存在时间上的冲突。
输入:
输入文件的第一行包括两个整数N、M(中间用一个空格隔开),其中1≤N≤300,1≤M≤N。 以下N行每行代表一门课。课号依次为1,2,…,N。每行有两个数(用一个空格隔开),第一个数为这门课先修课的课号(若不存在先修课则该项为0),第二个数为这门课的学分。学分是不超过10的正整数。
输出:
- 第一行:只有一个数:实际所选课程的学分总数。
- 下面m行:从下到大所选择的课程。
题解:
这道题相对于上一道题不过是多了一种选择的方案输出而已,我们在转化为二叉树的基础上,愉快的再次建立一个bool数组 ans[i]
来表示这个点 i 是否被选中,然后再次一个DFS递归,如果当前的 f[x][y]==f[Borther[x]][y]
那么我们就继续递归 DFS(Brother[x],y)
就行,当前这个点x完全可以扔掉了!如果当前的 if(f[x][y]==f[Brother[x]][i-1]+f[Child[x]][y-i]+Value[x])
,那么我们就可以判定,当前点x一定是被选中的,然后分别对两个分支进行DFS,一个是儿子,一个是兄弟,然后就可以return了。
记f[x][j]表示以点x为根,选择j个黑点,对答案的最大贡献
为什么是说“对答案的最大贡献呢”?首先我们是从x的子节点y转移过来,那么主要是想到一点,即分别考虑每条边x→y对答案的贡献,也就是“边一侧的黑点数 × 另一侧的黑点数 × 边的长度 + 边一侧的白点数 × 另一侧的白点数 × 边的长度”
我们记sze[x]表示以x为根的子树大小,边x→y对答案的贡献为valx→y,则状态转移方程为:
f[x][j]=Max(f[x][j−k]+f[y][k]+valx→y)
=Max(f[x][j−k]+f[y][k]+k×(j−k)×lenx→y+(sze[y]−k)×(n−m−sze[y]+k)×lenx→y)
看起来我们要枚举x→y,k,j,复杂度为O(n3),实际上我们会发现枚举的范围为0≤k≤sze[y],0≤j−k≤sze[x]−sze[y],那么总的枚举次数就为∑(sze[x]−sze[y])×sze[y],我们不妨把其看作每次在以某一点x为根的子树上任选两个点a,b,使得a,b的最近公共祖先为x的方案数,因此实际复杂度为O(n2)
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| #include<iostream> #include<algorithm> #include<cstring> #include<cstdio> #include<cmath> using namespace std; int n,m,Value[600],Brother[600],x; int f[600][600],Child[600]; bool ans[600]; void DFS(int x,int y) { if(f[x][y]>=0) return; if(x==0||y==0) { f[x][y]=0; return; } DFS(Brother[x],y); for(int i=0;i<y;i++) { DFS(Brother[x],i); DFS(Child[x],y-i-1); int This=max(f[Brother[x]][y],f[x][y]); f[x][y]=max(This,f[Brother[x]][i]+f[Child[x]][y-i-1]+Value[x]); } } void Find(int x,int y) { if(x==0||y==0) return; if(f[x][y]==f[Brother[x]][y]) Find(Brother[x],y); else for(int i=1;i<=y;i++) if(f[x][y]==f[Brother[x]][i-1]+f[Child[x]][y-i]+Value[x]) { Find(Brother[x],i-1); Find(Child[x],y-i); ans[x]=true; return; } } int main() { memset(f,-1,sizeof(f)); memset(ans,0,sizeof(ans)); memset(Child,0,sizeof(Child)); memset(Brother,0,sizeof(Brother)); cin>>n>>m; for(int i=1;i<=n;i++) { cin>>x>>Value[i]; Brother[i]=Child[x]; Child[x]=i; } DFS(Child[0],m); cout<<f[Child[0]][m]<<endl; Find(Child[0],m); for(int i=1;i<=n;i++) if(ans[i]) cout<<i<<endl; return 0; }
|
P1306河流
题目描述:
几乎整个Byteland 王国都被森林和河流所覆盖。小点的河汇聚到一起,形成了稍大点的河。就这样,所有的河水都汇聚并流进了一条大河,最后这条大河流进了大海。这条大河的入海口处有一个村庄——Bytetown。
- 在Byteland国,有n个伐木的村庄,这些村庄都座落在河边。目前在Bytetown,有一个巨大的伐木场,它处理着全国砍下的所有木料。木料被砍下后,顺着河流而被运到Bytetown的伐木场。Byteland 的国王决定,为了减少运输木料的费用,再额外地建造k个伐木场。这k个伐木场将被建在其他村庄里。这些伐木场建造后,木料就不用都被送到Bytetown了,它们可以在 运输过程中第一个碰到的新伐木场被处理。显然,如果伐木场座落的那个村子就不用再付运送木料的费用了。它们可以直接被本村的伐木场处理。 注:所有的河流都不会分叉,形成一棵树,根结点是Bytetown。
国王的大臣计算出了每个村子每年要产多少木料,你的任务是决定在哪些村子建设伐木场能获得最小的运费。其中运费的计算方法为:每一吨木料每千米1分钱。
输入:
- 第一行包括两个数n(2<=n<=100),k(1<=k<=50,且k<=n)。n为村庄数,k为要建的伐木场的数目。除了Bytetown 外,每个村子依次被命名为 1,2,3……n,Bytetown被命名为0。
- 接下来n行,每行3个整数: wi——每年 i 村子产的木料的块数。(0<=wi<=10000)vi——离 i 村子下游最近的村子。(即 i 村子的父结点)(0<=vi<=n)di——vi 到 i 的距离(千米)。(1<=di<=10000)
- 保证每年所有的木料流到bytetown 的运费不超过2000,000,000分.50%的数据中n不超过20。
输出:
一行,最小的运费。
题解:
拿到这道题,其实我是想了好久的,尝试了各种各种DP的方法,但是一直GG,后来我突然想到了上一道题所用到的多叉树转二叉树的方法,然后用DFS遍历所有的点就可以了,最开始的时候我设置的状态 f[i][j][k]
表示的是对于当前节点i,一共使用j个工厂,然后k表示true或者false表示是否在当前进行建立工厂的最优值。但是发现又不行,因为距离是没有办法进行记录的,也就是说,这样的话记录下来没有办法确定木头的运输距离,也就是不知道距离它最近的工厂的位置,那么我们就更改一下状态的设置,我们表示出来 f[i][j][k]
表示的是对于第i个点,距离他最近有工厂的点为j,其中选择建造了k个工厂。然后在DFS传参数的时候加上距离dis一项,然后我们就可以进行愉快的计算了。
然后对于一个点x,我们有两种选择方法,也就是第一种,自己建造工厂和自己用别人的工厂,那么我们就进行两次DP,然后在其中求出最优值,如果不在这里进行建造工厂的话枚举i从0到k,表示自己的儿子负担i个,然后兄弟们负担 k-i
个。
第二种,如果自己亲自进行建造工厂,那么枚举i从0到k-1,然后表示自己的孩子一共建造i个,然后自己的兄弟们负责建造 k-i-1
个(减去的1表示点x已经建造了一个工厂)
以后看到题想不清楚了,然后仔细推一推看看自己的状态设置是不是少了什么,是不是有些是没有用的,可以用更加强大的进行代替掉,然后就是参数的传递上需要进行传递什么,也要仔细考虑清楚的!
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| #include<iostream> #include<cstring> #include<algorithm> #include<cstdio> #include<cmath> using namespace std; int n,m,Child[200],Brother[200],len=0,x,Get; int f[200][200][200],Weight[200],Distance[200]; bool vis[200]; int DFS(int x,int Root,int k,int dis) { if(x==-1||Root==-1) return 0; if(f[x][Root][k]!=Get) return f[x][Root][k]; int LHH=f[x][Root][k]; for(int i=0;i<=k;i++) { int First=DFS(Child[x],Root,i,dis+Distance[x]); int Second=DFS(Brother[x],Root,k-i,dis); int Value=(dis+Distance[x])*Weight[x]; LHH=min(LHH,First+Second+Value); } for(int i=0;i<k;i++) { int First=DFS(Child[x],x,i,0); int Second=DFS(Brother[x],Root,k-i-1,dis); LHH=min(LHH,First+Second); } f[x][Root][k]=LHH; return f[x][Root][k]; } int main() { memset(Child,-1,sizeof(Child)); memset(vis,false,sizeof(vis)); memset(Brother,-1,sizeof(Brother)); memset(f,120,sizeof(f)); cin>>n>>m; Get=f[0][0][0]; for(int i=1;i<=n;i++) { cin>>Weight[i]>>x>>Distance[i]; Brother[i]=Child[x]; Child[x]=i; } cout<<DFS(0,0,m,0)<<endl; return 0; }
|
P1512软件安装
题目描述:
- 现在我们的手头有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
- 第3行:V1,V2, … Vi, … ,Vn
- 第4行:D1,D2, … Di, … ,Dn
输出:
一个整数,代表最大价值。
题解:
这道题也是非常神奇的,一开始我是忽略掉了可能会出现环的情况,然后就开心的当成了选课,多叉转二叉,DFS直接开始深搜……然后就只拿到了40分(省选题暴力搞到40分也是蛮不错的)
然后仔细想了想,发现的确可能出现环的情况,在左右思索无助的时候还是忍不住的看了题解QAQ,然后就来总结一发。
这道题的其实可以通过题目上的一个条件很清楚的看出来,因为题目上已经说过了,如果没有安装他的依赖软件,那么这个软件也是可以安装的,但是权值为0,所以这里就决定了这个图并不是寻常的树规……
对于出现环的关系,也就是1依赖2,2依赖1,那么出现这种情况的时候,我们就可以将2和1看作是一个点了,如果选择这个点那么就是肯定里面的1和2都选择,如果不选择这个点,那么权值和重量就全都是0了。
如果里面只选择1的话,结果就和全部都不选是一样的。那么我们就可以用Tarjan算法求出所有的联通分量,然后将所有的联通分量当成一个点,然后重新建造一个二叉树,在重新建造的二叉树上进行DP,状态设置就是 f[i][j]
表示对于节点i在承受j重量下,所能达到的最大权值。(变量声明的有些多QAQ)
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| #include<iostream> #include<algorithm> #include<cstdio> #include<cmath> #include<cstring> using namespace std; struct Edge { int Next; int Point; }a[200]; int Value[200],Weight[200],n,m,len=0,This; int Father,Link_First[200],f[200][600],top=0; int ind=0,low[200],dfn[200],tot=0,Stack[200]; int Belong[200],Avalue[200],Aweight[200]; int Link_Second[200],Child[200],Brother[200]; bool Vis[200],Get[200],Find[200][600]; void Tarjan(int x) { int This=0; low[x]=dfn[x]=++ind; Stack[++top]=x; Vis[x]=true; for(int i=Link_First[x];i;i=a[i].Next) { int y=a[i].Point; if(!dfn[y]) { Tarjan(y); low[x]=min(low[x],low[y]); } else if(Vis[y]) low[x]=min(low[x],dfn[y]); } if(low[x]==dfn[x]) { tot++; while(This!=x) { This=Stack[top--]; Vis[This]=false; Belong[This]=tot; Avalue[tot]+=Value[This]; Aweight[tot]+=Weight[This]; } } } void Rebuild() { int Cut=0; for(int x=1;x<=n;x++) for(int i=Link_First[x];i;i=a[i].Next) if(Belong[a[i].Point]!=Belong[x]) { This=a[i].Point; Get[Belong[This]]=true; Brother[Belong[This]]=Child[Belong[x]]; Child[Belong[x]]=Belong[This]; } } int DFS(int x,int dis) { if(dis<=0||x<0) return 0; if(Find[x][dis]==true) return f[x][dis]; Find[x][dis]=true; for(int i=0;i<=dis-Aweight[x];i++) { int First=DFS(Child[x],i)+Avalue[x]; int Second=DFS(Brother[x],dis-i-Aweight[x]); f[x][dis]=max(f[x][dis],First+Second); } f[x][dis]=max(f[x][dis],DFS(Brother[x],dis)); return f[x][dis]; } int main() { ios::sync_with_stdio(false); memset(Find,false,sizeof(Find)); memset(Brother,-1,sizeof(Brother)); memset(Child,-1,sizeof(Child)); memset(Vis,false,sizeof(Vis)); memset(Get,false,sizeof(Get)); cin>>n>>m; for(int i=1;i<=n;i++) cin>>Weight[i]; for(int i=1;i<=n;i++) cin>>Value[i]; for(int i=1;i<=n;i++) { cin>>Father; a[i].Point=i; a[i].Next=Link_First[Father]; Link_First[Father]=i; } for(int i=0;i<=n;i++) if(!dfn[i]) Tarjan(i); Rebuild(); for(int i=1;i<=tot;i++) if(Get[i]==false) { Get[i]=true; Brother[i]=Child[0]; Child[0]=i; } cout<<DFS(0,m)<<endl; return 0; }
|