1. 程式人生 > 實用技巧 >二叉蘋果樹 && 選課(樹上揹包)

二叉蘋果樹 && 選課(樹上揹包)

二叉蘋果樹:https://ac.nowcoder.com/acm/problem/50505

有一棵二叉蘋果樹,如果數字有分叉,一定是分兩叉,即沒有隻有一個兒子的節點。這棵樹共N個節點,標號1至N,樹根編號一定為1。
我們用一根樹枝兩端連線的節點編號描述一根樹枝的位置。一棵有四根樹枝的蘋果樹,因為樹枝太多了,需要剪枝。但是一些樹枝上長有蘋果,給定需要保留的樹枝數量,求最多能留住多少蘋果。

輸入
5 2 1 3 1 1 4 10 2 3 20 3 5 20
輸出
21

程式碼:

#include<iostream>
#include<algorithm>
#include<cstring>
#include
<cstdio> #include<sstream> #include<vector> #include<stack> #include<deque> #include<cmath> #include<map> #include<queue> #include<bitset> //#include<hash_map> #define sd(x) scanf("%d",&x) #define lsd(x) scanf("%lld",&x) #define ms(x,y) memset(x,y,sizeof x) #define
fu(i,a,b) for(int i=a;i<=b;i++) #define fd(i,a,b) for(int i=a;i>=b;i--) #define all(a) a.begin(),a.end() #define lson l,mid,rt<<1 #define rson mid+1,r,rt<<1|1 using namespace std; //using namespace __gnu_cxx; typedef long long ll; typedef unsigned long long ull; typedef long double ld;
const int maxn=1e2+79; const int mod=998244353; const int INF=1e9+7; const double pi=acos(-1); ll dp[maxn][maxn],a[maxn]; vector<od> son[maxn]; int n,k; void dfs(int x,int fa) { for(od y:son[x]) { int nxt=y.to,len=y.len; if(nxt==fa) continue; dfs(nxt,x); //dp:第i個點為根,連著j條根 fd(j,k,1) { fu(t,0,j-1) //左+右組合 dp[x][j]=max(dp[nxt][t]+dp[x][j-t-1]+len,dp[x][j]); } } } int main() { sd(n);sd(k); fu(i,1,n-1) { int u,v,w;sd(u);sd(v);sd(w); son[u].push_back(od{v,w}); son[v].push_back(od{u,w}); } dfs(1,0); int ans=dp[1][k]; printf("%lld\n",ans); return 0; }

選課:https://ac.nowcoder.com/acm/problem/51179

學校實行學分制。
每門的必修課都有固定的學分,同時還必須獲得相應的選修課程學分。
學校開設了 N 門的選修課程,每個學生可選課程的數量 M 是給定的。
學生選修了這 M 門課並考核通過就能獲得相應的學分。
在選修課程中,有些課程可以直接選修,有些課程需要一定的基礎知識,必須在選了其他的一些課程的基礎上才能選修。
例如《Windows程式設計》必須在選修了《Windows操作基礎》之後才能選修。
我們稱《Windows操作基礎》是《Windows程式設計》的先修課。
每門課的直接先修課最多隻有一門。
兩門課可能存在相同的先修課。
你的任務是為自己確定一個選課方案,使得你能得到的學分最多,並且必須滿足先修條件。
假定課程之間不存在時間上的衝突。 輸入描述:     輸入檔案的第一行包括兩個整數N、M(中間用一個空格隔開)其中1≤N≤300,1≤M≤N,1≤N≤300,1≤M≤N 接下來N行每行代表一門課,課號依次為1,2,…,N。
每行有兩個數(用一個空格隔開),第一個數為這門課先修課的課號(若不存在先修課則該項為0),第二個數為這門課的學分。
學分是不超過10的正整數。
輸入:
7 4
2 2 0 1 0 4 2 1 7 1 7 6 2 2
輸出:
13
程式碼:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<sstream>
#include<vector>
#include<stack>
#include<deque>
#include<cmath>
#include<map>
#include<queue>
#include<bitset>
//#include<hash_map>
#define sd(x) scanf("%d",&x)
#define lsd(x) scanf("%lld",&x)
#define ms(x,y) memset(x,y,sizeof x)
#define fu(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define all(a) a.begin(),a.end()
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
using namespace std;
//using namespace __gnu_cxx;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int maxn=300+79;
const int mod=998244353;
const ll INF=0x7f7f7f7f;
const double pi=acos(-1);
ll dp[maxn][maxn],sco[maxn];
vector<int> to[maxn];
int n,m;
void dfs(int x)
{
    dp[x][0]=0;
    if(!to[x].empty())
    {
        for(int y:to[x])
        {
            dfs(y);
            fd(j,m,0) //總共選j個,01揹包倒序
                fu(k,0,m)
                if(j-k>=0)
                dp[x][j]=max(dp[x][j],dp[x][k]+dp[y][j-k]);
        }
    }
    if(x!=0)//不是0的話,都要加上x點的學分
    fd(i,m,1) dp[x][i]=dp[x][i-1]+sco[x]; 
}
int main()
{
    sd(n);sd(m);
    fu(i,1,n)
    {
        int x,score;
        sd(x);sd(score);
        sco[i]=score;
        to[x].push_back(i);
    }
    //建一個超級點0
    dfs(0);
    printf("%lld\n",dp[0][m]);
    return 0;
}

Note:

兩題都是樹上揹包。兩題f[u][j]都是表示以u為根,選j個邊(點)的最優解。

第一題:選邊使邊權和最小。

• f[u][j] = max(f[u][k] + f[v][j – k - 1] + W) • v分別是u的兒子,w為u到v邊上的蘋果數目(也即這條邊權值), k屬於[0, j]。 第二題:選點使點權最大。 • f[u][j] = max(f[u][k] + f[v][j – k ]) • v分別是u的兒子, k屬於[0, j]。 •最後(因為以x為根,點x一定要選上)
//把每個點x的學分都加上去,逆序是為了讓一個點的值不被重複加上去
for(int i=m;i>=1;i--)
      dp[x][i]=dp[x][i-1]+val[x];