1003 动规专练 解题报告

T1  卡车更新问题 (文件名:truck.pas/c/cpp)

【题目描述】

某人购置了一辆新卡车, 从事个体运输业务. 给定以下各有关数据: R[t], t=0,1,2,...,k, 表示已使用过 t 年的卡车, 再工作一年所得的运费, 它随 t 的增加而减少, k (k≤20) 年后卡车已无使用价值.

U[t]: t=0,1,...,k, 表示已使用过 t 年的卡车, 再工作一年所需的维修费, 它随 t 的增加而增加.

C[t], t=0,1,2,...,k, 表示已使用过 t 年的旧卡车, 卖掉旧车, 买进新车, 所需的净费用, 它随 t 的增加而增加. 以上各数据均为实型, 单位为"万元".

设某卡车已使用过 t 年,

① 如果继续使用, 则第 t+1 年回收额为 R[t]-U[t],

② 如果卖掉旧车,买进新车, 则第 t+1年回收额为 R[0]-U[0]-C[t] .

该运输户从某年初购车日起,计划工作 N (N<=20) 年, N 年后不论车的状态如何,不再工作. 为使这 N 年的总回收额最大, 应在哪些年更新旧车?  假定在这 N年内, 运输户每年只用一辆车, 而且以上各种费用均不改变.

【输入数据】

第 1 行: N (运输户工作年限)

第 2 行: k (卡车最大使用年限, k≤20 )

第 3 行: R[0] R[1] ... R[k]

第 4 行: U[0] U[1] ... U[k]

第 5 行: C[0] C[1] ... C[k]

【输出数据】

第 1 行: W ( N 年总回收额 )

第 2--N+1 行: 每行输出 3 个数据:

  年序号     ( 从 1 到 N 按升序输出 );

  否更新   ( 当年如果更新,输出 1, 否则输出 0);

  当年回收额 ( N 年回收总额应等于 W ).

【吐槽】

临场去掉了输出方案,整个题就水了。(借用Cydiater的话:输出方案就是毒瘤)

用f[i][j]表示前i年,当前车使用了j年的总回收额,则

当j==1时,f[i][j]=max{f[i][j],f[i-1][k]+R[0]-U[0]-C[k]}    (1<=k<=min(i-1,m))

否则,f[i][j]=f[i-1][j-1]+R[j-1]-U[j-1]       (2<=j<=min(i,m))

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cstdlib>
 5 #include<cmath>
 6 #include<ctime>
 7 #include<algorithm>
 8 using namespace std;
 9 #define MAXN 30
10 struct node{int flag;double v;}ANS[MAXN];
11 int n,m;
12 double ans,R[MAXN],U[MAXN],C[MAXN],f[MAXN][MAXN];
13 inline int read()
14 {
15     int x=0,f=1;  char ch=getchar();
16     while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
17     while(isdigit(ch))  {x=x*10+ch-'0';  ch=getchar();}
18     return x*f;
19 }
20 double Max(double a,double b)  {if(a>b)  return a;  else return b;}
21 int main()
22 {
23     freopen("truck.in","r",stdin);
24     freopen("truck.out","w",stdout);
25     n=read();  m=read();
26     for(int i=0;i<=m;i++)  scanf("%lf",&R[i]);
27     for(int i=0;i<=m;i++)  scanf("%lf",&U[i]);
28     for(int i=0;i<=m;i++)  scanf("%lf",&C[i]);
29     f[1][1]=R[0]-U[0];
30     for(int i=2;i<=n;i++)
31         for(int j=1;j<=min(i,m);j++)
32         {
33             if(j==1)
34             {
35                 for(int k=1;k<=min(i-1,m);k++)
36                     f[i][j]=Max(f[i][j],f[i-1][k]+R[0]-U[0]-C[k]);
37             }
38             else f[i][j]=f[i-1][j-1]+R[j-1]-U[j-1];
39         }
40     for(int i=1;i<=m;i++)  ans=Max(ans,f[n][i]);
41     printf("%.1lf\n",ans);
42     return 0;
43 }
View Code

 

 

 

 

 

