您的位置:首页 > Web前端

USACO 2002 February

2014-08-29 20:32 344 查看
今天刷了刷USACO 2002 February,从年份上看题目已经很老了。。可是我还是刷不动。。。

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

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

POJ 1949 - [b]Chores(这是我做的这套题里的第一道题目)[/b]
题目大意:给你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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: