算法竞赛入门经典【第九章 动态规划】例题11-20

例题12 Another Crisis UVA - 12186 (树形dp)

题目链接
状态方程和转移: 将老板作为根,用 d p [ u ] dp[u] dp[u]表示命令会从 u u u向上级传导时的最少工人数,目标状态为 d p [ 0 ] dp[0] dp[0],起始状态为树的叶子,即工人们,直接返回1。将所有获得的孩子的 d p dp dp值从小到大进行排序,按比例取前 c n t cnt cnt个。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+10;
int n,k;
int dp[N];//dp[i]表示让i签字的最少工人数量
vector<int>son[N];
int DP(int u){
    int sz=son[u].size();
    if(!sz)return dp[u]=1;//工人
    if(dp[u])return dp[u];
    int cnt=sz*k/100;
    if(sz*k%100)cnt++;
    vector<int>son_dp;
    for(int i=0;i<sz;i++)son_dp.push_back(DP(son[u][i]));
    sort(son_dp.begin(),son_dp.end());
    for(int i=0;i<cnt;i++)dp[u]+=son_dp[i];
    return dp[u];
}
void solve(){
    memset(dp,0,sizeof dp);
    for(int i=0;i<=n;i++)son[i].clear();
    for(int i=1;i<=n;i++){
        int x;cin>>x;
        son[x].push_back(i);
    }
    cout<<DP(0)<<endl;
}
int main(){
    while(cin>>n>>k&&n&&k)
        solve();
    return 0;
}

例题13 Party at Hali-Bula UVA - 1220 (树形dp模板)

题目链接
状态设计: d p [ u ] [ 0 ] dp[u][0] dp[u][0]表示以u为根的树中来的最多人数,同时 u u u不来, d p [ u ] [ 1 ] dp[u][1] dp[u][1]表示以u为根的树中来的最多人数,且 u u u来。因为还需要判断答案的唯一性,再用 d p 2 [ u ] [ 0 ] / d p 2 [ u ] [ 1 ] dp2[u][0]/dp2[u][1] dp2[u][0]/dp2[u][1],为真表示对应的 d p [ u ] [ 0 ] dp[u][0] dp[u][0]不唯一。

#include<bits/stdc++.h>
using namespace std;
const int N=210;
map<string,int>ma;
int n;
vector<int>son[N];
int dp[N][2],cnt;
bool dp2[N][2];//如果是1表示方案不唯一
void dfs(int u){
    if(!son[u].size()){
        dp[u][1]=1,dp[u][0]=0;return;
    }
    dp[u][0]=0,dp[u][1]=1;
    int fl=0,maxx=0;
    for(int i=0;i<son[u].size();i++){
        int v=son[u][i];
        dfs(v);
        //更新1
        if(dp2[v][0])dp2[u][1]=1;
        dp[u][1]+=dp[v][0];
        //更新0
        dp[u][0]+=max(dp[v][0],dp[v][1]);
        if((dp[v][0]==dp[v][1])||(dp[v][0]>dp[v][1]&&dp2[v][0])||(dp[v][1]>dp[v][0]&&dp2[v][1]))
            dp2[u][0]=1;
    }
}
void solve(){
    ma.clear();
    for(int i=0;i<=n;i++)son[i].clear();
    memset(dp,-1,sizeof dp);
    memset(dp2,0,sizeof dp2);
    string tmp;cin>>tmp;
    ma[tmp]=cnt=1;//1是根
    for(int i=1;i<n;i++){
        string u,v;cin>>u>>v;
        if(!ma[u])ma[u]=++cnt;
        if(!ma[v])ma[v]=++cnt;
        son[ma[v]].push_back(ma[u]);
    }
    son[0].push_back(1);
    dfs(0);
    cout<<dp[0][0]<<' ';
    if(dp2[0][0])cout<<"No"<<endl;
    else cout<<"Yes"<<endl;
}
int main(){
    while(cin>>n&&n)
        solve();
}

例题14 Perfect Service UVA - 1218(树形dp)