T2 打扫卫生 (文件名:cleanup.pas/c/cpp)

【问题描述】

很久很久以前,Framer John只会做一种食品;而现在John能给他的N(1<=N<=40000)只奶牛供应M(1<=M<=N)种不同的食品。奶牛们非常挑剔,i号奶牛只吃食品Pi(1<=Pi<=M)。

每天,奶牛们按编号排队进自助餐厅用餐。不幸的是,这么多各类的食品让清扫工作变得非常复杂。当John供应K种食品,他之后就需要K^2的时间进行清扫。

为了减少清扫的时间,John决定把排好队的奶牛划分成若干组。每一组包含一段连续的奶牛,每一次,只让一组奶牛进入餐厅。这样,他可以让清扫所需的总用时变得最小。请计算这个最小用时。

【输入】

第一行,两个整数,N和M。

接下来N行,第i个整数位Pi。

【输出】

一个整数S,表示最小话费的时间。

【暴力法】

预处理出sum数组,sum[i][j]表示从第i到j头奶牛不同的食品数,f[i]表示前i头奶牛花费的最小时间,

状态转移方程:f[i]=min{f[i],f[i-k]+sum[i-k+1][i]^2}  (1<=k<=i)

时间复杂度:O(n^2)    可以得到50分。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cstdlib>
 5 #include<cmath>
 6 #include<ctime>
 7 #include<algorithm>
 8 using namespace std;
 9 #define MAXN 5010
10 long long n,m,a[MAXN],f[MAXN],check[MAXN],sum[MAXN][MAXN];
11 inline long long read()
12 {
13     long long x=0,f=1;  char ch=getchar();
14     while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
15     while(isdigit(ch))  {x=x*10+ch-'0';  ch=getchar();}
16     return x*f;
17 }
18 int main()
19 {
20     freopen("cleanup.in","r",stdin);
21     freopen("cleanup.out","w",stdout);
22     memset(f,10,sizeof(f));
23     n=read();  m=read();
24     for(long long i=1;i<=n;i++)  a[i]=read();
25     for(long long i=1;i<=n;i++)
26     {
27         memset(check,0,sizeof(check));
28         sum[i][i]=1;  check[a[i]]=1;
29         for(long long j=i+1;j<=n;j++)
30         {
31             sum[i][j]=sum[i][j-1]+(check[a[j]]?0:1);
32             check[a[j]]=1;
33         }
34     }
35     f[1]=1;  f[0]=0;
36     for(long long i=2;i<=n;i++)
37         for(long long k=1;k<=i;k++)
38             f[i]=min(f[i],f[i-k]+sum[i-k+1][i]*sum[i-k+1][i]);
39     printf("%I64d\n",f[n]);
40     return 0;
41 }
View Code

【奇妙的正解】

可以发现每一段中不同的数的个数不超过sqrt(n)个。

所以我们只需要记录每个点为结尾,一段序列中不同的数的个数不超过j的左端点的位置-1,pos[j]。通过pos数组进行DP转移,可以将复杂度从O(n^2)将为O(sqrt(n)n)。

状态转移方程:f[i]=min{f[pos[j]]+j*j}。

下面我们考虑右端点右移,也就是i++时,如何更新pos数组。

为了快速更新,我们还需要记录每个数值i的最后一个位置pre[i],和以pos[j]为左端点的序列中不同的数的个数cnt[j]。

当i++后,如果pre[a[i]]≤pos[j],那么cnt[j]++,说明序列中加入一个新的元素。

然后我们找出所有cnt[j]>j的序列,也就是不满足条件的序列,适当地调整左端点pos[j],使其满足cnt[j]≤j。其中对于左端点的调整只需要暴力即可,这是均摊O(1)的。

所以这道题最终的时间复杂度为O(n*sqrt(n))。

——正解转自  http://blog.csdn.net/aarongzk/article/details/50756718

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cstdlib>
 5 #include<cmath>
 6 #include<ctime>
 7 #include<algorithm>
 8 using namespace std;
 9 #define MAXN 40010
10 int n,m,a[MAXN],pos[MAXN],cnt[MAXN],pre[MAXN],f[MAXN];
11 inline int read()
12 {
13     int x=0,f=1;  char ch=getchar();
14     while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
15     while(isdigit(ch))  {x=x*10+ch-'0';  ch=getchar();}
16     return x*f;
17 }
18 int main()
19 {
20     freopen("cleanup.in","r",stdin);
21     freopen("cleanup.out","w",stdout);
22     n=read();  m=read();  m=(int)sqrt(n*1.0);  memset(pre,-1,sizeof(pre));
23     for(int i=1;i<=n;i++)  a[i]=read();
24     for(int i=0;i<=n;i++)  f[i]=i;//f的已知最小值
25     for(int i=1;i<=n;i++)
26     {
27         for(int j=1;j<=m;j++)  if(pre[a[i]]<=pos[j])  cnt[j]++;
28         pre[a[i]]=i;
29         for(int j=1;j<=m;j++) 
30             if(cnt[j]>j)
31             {
32                 int t=pos[j]+1;
33                 while(pre[a[t]]>t)  t++;
34                 pos[j]=t;
35                 cnt[j]--;
36             }
37         for(int j=1;j<=m;j++)  f[i]=min(f[i],f[pos[j]]+j*j);
38     }
39     printf("%d\n",f[n]);
40     return 0;
41 }
View Code

 

 

 

 

 

T3   工作计划 (文件名:wrk.pas/c/cpp)

【题目描述】

Mark 在无意中了解到了 Elf 的身世。在和 James 商量过之后,好心的他们打算送 Elf 返回故乡。然 而,去往 Gliese 的飞船票价高的惊人,他们暂时还 付不起这笔费用。经过一番考虑,Mark 打算去额外 做一些工作来获得收入。

经过一番调查,Mark 发现有 N 个工作可以做。 做第 i 件工作所需要的时间为 Di,同时也需要一个能力值 Ci 才可以去做,每件工作都可以在任意时间开始,也可以做任意多次。所有的工作 给付的报酬都是一致的。同时,有 S 个课程可以参加,我们认为今天是第 0 天,第 i 个课程 在第 Mi 天开始,持续时间为 Li 天,课程结束之后能力值会变为 Ai。现在 Mark 的能力值为1。Mark 只能做工作到第 T 天(因为那是飞船起飞的日子)。 他想知道期限内他最多可以做多少件工作,好决定未来的打算。于是他找到了 applepi。

然而 applepi 还差一个题没有出,所以这个任务就交给你了。

【输入格式】

第一行包含三个空格分隔的整数 T,S,N。

之后 S 行,每行三个整数 M,L,A,描述一个课程。 之后 N 行,每行两个整数 C,D,描述一件工作。

【输出格式】

一个整数,表示 Mark 最多可以做多少件工作。

【吐槽】

写题时把k写成了i,光荣爆掉。。。。。。

题解:动态规划,定义f[i][j]代表在i时间,能力值为j的最多工作次数。

对应最后三种选择:

①不作为 f[i][j]=f[i-1][j],

②上课 f[i][j]=f[上课前一个时刻][任意],

③做工作 f[i][j]=f[i-po[j]][j]+1 (po[j]为能力值<=j的工作一次的最短用时)。

对于②可以在预处理出ke[i][j]在i时刻结束,能力值达到j的课程的最晚开始时间。dp过程中处理出g[i]=max{f[i][j]}。

g[t]即为答案。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cstdlib>
 5 #include<cmath>
 6 #include<ctime>
 7 #include<algorithm>
 8 #include<queue>
 9 using namespace std;
