USACO 2002 February

4 篇文章 0 订阅

今天刷了刷USACO 2002 February,从年份上看题目已经很老了。。可是我还是刷不动。。。


只能说是自己太弱了   还是需要多加练习才是啊。


今天只做了三道题。。其中有一道题是刚才补得。。学习了一下树形dp。。实在是惭愧的很。。


POJ 1949 - Chores(这是我做的这套题里的第一道题目)

题目大意:给你N个任务,这些任务是可以并行开始的,但是部分任务需要完成它的前置任务后才能去做,问你完成所有任务的最短时间

题目分析:可以把每个任务当做顶点,再从前置任务到后置任务之间连边,就形成了一个AOV(Active on vertex)网,然后数据结构课上老师说过,我们可以先对这个AOV网进行拓扑排序,然后再对这个网进行dp,最后求得整个问题的解。

dp[i]表示完成第i个任务需要的最短时间

则有 dp[son[i]] = min{ dp[i]+time[son[i]] }

写的时候竟然发现拓扑排序不会写!!好吧。。看来真得需要多练练了。

这道题目我偷了个懒,我把所有没有前置任务的任务都给了个前置任务:0号任务,我把所有没有后置任务的任务都给了个后置任务:N+2号任务,这样就能够保证只形成了一个AOV网,不用去分开计算每个AOV网的值。

代码写的略挫。。还WA了几记在初始化上。。自己真是菜。。

#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;

int N,time[10100];
vector<int> G[10100];
int vis[10100],topo[10100],ptr_t;
int dp[10100];

//此处是拓扑排序用到的dfs
bool dfs(int u){
    vis[u] = -1;
    for(int v=0;v<G[u].size();v++){
        if(vis[G[u][v]]<0 ) return false;
        if(!vis[G[u][v]]) dfs(G[u][v]);
    }
    topo[--ptr_t] = u;
    vis[u] = 1;
    return true;
}

//拓扑排序的启动函数,结果存在topo数组里
void topo_sort(){
    for(int i=0;i<N;i++){
        if(!vis[i]){
            dfs(i);
        }
    }
}

int main()
{
    while(scanf("%d",&N)!=EOF){
        memset(vis,0,sizeof(vis));
        memset(dp,0,sizeof(dp));
        memset(topo,0,sizeof(topo));
        memset(time,0,sizeof(time));
        for(int i=0;i<10100;i++)G[i].clear();
        for(int i=1;i<=N;i++){
            int nn;
            scanf("%d%d",&time[i],&nn);
            for(int j=0;j<nn;j++){
                int t;
                scanf("%d",&t);
                G[t].push_back(i);
            }
            if(nn==0){
                G[0].push_back(i);  //给所有没有前置任务的任务指定前置任务0
            }
        }
        for(int i=1;i<=N;i++){
            //给所有没有后置任务的任务指定后置任务N+1
            if(G[i].size()==0){
                G[i].push_back(N+1); 
            }
        }
        N+=2; //多了两个任务
        ptr_t = N;
        topo_sort();
        dp[0] = 0;
        for(int i=0;i<N;i++){
            int u = topo[i];
            for(int w = 0; w<G[u].size();w++){
                int v = G[u][w];
                dp[v] = max(dp[v],dp[u]+time[v]);
            }
        }
        printf("%d\n",dp[N-1]);
    }
    return 0;
}


POJ 1951 - Extra Krunch

题目大意:给你一个字符串,让你去除重复出现的字母以及AEIOU这5个字母,还有多余的空格。

题目分析:题目很简单,照着做就行了,详见代码,但是要注意一点:后面如果出现了句点,那么之前是不可以有空格的。

代码:

#include <cstdio>
#include <vector>
#include <cstring>
#include <cctype>
using namespace std;

char s[1000];
bool has[30];
char ans[1000];

int getNum(char c){
    if(c<='Z'&&c>='A') return c-'A';
    return 26;
}

void init(){
    has['A'-'A'] = has['E'-'A'] = has['I'-'A'] =
    has['O'-'A'] = has['U'-'A'] = true;
}

