ID
|
Origin
|
Title
| ||
---|---|---|---|---|
167 / 465 | Problem A | HDU 1024 | Max Sum Plus Plus | |
234 / 372 | Problem B | HDU 1029 | Ignatius and the Princess IV | |
161 / 259 | Problem C | HDU 1069 | Monkey and Banana | |
104 / 188 | Problem D | HDU 1074 | Doing Homework | |
153 / 248 | Problem E | HDU 1087 | Super Jumping! Jumping! Jumping! | |
127 / 215 | Problem F | HDU 1114 | Piggy-Bank | |
151 / 428 | Problem G | HDU 1176 | 免费馅饼 | |
118 / 199 | Problem H | HDU 1260 | Tickets | |
167 / 292 | Problem I | HDU 1257 | 最少拦截系统 | |
102 / 198 | Problem J | HDU 1160 | FatMouse's Speed | |
43 / 139 | Problem K | POJ 1015 | Jury Compromise | |
109 / 183 | Problem L | POJ 1458 | Common Subsequence | |
69 / 208 | Problem M | POJ 1661 | Help Jimmy | |
96 / 160 | Problem N | POJ 2533 | Longest Ordered Subsequence | |
79 / 135 | Problem O | POJ 3186 | Treats for the Cows | |
70 / 169 | Problem P | HDU 1078 | FatMouse and Cheese | |
67 / 137 | Problem Q | HDU 2859 | Phalanx | |
81 / 164 | Problem R | POJ 3616 | Milking Time | |
56 / 145 | Problem S | POJ 3666 | Making the Grade |
167 / 465 Problem A HDU 1024 Max Sum Plus Plus
d.已知有n个数,求m段不相交的子段权值之和最大
s.本题的大致意思为给定一个数组,求其分成m个不相交子段和最大值的问题。
设Num为给定数组,n为数组中的元素总数,Status[i][j]表示前i个数在选取第i个数的前提下分成j段的最大值,其中1<=j<=i<=n && j<=m,状态转移方程为:
Status[i][j]=Max(Status[i-1][j]+Num[i],Max(Status[0][j-1]~Status[i-1][j-1])+Num[i])
乍看一下这个方程挺吓人的,因为题中n的限定范围为1~1,000,000而m得限定范围没有给出,m只要稍微大一点就会爆内存。但仔细分析后就会发现Status[i][j]的求解只和Status[*][j]与Status[*][j-1]有关所以本题只需要两个一维数组即可搞定状态转移。
在进行更进一步的分析还会发现其实Max(Status[0][j-1]~Status[i-1][j-1])根本不需要单独求取。在求取now_Status(保存本次状态的数组)的过程中即可对pre_Status(保存前一次状态的数组)进行同步更新。
/* 状态dp[i][j] 由前j个数(包含第j个数),组成i组的和的最大值。 决策: 第j个数,是在第包含在第i组里面,还是自己独立成组。 方程 dp[i][j]=Max(dp[i][j-1]+a[j] , max( dp[i-1][k] ) + a[j] ) 0<k<j 空间复杂度,m未知,n<=1000000, 继续滚动数组。 时间复杂度 n^3. n<=1000000. 显然会超时,继续优化。 max( dp[i-1][k] ) 就是上一组 0....j-1 的最大值。我们可以在每次计算dp[i][j]的时候记录下前j个 的最大值 用数组保存下来 下次计算的时候可以用,这样时间复杂度为 n^2. */ #include<iostream> #include<stdio.h> using namespace std; #define MAXN 1000005 int s[MAXN]; int dp[MAXN];//dp[j]相当于dp[i][j] int ma[MAXN];//ma[j]相当于dp[i-1][0]...dp[i-1][j]中的最大值 int main(){ int m,n; int _ma;//分为i组时的最大值 while(~scanf("%d%d",&m,&n)){ for(int i=1;i<=n;++i){ scanf("%d",&s[i]); ma[i]=0;//这样的初始化,好像比直接memset(ma,0,sizeof(ma))省点时间。 } ma[0]=0; for(int i=1;i<=m;++i){ dp[i]=ma[i-1]+s[i];//要分为i组,j最小为i,即dp[i][i]的初始化。它的值也就是前面i个数的和了。 _ma=dp[i];//分为i组时的最大值初始化 for(int j=i+1;j<=n;++j){ dp[j]=max(dp[j-1]+s[j],ma[j-1]+s[j]); ma[j-1]=_ma;//上一次的值用完了,更新为这一次的值 _ma=max(_ma,dp[j]);//更新分为i组时的最大值 } } printf("%d ",_ma); } return 0; }
234 / 372 Problem B HDU 1029 Ignatius and the Princess IV
d.找主元素
s.主元素即在数列中出现次数多于n/2的元素
我们很容易的看出来,在一个序列中如果去掉2个不同的元素,
那么原序列中的多元素,在新的序列中还是多元素,
因此我们只要按照序列依次扫描,先把t赋值给result,
增加个计数器,cnt = 1;然后向右扫描,
如果跟result相同,则cnt++,不同,那么cnt --,
这个真是我们从上面那个结论里得出的,一旦cnt == 0了,
那么必定c不是多元素,这个时候把t赋值为result,cnt = 1;,
重复该过程,知道结束,这个时候,result就是多元素,
这个的时间复杂度为n,该题本来可以用数组保存每个元素,
然后递归上述过程,可是,用数组超内存,
因此我们可以直接按照上述过程计算
#include<iostream> #include<stdio.h> using namespace std; int main(){ int N; int t; int i; int ans; int num; while(~scanf("%d",&N)){ num=0; for(i=0;i<N;++i){ scanf("%d",&t); if(num==0){ ans=t; ++num; } else{ if(t==ans){ ++num; } else{ --num; } } } printf("%d ",ans); } return 0; }
161 / 259 Problem C HDU 1069 Monkey and Banana
d.n种箱子,长宽高分别为x,y,z,箱子有任意个,可以任意旋转,小箱子可以叠在大箱子上(上面的长宽要小于下面的),求可以叠起来的最大高度
s.把箱子的几种旋转情况分开,解决放任意多个的问题,然后相当于最长上升子序列,O(n^2)
#include<iostream> #include<stdio.h> #include<algorithm> using namespace std; const int MAXN=200; struct Node{ int a,b,high; int dp;//该箱子在最下面时的最大高度 }block[MAXN]; int tot; void addBlock(int high,int a,int b){ block[tot].high=high; block[tot].a=a; block[tot].b=b; block[tot].dp=high; ++tot; } bool cmp(Node a,Node b){ if(a.a!=b.a)return a.a<b.a; return a.b<b.b; } int main(){ int n; int x,y,z; int i; int j; int ma_high; int ca=0; while(~scanf("%d",&n)){ if(n==0){ break; } tot=0; for(i=0;i<n;++i){//把给出的block放置的所有可能放进block[]中,这样就可以解决有无限块的问题 scanf("%d%d%d",&x,&y,&z); if(x==y&&y==z){//3个相等 x,x,x addBlock(x,x,x); } else if(x==y){//2个相等 x,x,z addBlock(z,x,x); addBlock(x,x,z); addBlock(x,z,x); } else if(x==z){// x,x,y addBlock(y,x,x); addBlock(x,x,y); addBlock(x,y,x); } else if(y==z){// y,y,x addBlock(x,y,y); addBlock(y,y,x); addBlock(y,x,y); } else{//都不相等 x,y,z addBlock(x,y,z); addBlock(x,z,y); addBlock(y,x,z); addBlock(y,z,x); addBlock(z,x,y); addBlock(z,y,x); } } sort(block,block+tot,cmp); ma_high=0; for(i=0;i<tot;++i){ for(j=0;j<i;++j){ if(block[i].a>block[j].a&&block[i].b>block[j].b){ block[i].dp=max(block[i].dp,block[j].dp+block[i].high); } } if(block[i].dp>ma_high){ ma_high=block[i].dp; } } printf("Case %d: maximum height = %d ",++ca,ma_high); } return 0; }
104 / 188 Problem D HDU 1074 Doing Homework
算法核心:状态压缩DP
大意:
有n门课程作业,每门作业的截止时间为D,需要花费的时间为C,若作业不能按时完成,每超期1天扣1分。
这n门作业按课程的字典序先后输入
问完成这n门作业至少要扣多少分,并输出扣分最少的做作业顺序
PS:达到扣分最少的方案有多种,请输出字典序最小的那一组方案
分析:
n<=15,由题意知,只需对这n份作业进行全排列,选出扣分最少的即可。
用一个二进制数存储这n份作业的完成情况,第1.。。。n个作业状况分别
对应二进制数的第0,1.。。。。,n-1位则由题意,故数字上限为2^n
其中 2^n-1即为n项作业全部完成,0为没有作业完成。。。
用dp[i]记录完成作业状态为i时的信息(所需时间,前一个状态,最少损失的分数)。
递推条件如下
1.状态a能做第i号作业的条件是a中作业i尚未完成,即a&i=0。
2.若有两个状态dp[a],dp[b]都能到达dp[i],那么选择能使到达i扣分小的那一条路径,若分数相同,转入3
3.这两种状态扣的分数相同,那么选择字典序小的,由于作业按字典序输入,故即dp[i].pre = min(a,b);
初始化:dp[0].cost = 0;dp[0].pre=-1;dp[0].reduced = 0;
最后dp[2^n-1].reduced即为最少扣分,课程安排可递归的输出
/* HDU1074 */ #include<stdio.h> #include<string.h> const int MAXN=1<<16; struct Node { int cost;//所需时间 int pre;//前一状态 int reduced;//最少损失的分数 }dp[MAXN]; bool visited[MAXN]; struct Course { int deadtime;//截止日期 int cost;//所需日期 char name[201]; }course[16]; void output(int status) { int curjob=dp[status].pre^status; int curid=0; curjob>>=1; while(curjob) { curid++; curjob>>=1; } if(dp[status].pre!=0) { output(dp[status].pre); } printf("%s ",course[curid].name); } int main() { int T,n,i,j; scanf("%d",&T); while(T--) { scanf("%d",&n); int upper=1<<n; int dayupper=0; for(i=0;i<n;i++) { scanf("%s%d%d",&course[i].name,&course[i].deadtime,&course[i].cost); dayupper+=course[i].cost; } memset(visited,false,sizeof(visited)); dp[0].cost=0; dp[0].pre=-1; dp[0].reduced=0;//dp[0]是指所有作业都没有做的状态 visited[0]=true; int work; int tupper=upper-1;//tupper表示成二进制数是n个1的,表示所有的作业都完成了 for(j=0;j<tupper;j++)//遍历所有状态 { for(work=0;work<n;work++) { int cur=1<<work; if((cur&j)==0)//该项作业尚未做过 { int curtemp=cur|j; int day=dp[j].cost+course[work].cost; dp[curtemp].cost=day; int reduce=day-course[work].deadtime; if(reduce<0)reduce=0; reduce+=dp[j].reduced; if(visited[curtemp])//该状态已有访问信息 { if(reduce<dp[curtemp].reduced) { dp[curtemp].reduced=reduce; dp[curtemp].pre=j; } //else if(reduce==dp[curtemp].reduced) //扣分相同,取字典序小的那一个,由于这里j是按从小到达搜索的,默认已是按字典序,不需再处理 // { // if(dp[curtemp].pre>j) // dp[curtemp].pre = j; // } } else //没有访问过 { visited[curtemp]=true; dp[curtemp].reduced=reduce; dp[curtemp].pre=j; } } } } printf("%d ",dp[tupper].reduced); output(tupper);//递归输出 } }
大意:
有n门课程作业,每门作业的截止时间为D,需要花费的时间为C,若作业不能按时完成,每超期1天扣1分。
这n门作业按课程的字典序先后输入
问完成这n门作业至少要扣多少分,并输出扣分最少的做作业顺序
PS:达到扣分最少的方案有多种,请输出字典序最小的那一组方案
分析:
n<=15,由题意知,只需对这n份作业进行全排列,选出扣分最少的即可。
用一个二进制数存储这n份作业的完成情况,第1.。。。n个作业状况分别
对应二进制数的第0,1.。。。。,n-1位则由题意,故数字上限为2^n
其中 2^n-1即为n项作业全部完成,0为没有作业完成。。。
用dp[i]记录完成作业状态为i时的信息(所需时间,前一个状态,最少损失的分数)。
递推条件如下
1.状态a能做第i号作业的条件是a中作业i尚未完成,即a&i=0。
2.若有两个状态dp[a],dp[b]都能到达dp[i],那么选择能使到达i扣分小的那一条路径,若分数相同,转入3
3.这两种状态扣的分数相同,那么选择字典序小的,由于作业按字典序输入,故即dp[i].pre = min(a,b);
初始化:dp[0].cost = 0;dp[0].pre=-1;dp[0].reduced = 0;
最后dp[2^n-1].reduced即为最少扣分,课程安排可递归的输出
/*分析:对于n种家庭作业,全部做完有n!种做的顺序 但是n!太大了,且对于完成作业1,2,3和1,3,2和2,1,3和3,2,1和3,1,2来说 完成它们消耗的天数一定是一样的,只是完成的顺序不同从而扣的分不同 所以可以将完成相同的作业的所有状态压缩成一种状态并记录扣的最少分即可 即:状态压缩dp 对于到达状态i,从何种状态到达i呢?只需要枚举所有的作业 假如对于作业k,i中含有作业k已完成,那么i可以由和i状态相同的状态仅仅是k未完成的 状态j=i-(1<<k)来完成k到达,并且j一定比i小,如果状态从0枚举到2^n-1那么j一定是在i之前已经计算过的 */ #include <bits/stdc++.h> #define INF 0x3f3f3f3f #define MAX ( (1<<15)+10 ) typedef long long LL; using namespace std; ///课程信息 可以封装成结构体 int deadT[20],cost[20]; char s[20][110]; ///DP的中间状态信息,可以封装成结构体 int dp[MAX],tim[MAX],pre[MAX];///dp[i]记录到达状态i扣的最少分,t是在dp[i](扣除最小分)相应的花去多少天了 void output(int x){ if(!x)return; output(x-(1<<pre[x])); printf("%s ",s[pre[x]]); } int main(){ int T,n; scanf("%d",&T); while(T--){ scanf("%d",&n); for(int i=0;i<n;++i)scanf("%s%d%d",&s[i],&deadT[i],&cost[i]); int bit=1<<n ; for(int i=1;i<bit;++i)///枚举到达状态i { dp[i]=INF;///初始化到达状态i的扣分 for(int j=n-1;j>=0;--j){///由于输入时按字符大小输入,而每次完成j相当于把j放在后面完成 ///这里下面判断是dp[i]>dp[i-temp]+score,所以是n-1开始 ///如果下面判断是dp[i]>=dp[i-temp]+score则从0开始 int temp=1<<j; if(!(i&temp)) continue;///状态i不存在作业j完成则不能通过完成作业j到达状态i int score=tim[i-temp]+cost[j]-deadT[j];///i-temp表示没有完成j的那个状态 score是在当前情形下完成j所扣除的分数 if(score<0)score=0;///完成j被扣分数最小是0 if(dp[i]>dp[i-temp]+score){ dp[i]=dp[i-temp]+score; tim[i]=tim[i-temp]+cost[j];///到达状态i花费的时间 pre[i]=j;///到达状态i的前驱,为了最后输出完成作业的顺序 } } } printf("%d ",dp[bit-1]); output(bit-1);///输出完成作业的顺序 } return 0; }
153 / 248 Problem E HDU 1087 Super Jumping! Jumping! Jumping!
d.找和最大的上升子序列
#include<iostream> #include<stdio.h> using namespace std; int main(){ int N; int v[1005]; int dp[1005];//dp[i]表示以a[i]结束的最大的上升子序列的和 int i; int ma; int j; while(~scanf("%d",&N)){ if(N==0){ break; } for(i=0;i<N;++i){ scanf("%d",&v[i]); } ma=0; for(i=0;i<N;++i){ dp[i]=v[i]; for(j=0;j<i;++j){ if(v[i]>v[j]){ dp[i]=max(dp[i],dp[j]+v[i]); } } if(dp[i]>ma){ ma=dp[i]; } } printf("%d ",ma); } return 0; }
127 / 215 Problem F HDU 1114 Piggy-Bank
d.给出一个存钱罐的容量,给出n种硬币的价值p和重量w(注意:每种硬币可无限取)
1.如果存钱罐能够正好塞满,输出塞满存钱罐需要的最少硬币的价值。
2.若不能完全塞满,则输出impossible。
s.每种物品可以放无限多次。所以为完全背包问题。此题是求最小值,为完全背包的变形。
注意初始化 dp[ 0 ]=0;
for i=1..N
for v=0..V
f[v]=max{f[v],f[v-cost]+weight}
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; int main(){ int T; int E,F; int N; int P[600],W[600]; int v;//背包大小 int dp[10100];//dp[i]表容量为i的时候所装东西的最小价值 int i,j; scanf("%d",&T); while(T--){ scanf("%d%d",&E,&F); v=F-E; scanf("%d",&N); for(i=0;i<N;++i){ scanf("%d%d",&P[i],&W[i]); } memset(dp,0x3f,sizeof(dp)); dp[0]=0; for(i=0;i<N;++i){ for(j=W[i];j<=v;++j){ dp[j]=min(dp[j],dp[j-W[i]]+P[i]); } } if(dp[v]==0x3f3f3f3f){ printf("This is impossible. "); } else{ printf("The minimum amount of money in the piggy-bank is %d. ",dp[v]); } } return 0; }
151 / 428 Problem G HDU 1176 免费馅饼
d.在0~10这11个位置上,某时刻某位置会掉落馅饼,gameboy在5位置,每次只能移动1,求他能接到的最大馅饼数。
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; int dp[100005][11];//dp[i][j]表示第i秒j位置可得的最大值 int main(){ int n; int x,T; int i; int j; int ma_t; while(~scanf("%d",&n)){ if(n==0){ break; } memset(dp,0,sizeof(dp)); ma_t=0; for(i=0;i<n;++i){ scanf("%d%d",&x,&T); ++dp[T][x]; if(T>ma_t){ ma_t=T; } } for(i=ma_t-1;i>=0;--i){ dp[i][0]=dp[i][0]+max(dp[i+1][0],dp[i+1][1]); for(j=1;j<10;++j){ dp[i][j]=dp[i][j]+max(max(dp[i+1][j-1],dp[i+1][j]),dp[i+1][j+1]); } dp[i][10]=dp[i][10]+max(dp[i+1][9],dp[i+1][10]); } printf("%d ",dp[0][5]); } return 0; }
118 / 199 Problem H HDU 1260 Tickets
d.K个人排队买票,单人买票花费Si时间,相邻两人一起买票花费Di时间,求售票所需最少时间。
s.dp[i]表示前i个人买票所需最少时间
dp[i]=min(dp[i-1]+S[i],dp[i-2]+D[i]);
#include<iostream> #include<stdio.h> using namespace std; const int MAXN=2010; int S[MAXN];//单人买票所需时间 int D[MAXN];//相邻两人买票所需时间 int dp[MAXN];//dp[i]表示前i个人买票所需最少时间 int main(){ int N; int K; char str_h[3],str_m[3],str_s[3]; int h,m,s; int i; scanf("%d",&N); while(N--){ scanf("%d",&K); for(i=1;i<=K;++i){ scanf("%d",&S[i]); } for(i=2;i<=K;++i){ scanf("%d",&D[i]); } dp[0]=0; dp[1]=S[1]; for(i=2;i<=K;++i){ dp[i]=min(dp[i-1]+S[i],dp[i-2]+D[i]); } h=dp[K]/60/60+8; m=(dp[K]/60)%60; s=dp[K]%60; while(h>=24){ h-=24; } if(h<=12){ str_h[0]=h/10+'0';str_h[1]=h%10+'0';str_h[2]='