10 int t,s,n,po[105],g[10005],f[10005][105],ke[10005][105];
11 inline int read()
12 {
13     int x=0,f=1;  char ch=getchar();
14     while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
15     while(isdigit(ch))  {x=x*10+ch-'0';  ch=getchar();}
16     return x*f;
17 }
18 int main()
19 {
20     freopen("wrk.in","r",stdin);
21     freopen("wrk.out","w",stdout);
22     memset(f,128,sizeof f);
23     t=read();  s=read();  n=read();
24     for(int i=1;i<=s;i++)
25     {
26         int m=read(),l=read(),a=read();
27         ke[m+l-1][a]=max(ke[m+l-1][a],m);
28     }
29     memset(po,0x3f,sizeof po);
30     for(int i=1;i<=n;i++)
31     {
32         int c=read(),d=read();
33         for(int j=c;j<=100;j++) po[j]=min(po[j],d);
34     }
35     f[0][1]=0;  g[0]=0;
36     for(int i=1;i<=t;i++)
37         for(int j=1;j<=100;j++)
38         {
39             f[i][j]=f[i-1][j];
40             if(ke[i-1][j])f[i][j]=max(f[i][j],g[ke[i-1][j]]);
41             if(i-po[j]>=0)f[i][j]=max(f[i][j],f[i-po[j]][j]+1);
42             g[i]=max(g[i],f[i][j]);
43         }
44     printf("%d\n",g[t]);
45     return 0;
46 }
View Code

 

 

 

 

 

T4   探险队 (exp.pas/c/cpp) 

【题目描述】

N个探险家组队去探索神秘的金字塔。 在路上有个 Stuff 问每个探险家参加过的探险活动的数目。 而探险家们都不愿意告诉他。 于是这个Stuff得到的回答都是“在这 N 个探险家中,一共 A个探险家参加过的探险活动比我多,而 B 个探险家参加过的探险活动比我少。 ” 当然并不一定所有的探险家都说了实话。现在这个悲催的 Stuff 想请你帮助他计算至少有多少个探险家说了谎。 

【输入格式】 
第一行是一个正整数 N,表示探险队的人数。
之后 N行每行两个整数 A和 B,表示一个探险家的答案。
【输出格式】 
输出一个整数表示答案,即至少有多少人说了谎。

【题解】

HAOI2011     problem a

http://wenku.baidu.com/link?url=WWQvRrX3LjD2FJnt5anmuYV0poVAn3XYMstxzTvx_6-YLmEj7qY80OBqaiCFZLED9v-HD0AEZy1sMNdS2P4Rxf0lLeSZUvt0qgpHqBnD4sW

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cstdlib>
 5 #include<cmath>
 6 #include<ctime>
 7 #include<algorithm>
 8 using namespace std;
 9 #define MAXN 100010
10 struct node{int l,r;}e[MAXN];
11 int n,len,l[MAXN],r[MAXN],v[MAXN],f[MAXN];
12 bool cmp(node a,node b)  {return a.r==b.r?(a.l<b.l):(a.r<b.r);}
13 inline int read()
14 {
15     int x=0,f=1;  char ch=getchar();
16     while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
17     while(isdigit(ch))  {x=x*10+ch-'0';  ch=getchar();}
18     return x*f;
19 }
20 int main()
21 {
22     freopen("exp.in","r",stdin);
23     freopen("exp.out","w",stdout);
24     n=read();
25     for(int i=1;i<=n;i++)
26     {
27         int x=read(),y=read();
28         e[i].l=x+1;  e[i].r=n-y;
29     }
30     sort(e+1,e+n+1,cmp);
31     for(int i=1;i<=n;i++)
32     {
33         if(e[i].l!=e[i-1].l||e[i].r!=e[i-1].r)  len++;
34         v[len]++;
35         v[len]=min(v[len],e[i].r-e[i].l+1);
36         l[len]=e[i].l;  r[len]=e[i].r;
37     }
38     int j=1;
39     for(int i=1;i<=n;i++)
40     {
41         f[i]=f[i-1];
42         while(j<=len&&r[j]==i)  {f[i]=max(f[i],f[l[j]-1]+v[j]);  j++;}
43     }
44     printf("%d\n",n-f[n]);
45     return 0;
46 }
View Code

 

 

 

 

 