题目链接
题目大意: 是一棵树,每个点只能和恰好一个服务站相连,但是服务站本身可以和服务站和普通站点连接,求最少需要建立的服务站个数。
状态设计和转移: 设当前节点 u u u的子节点是 v v v,首先随便选节点1作为根,将无根树转化为有根树。接下来给节点分几种情况:
0. 本身不是服务站,父亲是服务站:孩子从1转移来,答案是 ∑ d p [ v ] [ 1 ] \sum{dp[v][1]} dp[v][1]

  1. 本身不是服务站,父亲不是服务站:恰好有一个孩子是服务站,假设孩子 k k k是服务站,答案是 ∑ v ! = k d p [ v ] [ 1 ] + d p [ k ] [ 2 ] \sum_{v!=k}{dp[v][1]}+dp[k][2] v!=kdp[v][1]+dp[k][2]我们其实可以直接计算 ∑ d p [ v ] [ 1 ] \sum{dp[v][1]} dp[v][1],再在 u u u的孩子中选一个孩子 k k k使得 d p [ k ] [ 2 ] − d p [ k ] [ 1 ] dp[k][2]-dp[k][1] dp[k][2]dp[k][1]的值最小,答案即是 ∑ d p [ v ] [ 1 ] + d p [ k ] [ 2 ] − d p [ k ] [ 1 ] \sum{dp[v][1]}+dp[k][2]-dp[k][1] dp[v][1]+dp[k][2]dp[k][1]
  2. 本身是服务站:孩子从0和2转移来,答案是 ∑ m i n ( d p [ v ] [ 0 ] , d p [ v ] [ 2 ] \sum{min(dp[v][0],dp[v][2]} min(dp[v][0],dp[v][2]

然后注意设置最大值为 n n n而不要设为 i n f = 0 x 3 f 3 f 3 f 3 f inf=0x3f3f3f3f inf=0x3f3f3f3f,因为有的孩子可能无法转移过来,很多 i n f inf inf相加就爆了,或者可以开 l o n g l o n g long long longlong试试看。(wa得一脸懵逼)

#include<bits/stdc++.h>
#define LL long long
#define min3(a,b,c) min(min(a,b),c)
using namespace std;
const int N=1E4+7;
vector<int>son[N];
int n,dp[N][3];
void dfs(int u,int fa){
    dp[u][0]=0,dp[u][2]=1;
    int dif=n;
    for(int i=0;i<son[u].size();i++){
        int v=son[u][i];
        if(v==fa)continue;
        dfs(v,u);
        dp[u][0]+=dp[v][1];
        dp[u][2]+=min(dp[v][0],dp[v][2]);
        dif=min(dif,dp[v][2]-dp[v][1]);
    }
    dp[u][1]=dp[u][0]+dif;
}
int main(){
    while(cin>>n){
        for(int i=0;i<=n;i++)dp[i][0]=dp[i][1]=dp[i][2]=n;
        for(int i=0;i<=n;i++)son[i].clear();
        son[1].push_back(0);
        for(int i=1;i<n;i++){
            int x,y;cin>>x>>y;
            son[x].push_back(y),son[y].push_back(x);
        }
        dfs(1,0);
        cout<<min(dp[1][1],dp[1][2])<<endl;
        int tmp;cin>>tmp;
        if(tmp==-1)break;
    }
    return 0;
}

例题15 Headmaster’s Headache UVA - 10817 (状态压缩,记忆化搜索)

题目链接
状态设计: d p [ i ] [ s 1 ] [ s 2 ] dp[i][s1][s2] dp[i][s1][s2]表示前 i i i个人之后的最少花费( i i i从0开始编号), s 1 s1 s1表示有一个教师教授的课程集合, s 2 s2 s2表示有两个教师教授的课程集合。起始状态是 s 1 , s 2 s1,s2 s1,s2均为0,且一个人都没有考虑到,即 d p [ 0 ] [ 0 ] [ 0 ] dp[0][0][0] dp[0][0][0]
状态转移:
s0:当前教师可以教的课程应该从集合里剔除。
s1:删去原来有一个人教的并且当前教师还会教的(因为这部分转移向s2); 同时加上原来没有人教的而当前教师会的。
s2:保留原来有两个人教; 再加上原来有一个人教并且当前的教师会教的。

其实写到现在还是对状态压缩不是特别理解,大概是用某个维度维护一个集合,进行集合的转移,直到想要的终态。

#include<bits/stdc++.h>
using namespace std;
const int N=200,S=8,inf=0x3f3f3f3f;
int dp[N][1<<S][1<<S],st[N],c[N];
int n,m,s;
int DP(int i,int s0,int s1,int s2){
    if(i==n+m)return (s2==((1<<s)-1))?0:inf;
    int &ans=dp[i][s1][s2];
    if(ans>=0)return ans;
    ans=inf;
    if(i>=m)ans=DP(i+1,s0,s1,s2);
    int m0=s0&st[i],m1=s1&st[i];
    s0^=m0,s1=(s1^m1)|m0,s2|=m1;
    return ans=min(ans,c[i]+DP(i+1,s0,s1,s2));
}
int main(){
    string str;
    while(getline(cin,str)){
        stringstream ss(str);
        ss>>s>>m>>n;
        if(s==0)break;
        for(int i=0;i<n+m;i++){
            getline(cin,str);
            stringstream ss(str);
            ss>>c[i];
            st[i]=0;
            int x;
            while(ss>>x)st[i]|=(1<<(x-1));
        }
        memset(dp,-1,sizeof dp);
        cout<<DP(0,(1<<s)-1,0,0)<<endl;
    }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值