int main(){
    while(gets(s)){
        memset(has,0,sizeof(has));
        memset(ans,0,sizeof(ans));
        init();
        int ptr = 0;
        int len = strlen(s);
        bool flag = true;
        for(int i=0;i<len;i++){
            int id;
            if(isalpha(s[i])){
                id = getNum(s[i]);
                if(!has[id]){
                    has[id] = true;
                    ans[ptr++] = s[i];
                    flag = false;
                }
            } else if(isspace(s[i])){
                if(!flag){
                    ans[ptr++] = s[i];
                }
                flag = true;
            } else {
                ans[ptr++] = s[i];
                flag = false;
            }
        }
        if(ans[ptr-1]=='.'&&ans[ptr-2]==' '){
            ans[ptr-2] = '.';
            ans[ptr-1] = '\0';
            ptr--;
        }
        for(int i=ptr-1;i>=0;i--){
            if(ans[i]!=' ') break;
            ans[i] = '\0';
        }
        puts(ans);
    }
    return 0;
}


POJ 1947 - Rebuilding Roads

题目大意:给你一棵有n个节点的树,问你从中获得一棵有p个节点的子树最少需要去掉几条边。

题目分析:这是我做的第一道树形dp的题目,之前从来都没做过,也是看了题解才明白的。

我们定义dp状态:dp[u][j]表示以根节点为u的子树中找出j个节点的子树需要去掉的最少的边。

那么这就有个dp转移方程了,我们假设v是u的儿子节点,那么dp[u][j] = dp[v1][k1]+dp[v2][k2]+dp[v3][k3]+...+dp[vn][kn],其中k1+k2+k3+...+kn = j

这个式子显然需要优化,于是有:dp[u][j] = dp[v1][k1]+dp[v2][k2]+...+dp[v i-1][k i-1]+dp[v i+1][k i+1]+...+dp[vn][kn]+dp[vi][ki],其中k1+k2+...+k i-1+k i+1 + kn = j-ki

于是有dp[u][j] = dp[v1][k1]+dp[v2][k2]+...+dp[v i-1][k i-1]+dp[vi][0]+dp[v i+1][k i+1]+...+dp[vn][kn]+dp[vi][ki]-dp[vi][0]

因为dp[u][j-k] = dp[v1][s1]+dp[v2][s2]+...dp[vn][sn] 其中s1+s2+...+sn=j-k,因此上式可以表示成为dp[u][j] = dp[u][j-k]+dp[vi][ki]-dp[vi][0]

因为dp[vi][0]始终为1(我们不要这整棵子树,只需要剪掉vi与u连的边就行了

因此整个dp方程为:dp[u][j] = dp[u][j-k]+dp[vi][ki]-1


见代码:

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
#define INF 0x3f3f3f3f

int dp[200][200];
vector<int> G[200];
int n,p;

void dfs(int u){
    if(G[u].size()==0){
        // 叶子节点上,如果只保留叶子节点则不需要剪掉任何一条边
        dp[u][1] = 0;
        return;
    }
    for(int i = 0;i < G[u].size(); i++){
        int v = G[u][i];
        dfs(v);
    }
    dp[u][1] = G[u].size();
    for(int i=0;i<G[u].size();i++){
        int v = G[u][i];
        for(int j=p;j>=2;j--){
            for(int k=1;k<=j;k++){
                dp[u][j] = min(dp[u][j],dp[u][j-k]+dp[v][k]-1);
            }
        }
    }
}

int main(){
    while(scanf("%d%d",&n,&p)!=EOF){
        for(int i=0;i<200;i++) G[i].clear();
        memset(dp,INF,sizeof(dp));
        for(int i=0;i<n-1;i++){
            int a,b;
            scanf("%d%d",&a,&b);
            G[a].push_back(b);
        }
        dfs(1);
        //答案是首先整棵树的根节点得到的值
        int ans = dp[1][p];
        //再来是每个以每个节点保留p个节点要剪掉的边,另外需要剪掉该节点与其父亲节点之间的边
        for(int i=2;i<=n;i++){
            ans = min(ans,dp[i][p]+1);
        }
        printf("%d\n",ans);
    }
    return 0;
}






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值