T5   金字塔 (pyr.pas/c/cpp) 

【题目描述】 

虽然探索金字塔是极其老套的剧情, 但是这一队探险家还是到了某金字塔脚下。经过多年的研究,科学家对这座金字塔的内部结构已经有所了解。首先,金字塔由若干房间组成,房间之间连有通道。如果把房间看做节点,通道看做边的话,整个金字塔呈现一个有根树结构,节点的子树之间有序,金字塔有唯一的一个入口通向树根。并且,每个房间的墙壁都涂有若干种颜色的一种。 探险队员打算进一步了解金字塔的结构,为此,他们使用了一种特殊设计的机器人。这种机器人会从入口进入金字塔, 之后对金字塔进行深度优先遍历。 机器人每进入一个房间 (无论是第一次进入还是返回) ,都会记录这个房间的颜色。最后,机器人会从入口退出金字塔。 显然, 机器人会访问每个房间至少一次, 并且穿越每条通道恰好两次 (两个方向各一次) ,然后,机器人会得到一个颜色序列。但是,探险队员发现这个颜色序列并不能唯一确定金字塔的结构。现在他们想请你帮助他们计算,对于一个给定的颜色序列,有多少种可能的结构会得到这个序列。由于结果可能会非常大,你只需要输出答案对 109取模之后的值。 

【输入格式】
输入文件包含一行,含有一个字符串,表示机器人得到的颜色序列。

【输出格式】
输出一个整数表示答案。

【题解】

区间dp+乘法原理

输入是一个回文串,如果不是回文串,肯定方法数是0.(这也是一个比较好的骗分策略。)

既然要分方法数,那么肯定就和递推或者动态规划有关系。并且数据量肯定满足动态规划,但是搜索肯定会超时。

我们思考一下如果这棵树分为几个叉,那么几个叉的方法数肯定就是一个乘积的关系,一个复杂的问题分为多个步骤来完成。

分成几叉的条件是什么,就是在中间出现和根相同的字符,这个条件在题目中尤为重要。

所以,这道题的代码就是一个基本的 区间dp模板。

这道题的类似题目推荐 oj 1892。Hnoi2010的题目,bzoj上也有的。

也是区间dp求方法数。

 1 #include<iostream>
 2 #include<string>
 3 #include<cstring>
 4 #include<cstdio>
 5 #include<queue>
 6 #include<utility>
 7 #include<cmath>
 8 #include<algorithm>
 9 #include<cstdlib>
10 #include<ctime>
11 #include<set>
12 #include<map>
13 #define ll long long
14 using namespace std;
15 const int maxn=305;
16 const int mod=1000000000;
17 int L;
18 char ch[maxn];
19 ll f[maxn][maxn];
20 int main()
21 {
22     freopen("pyr.in","r",stdin);
23     freopen("pyr.out","w",stdout);
24     scanf("%s",ch+1);
25     L=strlen(ch+1);
26     for(int i=1;i<=L;i++) f[i][i]=1;
27     for(int len=2;len<=L;len++)
28     {
29         for(int i=1;i<=L;i++)
30         {
31             int j=i+len-1;
32             if(j>L) break;
33             if(ch[i]==ch[j]&&((j-i+1)&1))
34                 f[i][j]=f[i+1][j-1];
35             else continue;
36             for(int k=i+2;k<j;k++)
37                 f[i][j]=(f[i][j]+f[i+1][k-1]*f[k][j])%mod;
38         }
39     }
40     cout<<f[1][L]<<endl;
41     return 0;
42 }
View Code

 

 

 

 

 

T6启示录 (apo.pas/c/cpp) 

