首页 > 代码库 > NOIP提高组 1999 & 2000 题解合集
NOIP提高组 1999 & 2000 题解合集
【序言】话说我在学神奇算法的时候,基础应该也要巩固,于是打算提前把NOIP提高组的刷完。
具体的题目描述和提交我就在VIJOS上完成。
【1999.1】
描述
给定一个信封,最多只允许粘贴N张邮票,计算在给定M(N+M<=10)种邮票的情况下(假定所有的邮票数量都足够),如何设计邮票的面值,能得到最大max ,使得1~max之间的每一个邮资值都能得到。
例如,N=3,M=2,如果面值分别为1分、4分,则在l分~6分之间的每一个邮资值都能得到(当然还有8分、9分和12分):如果面值分别为1分、3分,则在1分~7分之间的每一个邮资值都能得到。可以验证当N=3,M=2时,7分就是可以得到连续的邮资最大值,所以MAX=7,面值分别为l分、3分。
样例输入:共一行,两个整数,分表为N与M的值。
格式
输入格式
一行,分别为N,M。
输出格式
两行。
第一行为m种邮票的面值,按升序排列,各数之间用一个空格隔开。
第二行为最大值。
如果有多解,输出字典序最大的一个。
样例1
样例输入1
3 2
样例输出1
1 3 MAX=7
限制
各个测试点1s
来源
NOIP1999
【分析】似乎很早很早以前做到过~那时候因为不太懂各种算法,就乱搞搞过去了。但是现在思维复杂起来了,时间效率考虑的太多,反而难以下手。开始想的是贪心,发现是有问题的。数据范围N+M<=10,那么我们果断爆搜。在爆搜的途中如何随时更新值?只能用背包了。1A。
【代码】
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=605; int n,m,Max,i,a[15],ans[15],f[maxn]; int dp(int x) { memset(f,63,sizeof(f)); f[0]=0;int i,j; for (i=1;i<=maxn;i++) { for (j=1;j<=x;j++) if (i-a[j]>=0) f[i]=min(f[i-a[j]]+1,f[i]); if (f[i]>n) break; } return i-1; } void dfs(int step) { int now=dp(step); if (step==m) { if (now>Max) Max=now,memcpy(ans,a,sizeof(a)); return; } for (int i=now+1;i>a[step];i--) { a[step+1]=i; dfs(step+1); } } int main() { scanf("%d%d",&n,&m); a[1]=1;Max=0; dfs(1); for (i=1;i<=m;i++) printf("%d ",ans[i]); printf("\nMAX=%d",Max); return 0; }
【1999.2】
描述
一个旅行家想驾驶汽车以最少的费用从一个城市到另一个城市(假设出发时油箱是空的)。给定两个城市之间的距离d1、汽车油箱的容量c(以升为单位)、每升汽油能行驶的距离d2、出发点每升汽油价格p和沿途油站数n,油站i离出发点的距离d[i]、每升汽油价格p[i]。
计算结果四舍五入至小数点后两位。
如果无法到达目的地,则输出-1。
格式
输入格式
输入共n+1行,第一行为d1,c,d2,p,n,以下n行,每行两个数据,分别表示该油站距出发点的距离d[i]和该油站每升汽油的价格p[i]。两个数据之间用一个空格隔开。
输出格式
1 <= n <= 100
样例1
样例输入1
275.6 11.9 27.4 2.8 2 102.0 2.9 220.0 2.2
样例输出1
26.95
限制
1s
提示
0<=n<=100
【分析】开始还以为是DP,仔细一想是贪心。因为细节没处理好,后来还下载数据调试= =。
策略:主要是采用搜索的框架(但是其实不是,每层只有一个点在操作)设当前到达的点是K。
①对于K能到的点中,找到第一个油费比K小的点P。若能找到,跳到②;否则跳到⑤。
②加上刚好去P的油,累加答案,并来到P这个状态。跳到①。
③说明在能到达的点中,K的油费最小。跳到④。
④判断K能否直接到达终点,若能累加答案并退出,否则跳到⑤。
⑤找到K能到达的点中油费最小的点,同时加满油驶向P。若已经没有点了,输出-1,否则跳到①。
但是很遗憾,还有一个小的问题:我们还要记录一下当前所剩的油量Q。以上过程都要涉及Q。
【代码】
#include<cstdio> using namespace std; double dis,c,speed,cost[105],X,COST,x[105],ans; int N,i,n; bool flag; void walk(int k,double now) { if (x[k]+speed*c<x[k+1]) return; for (int i=k+1;i<=n&&x[k]+speed*c>=x[i];i++) if (cost[i]<cost[k]) { double need=(x[i]-x[k])/speed; if (now>=need) walk(i,now-need); else ans+=(need-now)*cost[k],walk(i,0); return; } if (x[k]+speed*c>=dis) { flag=1; double need=(dis-x[k])/speed; if (now<need) ans+=(need-now)*cost[k]; return; } int p=k+1; for (int i=k+2;i<=n&&x[k]+speed*c>=x[i];i++) if (cost[i]<cost[p]) p=i; ans+=(c-now)*cost[k]; walk(p,c-(x[p]-x[k])/speed); } int main() { scanf("%lf%lf%lf%lf%d",&dis,&c,&speed,&cost[0],&N); x[0]=0; for (i=1;i<=N;i++) { scanf("%lf%lf",&X,&COST); if (X<dis) x[++n]=X,cost[n]=COST; } ans=0;flag=0;walk(0,0); if (flag) printf("%.2lf",ans);else printf("-1"); return 0; }
1999的其它两题就不必说了吧~~~
【2000.1】
背景
JerryZhou同学经常改编习题给自己做。
这天,他又改编了一题。。。。。
描述
设有N*N的方格图,我们将其中的某些方格填入正整数,
而其他的方格中放入0。
某人从图得左上角出发,可以向下走,也可以向右走,直到到达右下角。
在走过的路上,他取走了方格中的数。(取走后方格中数字变为0)
此人从左上角到右下角共走3次,试找出3条路径,使得取得的数总和最大。
格式
输入格式
第一行:N (4<=N<=20)
接下来一个N*N的矩阵,矩阵中每个元素不超过80,不小于0
输出格式
一行,表示最大的总和。
样例1
样例输入1
4 1 2 3 4 2 1 3 4 1 2 3 4 1 3 2 4
样例输出1
39
限制
各个测试点1s
提示
多进程DP
【分析】其实原题是二取方格数,不过这样更加可以练练手。DP的方法应该很熟练了,我就写了些网络流。太高兴了,建图自己就YY出来了。设超级源点S,与左上角连一条费用为0,流量为3的边;设超级汇点T,与右下角连一条同样的边。能到达的两点连一条流量为INF,费用为0的边。然后把每个点拆成两个,连一条流量为INF,费用为0的边和一条流量为1,费用为权值的边。流量的意义是路径,费用的意义是价值。然后跑一遍最大费用最大流。
【代码】
#include<cstdio> #include<cstring> #include<algorithm> #define N 45 #define V 1005 #define M 80005 #define INF 2139062144 using namespace std; struct arr{int s,w,go,next;}a[M]; bool flag[V]; int f[V],q[M],pre[V],num[N][N][2],map[N][N],end[V]; int S,T,n,i,j,cnt,ans; bool spfa() { int h=0,t=1; memset(f,128,sizeof(f)); memset(flag,0,sizeof(flag)); f[S]=0;q[1]=S;flag[S]=true; while (h<t) { h++;int now=q[h]; for (int i=end[now];i;i=a[i].next) { int go=a[i].go; if (a[i].s&&f[now]+a[i].w>f[go]) { f[go]=f[now]+a[i].w;pre[go]=i; if (!flag[go]) { flag[go]=true;t++;q[t]=go; } } } flag[now]=false; } if (f[T]==-INF) return 0;return 1; } void cost() { int sum=INF; for (int i=pre[T];i;i=pre[a[i^1].go]) { sum=min(sum,a[i].s); if (a[i^1].go==S) break; } for (int i=pre[T];i;i=pre[a[i^1].go]) { a[i].s-=sum; a[i^1].s+=sum; ans+=sum*a[i].w; if (a[i^1].go==S) break; } } void add(int u,int v,int s,int w) { a[++cnt].go=v;a[cnt].s=s;a[cnt].w=w;a[cnt].next=end[u];end[u]=cnt; a[++cnt].go=u;a[cnt].s=0;a[cnt].w=-w;a[cnt].next=end[v];end[v]=cnt; } int main() { scanf("%d",&n);S=T=0;cnt=1; for (i=1;i<=n;i++) for (j=1;j<=n;j++) scanf("%d",&map[i][j]),num[i][j][0]=++T,num[i][j][1]=++T; T++; for (i=1;i<=n;i++) for (j=1;j<=n;j++) { add(num[i][j][0],num[i][j][1],1,map[i][j]); add(num[i][j][0],num[i][j][1],INF,0); if (i<n) add(num[i][j][1],num[i+1][j][0],INF,0); if (j<n) add(num[i][j][1],num[i][j+1][0],INF,0); } add(S,num[1][1][0],3,0);add(num[n][n][1],T,3,0); while (spfa()) cost(); printf("%d",ans); return 0; }
【2000.2】
描述
单词接龙是一个与我们经常玩的成语接龙相类似的游戏,现在我们已知一组单词,且给定一个开头的字母,要求出以这个字母开头的最长的“龙”(每个单词都最多在“龙”中出现两次),在两个单词相连时,其重合部分合为一部分,例如 beast和astonish,如果接成一条龙则变为beastonish,另外相邻的两部分不能存在包含关系,例如at 和 atide 间不能相连。
格式
输入格式
输入的第一行为一个单独的整数n (n<=20)表示单词数,以下n 行每行有一个单词,输入的最后一行为一个单个字符,表示“龙”开头的字母。你可以假定以此字母开头的“龙”一定存在.
输出格式
只需输出以此字母开头的最长的“龙”的长度
样例1
样例输入1
5 at touch cheat choose tact a
样例输出1
23
限制
各个测试点1s
提示
连成的“龙”为atoucheatactactouchoose
来源
NOIP2000提高组第3题
【分析】开始还以为是DP,用f[i][j]表示连接了i个单词,且最后一个是j的最大长度,后来发现无法获知每个单词是否用过(状压DP?)。想了一会了,N<=20,果断搜索。先预处理两两单词的关系,然后爆搜~。1A。
【代码】
#include<cstdio> #include<cstring> using namespace std; int sum[45][45],size[45],n,m,i,l,j,ans; bool flag[45]; char s[305],now[55],a[45][55]; int Do(int p,int q) { int ans=0; for (int i=1;i<=size[q];i++) { bool flag=1; for (int j=1;j<=i;j++) if (a[q][j]!=a[p][size[p]-i+j]) {flag=0;break;} if (flag) {ans=i;break;} } if (ans==size[q]) ans=0; return ans; } void solve(char *s,int l,int k) { char ss[305]; if (l>ans) ans=l; memcpy(ss,s,sizeof(s)); for (int i=1;i<=n*2;i++) if (!flag[i]&&sum[k][i]&&(sum[k][i]<l||k==n*2+1)) { flag[i]=true;int L=l; for (int j=sum[k][i]+1;j<=size[i];j++) ss[++L]=a[i][j]; solve(ss,L,i); flag[i]=false; } } int main() { scanf("%d",&n); for (i=1;i<=n;i++) { scanf("%s",s); size[i]=size[i+n]=strlen(s); for (j=1;j<=size[i];j++) a[i][j]=s[j-1]; } for (i=1;i<=n;i++) for (j=1;j<=n;j++) sum[i][j]=sum[i+n][j]=sum[i][j+n]=sum[i+n][j+n]=Do(i,j); scanf("%s",s); size[n*2+1]=l=ans=strlen(s); for (i=1;i<=l;i++) a[n*2+1][i]=s[i-1]; for (i=1;i<=n;i++) sum[n*2+1][i]=sum[n*2+1][i+n]=Do(n*2+1,i); solve(a[n*2+1],l,n*2+1); printf("%d",ans); return 0; }【2000.3】
背景
NOIP2000 提高组第一题
描述
我们可以用这样的方式来表示一个十进制数:将每个阿拉伯数字乘以一个以该数字所处位置的(值减1)为指数,以10为底数的幂之和的形式。例如,123可表示为1*10^2+2*10^1+3*10^0这样的形式。
与之相似的,对二进制数来说,也可表示成每个二进制数码乘以一个以该数字所处位置的(值-1)为指数,以2为底数的幂之和的形式。一般说来,任何一个正整数R或一个负整数-R都可以被选来作为一个数制系统的基数。如果是以R或-R为基数,则需要用到的数码为0,1,....R-1。例如,当R=7时,所需用到的数码是0,1,2, 3,4,5和6,这与其是R或-R无关。如果作为基数的数绝对值超过10,则为了表示这些数码,通常使用英文字母来表示那些大于9的数码。例如对16进制数来说,用A表示10,用B表示11,用C表示12,用D表示13,用E表示14,用F表示15。在负进制数中是用-R作为基数,例如-15(+进制)相当于110001(-2进制),
并且它可以被表示为2的幂级数的和数:
110001=1*(-2)^5+1*(-2)^4+0*(-2)^3+0*(-2)^2+0*(-2)^1+1*(-2)^0
问题求解:
设计一个程序,读入一个十进制数的基数和一个负进制数的基数,并将此十进制数转换为此负进制下的数:-R∈{-2,-3,-4,....-20}
格式
输入格式
输入文件有若干行,每行有两个输入数据。
第一个是十进制数N(-32768<=N<=32767); 第二个是负进制数的基数-R。
输出格式
输出此负进制数及其基数,若此基数超过10,则参照16进制的方式处理。【具体请参考样例】
样例1
样例输入1
30000 -2 -20000 -2 28800 -16 -25000 -16
样例输出1
30000=1101101010111000(base -2) -20000=1111011000100000(base -2) 28800=19180(base -16) -25000=7FB8(base -16)
限制
每个点1s。
提示
每个测试数据不超过1000组。
来源
From 玛维-影之歌
NOIP2000原题
【分析】说来惭愧,开始想的太复杂了=_=。当进制是负数时该怎么处理?开始想是枚举答案的位数,然后一步步的逼近~~真是麻烦。后来一拍脑袋,不是可以直接除吗?负数应该也满足整数的性质,只是除的时候要特判。1A。
【代码】
#include<cstdio> #include<cstdlib> using namespace std; int DIV(int A,int B) { int ans=A/B; if (ans*B>A) (ans*B>0)?ans--:ans++; return ans; } int main() { int a,p,n,t_a,i,ans[105]; while (scanf("%d%d",&a,&p)!=EOF) { n=0;printf("%d=",a); while (a!=0) { t_a=DIV(a,p); ans[++n]=a-t_a*p; a=t_a; } for (i=n;i;i--) printf("%c",(ans[i]<10)?ans[i]+48:ans[i]+55); printf("(base %d)\n",p); } }