首页 > 代码库 > 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);
  }
}