【题目描述】

 探险队员终于进入了金字塔。通过对古文字的解读,他们发现,和《圣经》的作者想的一样,古代人认为 666是属于魔鬼的数。不但如此,只要某数字的十进制表示中有三个连续的 6, 古代人也认为这个是魔鬼的数, 比如 666, 1 666, 2 666, 3 666, 6 663, 16 666, 6 660 666 等等, 统统是魔鬼的数。 古代典籍经常用 “第 X大的魔鬼的数” 来指代这些数。这给研究人员带来了极大的不便。 为了帮助他们, 你需要写一个程序来求出这些魔鬼的数字。 

【输入格式】
输入文件包含多组测试数据。第一行有一个整数 T 表示测试数据的组数。
每组测试数据包含一个整数 X,表示需要求第 X大的魔鬼的数。

【输出格式】 
对于每组测试数据,在一行内输出结果。

 

数位dp,没学过,留个坑。。。。。。

===========================

今天学了数位dp,把这个坑填上。

首先我们把这个问题转化成另一个问题:给定n,求1~n中有多少个数含有666

解决了这个问题,把原问题二分答案即可

首先预处理f数组,令

f[i][0]表示i位数中首位不为6且不含666的数的数量
f[i][1]表示i位数中首位连续1个6并且不含666的数的数量
f[i][2]表示i位数中首位连续2个6并且不含666的数的数量
f[i][3]表示i位数中含有666的数的数量

于是我们有递推式

f[i][0]=(f[i-1][0]+f[i-1][1]+f[i-1][2])*9;
f[i][1]=f[i-1][0];
f[i][2]=f[i-1][1];
f[i][3]=f[i-1][3]*10+f[i-1][2];

然后数位DP即可

其中处理的时候要记录当前确定数字末尾的6的个数 以及确定数字中是否含有666

 

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cstdlib>
 5 #include<cmath>
 6 #include<ctime>
 7 #include<algorithm>
 8 using namespace std;
 9 typedef long long ll;
10 ll T,S,f[30][5],digit[30];
11 inline ll read()
12 {
13     ll x=0,f=1;  char ch=getchar();
14     while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
15     while(isdigit(ch))  {x=x*10+ch-'0';  ch=getchar();}
16     return x*f;
17 }
18 void pre()
19 {
20     f[0][0]=1;
21     for(ll i=1;i<=29;i++)
22     {
23         f[i][0]=(f[i-1][0]+f[i-1][1]+f[i-1][2])*9;
24         f[i][1]=f[i-1][0];
25         f[i][2]=f[i-1][1];
26         f[i][3]=f[i-1][2]+f[i-1][3]*10;
27     }
28 }
29 int getans(ll x)
30 {
31     ll len=0,ans=0,cnt=0;
32     while(x)  {digit[++len]=x%10;  x/=10;}
33     for(ll i=len,j;i;i--)
34     {
35         ll sum;
36         for(int j=1;j<=digit[i];j++)
37         {
38             if(cnt==3)  sum=3;
39             else if(j==7)  sum=cnt+1;
40             else sum=0;
41             for(ll k=3;k>=3-sum;k--)  ans+=f[i-1][k];
42         }
43         if(cnt!=3)  cnt=(digit[i]==6?cnt+1:0);
44     }
45     return ans;
46 }
47 int main()
48 {
49     //freopen("cin.in","r",stdin);
50     //freopen("cout.out","w",stdout);
51     T=read();  pre();
52     while(T--) 
53     {
54         ll S=read(),l=0,r=100000000000ll;
55         while(l+1<r)
56         {
57             ll mid=(l+r)/2;
58             if(getans(mid+1)>=S)  r=mid;
59             else l=mid;
60         }
61         if(getans(r)==S)  printf("%I64d\n",l);
62         else printf("%I64d\n",r);
63     }
64     return 0;
65 }

 

posted @ 2016-10-04 13:13  chty  阅读(870)  评论(0编辑  收藏  举报