C语言速成手册(零):序言、环境、你的第一个程序

本文目的:介绍C语言最基本的用法,能对付NOIp就行
本文特点:没有废话,不讲概念,只介绍语法
适宜读者:已经学过其它语言,希望学习C语言的人;特别适合熟练Pascal并想转用C语言的OIer
编程环境:就我个人而言,Windows下用Dev-C++,Linux下用Emacs

目录:
C语言速成手册(一):基本数据类型、标准输出、函数
C语言速成手册(二):布尔值、条件判断、循环
C语言速成手册(三):数组、字符串、结构
C语言速成手册(四):指针、动态内存分配、标准输入
C语言速成手册(五):其它运算符、文件操作、其它函数
C语言速成手册(六):其它问题、后记

A+B问题代码:
#include <stdio.h>
int main()
{
  int a, b;
  scanf("%d%d", &a, &b);
  printf("%d", a+b);
  return 0;
}

使用Dev-C++编译你的第一个程序
    1. 安装最新版的Dev-C++,运行Dev-C++后按快捷键Ctrl+N建新文件,

    2. 将上面的A+B代码粘贴进去;

    3. 按Ctrl+S保存文件,文件类型选择C源码。如图,点击保存后F盘里将产生AplusB.c文件。.c是C源代码的后缀名。

    4. 按Ctrl+F9编译;

    5. Ctrl+F5在当前位置设断点,F8进入调试状态,F7单步。

    你也可以直接在下面的Debug工具栏里进行操作。只需要把鼠标移到源码中的变量名上停留片刻就可以Watch该变量。

编写一个最简单的交互式题目

    最近无聊学着编了一下交互式类型的题目。平常网上交互式题目的库文件源码并不多见,在这里把我写的一个题目分享给大家,希望对大家能够有帮助。

    题目是一个经典问题,下面所引用的内容是全部的题目描述:

Problem : famous
谁是名人

问题描述
    Matrix67所在的小镇上有N(2<=N<=1000)个人,它们从1到N编号。在小镇中,一些人认识另一些人,这种认识的关系是单向的。一个人是名人当且仅当小镇上所有的人都认识他,而除他之外的所有N-1个人他都不认识。假设这个小镇上有且只有一个名人,而你只能询问诸如“A是否认识B”这样的关系。请用不超过2000次的询问找到这个名人。

交互方法
    这个题目是一个交互式的题目。我们不提供输入文件,同时也不需要任何输出文件。你需要调用一个我们准备好的库函数来完成所有的操作。你可以得到libfam.ppu和libfam.o两个文件。你需要把这两个文件放在和你的源程序相同的目录下,并在源程序里加入“uses libfam”完成对库文件的引用。我们的库文件提供了以下两个函数和一个过程:
      function init:longint;
      function know(a,b:longint):boolean;
      procedure submit(sol:longint);

    init函数只能调用一次。这个函数仅在程序开始时调用,他将返回N值,代表小镇上的人数。
    函数know(a,b)返回一个布尔值,它告知了编号为a的人与编号为b的人的关系。如果该函数返回true,则你获得了“a认识b”这一信息;如果该函数返回false,则你获得了“a不认识b”这一信息。该函数可以多次调用,每调用一次称做为一次询问。你的询问次数不能超过2000次,也就是说,该函数最多被调用2000次。
    submit过程只能调用一次。这个过程仅在程序结束时调用,用于提交你得到的答案,所带的参数即是名人的编号。调用这个过程将终止你的程序,你可以在日志文件中看到反馈信息。

一个成功交互的例子
    下面这一程序代码说明了如何进行正确的操作。除了解释如何使用交互库之外,这个代码不具有任何意义。它不具有提示你询问方法的作用。

program famous;
uses libfam;

var
   n:longint;
   foo,bar:boolean;
begin
   n:=init;         //初始化,得到N的值
   foo:=know(1,3);  //询问1是否认识3
   bar:=know(n-1,4);  //询问n-1是否认识4
   submit(2);       //所提交的答案为2
end.

你如何测试自己的程序
    我们为选手准备了一种自我测试的方法。你需要在程序所在目录下建立test.in文件并在此文件下输入相关信息。该文件的构成应该如下:
    第一行:一个整数N,代表人数;
    第二行到第N+1行:输入一个01矩阵,数字与数字间用空格分隔。其中,第i行的第j个数字表示i是否认识j,“1”表示认识,“0”表示不认识。当i和j相等时,该位置上的0或1没有意义。
    一个合法的test.in文件内容可能如下:
3
1 1 0
0 0 0
1 1 1
    容易看出,该输入文件表明N=3时的情况,其中第2个人是名人。

    你的程序运行时将读取该文件的相关信息并作为此次测试的数据。
    程序退出后该目录将生成一个名为famous.log的文件。该文件是日志文件,它详细记录了你的程序与库的交互过程。一些可能出现的情况如下(部分语句较长,用…省略):
    Init called more than once.  表明你的程序调用init函数超过1次。
    File not exist.              表明目录里找不到test.in文件或无法打开该文件。
    Error reading N.             读入数字N时出错。该位置可能有其它字符。
    N must between 2 and 1000.   test.in中输入的N范围不正确。N应该在2到1000之间。
    Error reading the matrix.    读入01矩阵时出错。该位置可能有其它字符。
    Numbers in matrix should…  矩阵中出现了除0和1以外的数字。
    No famous person found…    你的输入数据中没有名人。
    Init called successfully.    程序调用init成功,继续运行。
    Too many queries…          你的询问次数超过2000次,程序被迫终止。
    Person # not exist.          你所询问的人不存在。可能你的参数大于了n或小于1。
    You asked a same person: #.  你的程序询问了两个相同的人的关系。
    No.# : # # TRUE/FALSE        依次说明这是第几次询问、询问的两个编号和返回的结果。
    Submitted after # queries.   你的程序成功地提交了答案。同时你可以看到你的总询问次数。
    Your answer: #               这是你提交的答案。
    Congrats, Your answer…     你提交的答案是正确的。你的程序通过了这次测试。
    Oops, Your answer is wrong…你提交的答案是错误的。同时你可以看到正确的答案是多少。

评分方法
    当你提交的答案与我们的正确答案相符时得10分。我们一共将有10次测试,总共100分。
    出现以下情况均不给分:
      程序提交的答案错误或没有提交答案;
      程序运行时间超过1秒;
      程序使用内存空间超过64M;
      程序询问次数超过2000次;
      程序崩溃或意外退出;
      错误访问库导致测试库出错;
      程序访问了其它外部文件。

数据规模
    对于30%的数据,N<=40;
    对于50%的数据,N<=50;
    对于70%的数据,N<=200;
    对于100%的数据,N<=1000。

    下面是这个题目的库文件代码:
unit libfam;

interface

function init:longint;
function know(a,b:longint):boolean;
procedure submit(sol:longint);

implementation

const
   limit=2000;

var
   map:array[1..1000,1..1000]of longint;
   n,ans:longint;
   query:longint=0;
   flog:text;
   inited:boolean=false;

procedure die(msg:string);
begin
   append(flog);
   writeln(flog,msg);
   close(flog);
   halt(1);
end;

procedure wrt(msg:string);
begin
   append(flog);
   writeln(flog,msg);
   close(flog);
end;

function init:longint;
var
   i,j:longint;
   flag:boolean;
begin
   if inited then die('Init called more than once.');

   assign(flog, 'famous.log');
   rewrite(flog);
   close(flog);

   {$i-}
   assign(input,'test.in');
   reset(input);
   if ioresult<>0 then die('File not exist.');
   {$i+}

   {$i-}
   readln(n);
   {$i+}
   if ioresult<>0 then die('Error reading N.');
   if (n<=1) or (n>1000) then die('N must between 2 and 1000.');

   for i:=1 to n do
   for j:=1 to n do
   begin
      {$i-}
      read(map[i,j]);
      {$i+}
      if ioresult<>0 then die('Error reading the matrix.');
      if (map[i,j]<>0) and (map[i,j]<>1) then die('Numbers in matrix should be 0 or 1.');
   end;
   close(input);

   ans:=-1;
   for i:=1 to n do
   begin
      flag:=true;
      for j:=1 to n do if (i<>j) and (map[i,j]=1) then flag:=false;
      for j:=1 to n do if (i<>j) and (map[j,i]=0) then flag:=false;
      if flag then ans:=i;
   end;
   if ans=-1 then die('No famous person found in your input data.');

   wrt('Init called successfully.');
   inited:=true;
   init:=n;
end;

function know(a,b:longint):boolean;
var
   sta,stb,stq:string;
begin
   inc(query);
   str(a,sta);
   str(b,stb);
   str(query,stq);

   if query>limit then die('Too many queries...');
   if (a<1) or (a>n) then die('Person '+sta+' not exist.');
   if (b<1) or (b>n) then die('Person '+stb+' not exist.');
   if a=b then die('You asked a same person: '+sta+'.');

   if map[a,b]=1 then wrt('No.'+stq+': '+sta+' '+stb+' TRUE')
                 else wrt('No.'+stq+': '+sta+' '+stb+' FALSE');
   know:=(map[a,b]=1);
end;

procedure submit(sol:longint);
var
   sts,stq,sta:string;
begin
   str(sol,sts);
   str(query,stq);
   str(ans,sta);
   wrt('');
   wrt('Submitted after '+stq+' queries.');
   wrt('Your answer: '+sts);
   if sol=ans then wrt('Congrats, Your answer is correct!')
              else wrt('Oops, Your answer is wrong! The answer should be '+sta+'.');
   halt(0);
end;

end.

    下面是一个数据生成器。在实际测试中使用的数据文件名并不是“test.in”,防止选手直接读入数据。因此,库的代码中读入部分也要做相应的改动。数据未经过加密,因此这种方法不能彻底防止选手作弊。使用Cena测试时,测试库需要放在Cena目录的compilersbin下。测试库还需要一些其它的改动,比如log文件可以改为只输出该测试点是否得分的信息(在Cena中设置成用答案正确时应该输出的log文件与实际输出的log文件进行对比)。
const
   c:array[0..9]of longint=
   (2,30,40,47,50,172,200,532,797,1000);
var
   a:array[1..1000,1..1000]of longint;
   i,j,k,n,t:longint;
begin
   randseed:=13875;
   for k:=0 to 9 do
   begin
      assign(output,'fam23z.'+chr(k+48)+'.in');
      rewrite(output);
      n:=c[k];
      for i:=1 to n do
      for j:=1 to n do a[i,j]:=random(2);
      t:=random(n)+1;
      for i:=1 to n do a[i,t]:=1;
      for i:=1 to n do a[t,i]:=0;
      writeln(n);
      for i:=1 to n do
      begin
         for j:=1 to n do write(a[i,j],' ');
         writeln;
      end;
      close(output);
   end;
end.

一个满分程序的代码可能如下:
uses libfam;
var
   i,t,n:integer;
begin
   n:=init;
   t:=1;
   for i:=2 to n do if know(t,i) then t:=i;
   submit(t);
end.

Matrix67原创
转载请注明出处

用Cena评测答案提交类题目的另类方法

    这几天组织了几次省选模拟赛,遇到了答案提交类的题目和交互式的题目。我一直使用Cena进行评测,现在希望把这两种类型的题目方便地加入Cena的评测结果中。交互式的题目使用Cena评测非常简单,只需要在库函数运行时输出一个以得分情况为内容的文件作为选手输出即可(http://www.matrix67.com/blog/article.asp?id=179)。但答案提交类的题目却遇到了麻烦,因为Cena肯定不允许程序访问外部文件(因此不能另写程序读入提交的答案并作为选手输出文件输出),而每个选手提交的答案文件所在位置又不确定(不知道文件夹名),不能把这些文件加入Cena的评测中。后来,我想到了这样一个解决方案。我可以用程序生成一个程序来生成选手输出文件(真他妈的绕口)。

    假设测试点共10个,所有的输入文件名为name.?.in,输出文件名为name.?.out,其中?取1到10中的数。那么下列程序可以生成一个printer.pas作为选手程序。以下程序将选手提交的答案写入pas源代码“printer.pas”中,它可以根据输入文件恰当地进行输出操作。“printer”将被设置为该题的源程序文件名。
    评测时所用的输入文件只有一个整数,标识这是第几个测试点。程序的输出(即选手提交的答案)可以和标准输出比较或另写Checker评分。

program print;
const
   fname='name';
var
   i:integer;
   st:string;

procedure init;
begin
   writeln('program printer;');
   writeln('var n:integer;');
   writeln;
   writeln('begin');
   writeln('   assign(input,'+#39+fname+'.in'+#39+');');
   writeln('   reset(input);');
   writeln('   readln(n);');
   writeln('   close(input);');
   writeln('   assign(output,'+#39+fname+'.out'+#39+');');
   writeln('   rewrite(output);');
   writeln;
end;

begin
   assign(output,'printer.pas');
   rewrite(output);
   init;
   for i:=1 to 10 do
   begin
      str(i,st);
      {$i-}
      assign(input,'name.'+st+'.out');
      reset(input);
      {$i+}
      if ioresult<>0 then continue;
      writeln('   if n=',i,' then begin');
      repeat
         readln(st);
         writeln('      writeln(',#39,st,#39,');');
      until eof;
      writeln('   end;');
      writeln;
   end;
   writeln('   close(output);');
   writeln('end.');
   close(output);
end.

    采取一些工具软件可以在不同的选手文件夹下批处理运行该程序。

    过几天我可能又要思考如何评测循环赛类型的题目了。

做人要厚道
转贴请注明出处