zpfbuaa / JobduInCPlusPlus

Solutions to Jobdu problems based on C++

Home Page:http://ac.jobdu.com/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

JobduInCPlusPlus

九度OJ永久关闭

List

Detail

题目1002:Grading

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1002

Problem description:

题目背景为高考试卷批改打分制度。对于每一道题,至少需要两位评审老师进行打分,当两个老师的打分结果相差在可接受范围内,那么该题最终得分为两位老师所给分数的平均分。
当打分相差较大超过可接受范围时,需要第三位评审老师打分。
如果第三位评审老师所给分数之和其中一位老师所给分数相差在可接受范围内,则最终分数为这两位老师所给分数的平均分。
如果第三位老师所给分数和前面两位老师所给分数之差均为可接受范围内,则最终分数取三位老师所给分数的最高分。
如果第三位老师所给分数和前面两位所给分数之差均超过可接受范围,则需要第四位评审老师给出分数,最终分数为第四位老师所给分数。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6711256.html

Analysis:

原题目为英文,看懂题目就很简单了。注意使用类型为double。并要注意输出精确到小数点后一位,使用printf("%.1lf\n",ans)

题目1003:A+B

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1003

Problem description:

给定两个整数A和B,其表示形式是:从个位开始,每三位数用逗号","隔开。
现在请计算A+B的结果,并以正常形式输出。 输入要求:输入包含多组数据数据,每组数据占一行,由两个整数A和B组成(-10^9 < A,B < 10^9)。
输出要求:请计算A+B的结果,并以正常形式输出,每组数据加换行。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6711267.html

Analysis:

两个整数带逗号,因此数据可使用char数组保存。之后需要将char数组保存的数据转为相对于的int类型的数字。转换方法有很多,下面给出一种:
求出char数组中保存数据的长度len,然后设置一个保存最终转换结果的int型变量并且初始化为0.除此之外设置一个记录当前数字长度的变量,用于倒序读取char数组,不断更新最终结果。然后还需要一个变量进行对转换的数字标记是正数还是负数,可以使用bool变量. 核心代码见下:

int lena = (int)strlen(a);
int size1 = 0;
int num1 = 0;
bool flag1 = (a[0]>='0'&&a[0]<='9') ? true : false;//默认0也是正数了!
for(int i = lena -1 ; i >= 0 ; i--){
   if(a[i]>='0' && a[i]<='9'){
       num1+=(a[i]-'0')*pow(10,size1);
       size1++;
   }
}

通过上述转换,可以得到最后的结果,然后根据A和B的符号进行相应的加法和减法操作即可。

if(flag1&&flag2) printf("%d\n",num1+num2);
else if(flag1 && !flag2) printf("%d\n",num1-num2);
else if(flag2 && !flag1) printf("%d\n",num2-num1);
else if(!flag1 && !flag2) printf("%d\n",0-num1-num2);

题目1004:Median

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1004

Problem description:

题目大致含义:给两个非递减的数组,找到两个数组的中位数。

输入要求:多组数据输入,第一行第一个数字为n,表示第一个数组有n个数字。第二行第二个数字为m,表示第二个数组有m个数字。

输出要求:两个数组的中位数。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6832183.html

Analysis:

使用一个足够大的数组保存下来全部的数字,进行排序之后求得中位数。

int cmp(void const * a,  void const * b){
    return * (int * )a - * (int * )b;
}
qsort(a,n+m,sizeof(a[0]),cmp);

题目1005:Graduate Admission

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1005

Problem description:

题目大致意思:现在有n个学生填报志愿,一共有m个学校招生,每个学生的填报志愿个数为k个。其中n取值范围为[1,40000]。m取值范围为[1,100],k取值范围为[1,5]。每个学生的成绩由两部分组成,初试成绩为GE,复试成绩为GI,最终成绩GF为(GE+GI)/2。学生成绩排名规则如下所示:按照最终成绩排名,如果最终成绩相同,那么初试成绩GE越高排名越靠前;如果初试成绩也相同,那么排名相同。学校录取规则如下:每个学校有额定录取名额,一旦录取人数达到录取名额则不再录取,但是如果多个人的排名相同(GF相同并且GE也相同)那么无论超过录取名额多少都要录取。

输入要求:多组数据,第一行为三个整数,n,m,k。分别代表学生个数,学校个数,志愿个数。接下来一行有m个正数,表示每个学校的额定录取人数。学校编号从0到m-1,编号为i的录取人数为第i个正数。接下来有n行,每一行有2+k个正数,第一个正数表示初试成绩GE,第二个整数表示复试成绩GI,接下来k个正数依次为填报志愿的顺序。

输出要求:第i行为编号为i-1的学校的录取情况,输出学生的编号。学生的编号从0到n-1。如果当期学校没有录取任何学生则输出空行。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6776372.html

Analysis:

为了保存学生志愿信息,可以利用结构体。如下所示:

#define CHOOSE 6
#define MAX_SIZE 40001
#define SCHOOL 101
struct Apply{
    int ge;
    int gi;
    double gf;
    int choose[CHOOSE];
    int id;
    bool operator < (const Apply &A) const{
        if(gf != A.gf){
            return gf > A.gf;
        }
        else if(ge != A.ge){
            return ge > A.ge;
        }
        else {
            return ge > A.ge;
        }
    }
};

为了存储学校录取情况,定义如下结构体:

struct School{
    int quota;
    int realNum;
    int appid[MAX_SIZE];
};

接下来要先按照成绩规则进行排名,在结构体中一定重载了小于运算符,因此调用STL提供的sort函数即可。 在对成绩做好排序之后,需要进行学校的录取,一次按照成绩排名选择其填报的志愿,如果被学校录取则需要将学生id放入学校的录取名单中,并且该学校的录取名额减去1,实际录取名额加1。同属需要判断当前学校录取的最后一位同学与现在申请该学校的学生的排名是否相同,如果相同那么无论是否超过额定录取名额,都要录取该学生。

sort(apply,apply+n);
int sid;
for(int i = 0 ; i < n ; i++){
    for(int j = 0 ; j < k ; j++){
        sid = apply[i].choose[j];
        if(school[sid].quota > 0){
            school[sid].appid[school[sid].realNum] = i;
            school[sid].realNum++;
            school[sid].quota--;
            break;
        }
        else{
            int lastid = school[sid].appid[school[sid].realNum-1];
            if(apply[i].gf == apply[lastid].gf && apply[i].ge == apply[lastid].ge){
                school[sid].appid[school[sid].realNum]=i;
                school[sid].realNum++;
                school[sid].quota--;
                break;
            }
        }
    }
}

for(int i = 0 ; i < m ; i++){
    for(int j = 0 ; j < school[i].realNum ; j++){
        school[i].appid[j] = apply[school[i].appid[j]].id;//保存学生的id
    }
}

输出学校信息时,需要注意当前学校是否录取有学生:

for(int i = 0 ; i < m ; i++){
    if(school[i].realNum==0){
        printf("\n");
    }
    else if(school[i].realNum==1){
        printf("%d\n",school[i].appid[0]);
    }
    else{
        sort(school[i].appid,school[i].appid+school[i].realNum);
        bool first = true;//第一个学生前面没有前置空格
        for(int j = 0 ; j < school[i].realNum ; j++){
            if(first==true){
                first = false;
            }
            else{
                printf(" ");
            }
            printf("%d",school[i].appid[j]);//学校i的第j个录取的学生的id
        }
        printf("\n");
    }
}

题目1006:ZOJ问题

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1006

Problem description:

对给定的字符串(只包含'z','o','j'三种字符),判断他是否能AC。
是否AC的规则如下:

  1. zoj能AC;
  2. 若字符串形式为xzojx,则也能AC,其中x可以是N个'o' 或者为空;
  3. 若azbjc 能AC,则azbojac也能AC,其中a,b,c为N个'o'或者为空;

输入要求:输入包含多组测试用例,每行有一个只包含'z','o','j'三种字符的字符串,字符串长度小于等于1000。

输出要求:输入包含多组测试用例,每行有一个只包含'z','o','j'三种字符的字符串,字符串长度小于等于1000。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6828317.html

Analysis:

记'z'前o的个数为a, 'z'和'j'之间o的个数为b, 'j'之后的o的个数为c.
分析文法可以发现,符合文法的字符串将满足:
1) b != 0
2) a * b = c
对于统计z前o个个数,以及z和j之间的o,以及j之后的o有不同的方法。其中可以利用标记z以及j的第一个出现位置来进行计算相应的个数。

通过以下函数进行求解:

bool judge(){
    int idxZ = -1 ;
    int idxJ = -1 ;
    bool zoj = true;
    int len = (int)str.size();
    for(int i = 0 ; i < len ; i++){
        if(str[i]=='z' && idxZ == -1){
            idxZ = i;
        }
        else if(str[i]=='j' && idxJ == -1){
            idxJ = i;
        }
        else if(str[i]!='o'){
            zoj = false;
        }
    }
    int a = idxZ;
    int b = idxJ - idxZ -1;
    int c = len - 1 - idxJ;
    if(zoj && idxZ != -1 && idxJ != -1 && (idxZ +1 < idxJ) && a*b==c){
        return true;
    }
    else return false;
}

题目1007:奥运排序问题

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1007

Problem description:

输入要求:有多组数据。
第一行给出国家数N,要求排名的国家数M,国家号从0到N-1。
第二行开始的N行给定国家或地区的奥运金牌数,奖牌数,人口数(百万)。
接下来一行给出M个国家号。

输出要求:排序有4种方式: 金牌总数 奖牌总数 金牌人口比例 奖牌人口比例
对每个国家给出最佳排名排名方式 和 最终排名
格式为: 排名:排名方式
如果有相同的最终排名,则输出排名方式最小的那种排名,对于排名方式,金牌总数 < 奖牌总数 < 金牌人口比例 < 奖牌人口比例
如果有并列排名的情况,即如果出现金牌总数为 100,90,90,80.则排名为1,2,2,4.
每组数据后加一个空行。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6782981.html

Analysis:

需要解释一下题目部分内容:排名方式序号为1,2,3,4分别对应按照金牌总数、奖牌总数、金牌人口比例、奖牌人口比例。
另外不是对所有的n个国家进行排序,而是对所指定的m个国家进行排序操作。
还要注意的是,如果出现金牌总数为 100,90,90,80.则排名为1,2,2,4。因此在排名计算时需要注意重复名次相同但是之后排名并不是紧挨着的!!

另外金牌人口比例以及奖牌人口比例可能出现小数,因此需要使用double变量来保存。
为了满足题目中的各种变量。声明如下的结构体,具体含义见变量命名:

struct Country{
    int id;
    int goldMedal;
    int totalMedal;
    int human;
    double goldRatio;
    double totalRatio;
    int rankGold;
    int rankTotal;
    int rankGoldRatio;
    int rankTotalRatio;
};

为了实现不同的排序,针对qsort函数,编写不同的cmp函数,如下所示:

int cmpRankGold(const void* a, const void *b){
    return (*(Country*)b).goldMedal - (*(Country*)a).goldMedal;
}
 
int cmpRankTotal(const void* a, const void *b){
    return (*(Country*)b).totalMedal - (*(Country*)a).totalMedal;
}
 
int cmpRankGoldRatio(const void* a, const void *b){
    return (*(Country*)b).goldRatio - (*(Country*)a).goldRatio;
}
 
int cmpRankTotalRatio(const void* a, const void *b){
    return (*(Country*)b).totalRatio - (*(Country*)a).totalRatio;
}
 
int cmpId(const void* a, const void *b){
    return (*(Country*)a).id - (*(Country*)b).id;
}

由于需要排名的只有m个国家因此需要两个Country数组来保存国家获奖排名等信息:

\#define MAX_SIZE 1001
Country country[MAX_SIZE];
Country cal[MAX_SIZE];

对于金牌排名按照如下方法:

qsort(cal,m,sizeof(Country),cmpRankGold);
int rank = 1;
cal[0].rankGold = 1;
for(int i = 1 ; i < m ; i++){
    if(cal[i].goldMedal!=cal[i-1].goldMedal){
        rank = i + 1;//需要注意排名的更新!
    }
    cal[i].rankGold = rank;
}

在输出时候需要针对不同的国家筛选出针对此国家的最优排名,同时需要记录下最优的排名是哪一种。

\#define RANK 1
for(int i = 0 ; i < m ; i++){
    int minRank = cal[i].rankGold;
    int rankChoose = RANK;
    if(cal[i].rankTotal < minRank){
        minRank = cal[i].rankTotal;
        rankChoose= RANK + 1;
    }
    if(cal[i].rankGoldRatio < minRank){
        minRank = cal[i].rankGoldRatio;
        rankChoose = RANK + 2;
    }
    if(cal[i].rankTotalRatio < minRank){
        minRank = cal[i].rankTotalRatio;
        rankChoose = RANK + 3;
    }
    printf("%d:%d\n",minRank,rankChoose);
}

题目1008:最短路径问题

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1008

Problem description:

给你n个点,m条无向边,每条边都有长度d和花费p,给你起点s终点t,要求输出起点到终点的最短距离及其花费,如果最短距离有多条路线,则输出花费最少的。

输入要求:输入n,m,点的编号是1~n,然后是m行,每行4个数 a,b,d,p,表示a和b之间有一条边,且其长度为d,花费为p。最后一行是两个数 s,t;起点s,终点t。n和m为0时输入结束。
n取值范围为(1,1000],m取值范围为(0,100000),并且s不等于t。

输出要求:输出 一行有两个数,最短距离及其花费,空格分隔。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6736356.html

Analysis:

同样使用dijkstra算法,但是由于存在多条最短路径,现在要求当最短路径有多个时需要输出花费最低的一条路径。并且最终结果需要输出最短路径长度以及对应的花费。

因此邻接矩阵的结构体需要增加一个成员变量cost来保存修路的花费。

struct E{
   int next;
   int len;
   int cost;
};

另外同时声明int cost[MAX_SIZE]; cost[i]保存从起点s到其他点i的花费。

if(dis[next]==-1 || dis[next] > dis[newP]+len || (dis[next]==dis[newP]+len && cost[next] > cost[newP]+cos)){
  	dis[next] = dis[newP] + len;
  	cost[next] = cost[newP] + cos;
}

题目1009:二叉搜索树

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1009

Problem description:

判断两序列是否为同一二叉搜索树序列。

输入要求: 开始一个数n,(1<=n<=20) 表示有n个需要判断,n= 0 的时候输入结束。
接下去一行是一个序列,序列长度小于10,包含(0~9)的数字,没有重复数字,根据这个序列可以构造出一颗二叉搜索树。
接下去的n行有n个序列,每个序列格式跟第一个序列一样,请判断这两个序列是否能组成同一颗二叉搜索树。

输出要求:如果序列相同则输出YES,否则输出NO

Source code:

http://www.cnblogs.com/zpfbuaa/p/6832230.html

Analysis:

关键是确定如何判断两个序列是否为同一个二叉搜索树。可采用方法:判断两个序列的后续遍历和中序遍历是否均相同。如果相同则是同一个二叉搜索树,否则不是。为了方便起见可以将后续遍历结果和中序遍历结果放到string字符串中,通过函数strcmp来判断是否等于0即可。

struct Node{
    Node * lchild;
    Node * rchild;
    int c;
}tree[110];
void inOrder(Node *T){
    if(T->lchild!=NULL)
        inOrder(T->lchild);
    str[(*size)++]=T->c+'0';//size++ and put the character into the str array
    if(T->rchild!=NULL)
        inOrder(T->rchild);
}
 
void postOrder(Node *T){
    if(T->lchild!=NULL)
        postOrder(T->lchild);
    if(T->rchild!=NULL)
        postOrder(T->rchild);
    str[(*size)++]=T->c+'0';//size++ and put the character into the str array
}

Node * insert(Node *T,int x){
    if(T==NULL){
        T=creat();
        T->c=x;
        return T;
    }
    else if(xc){
        T->lchild=insert(T->lchild, x);
    }
    else if(x>T->c){
        T->rchild=insert(T->rchild, x);
    }
    return T;
}

题目1010:A + B

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1010

Problem description:

读入两个小于100的正整数A和B,计算A+B.
需要注意的是:A和B的每一位数字由对应的英文单词给出.

输入要求:测试输入包含若干测试用例,每个测试用例占一行,格式为"A + B =",相邻两字符串有一个空格间隔.当A和B同时为0时输入结束,相应的结果不要输出.

输出要求:对每个测试用例输出1行,即A+B的值.

样例输入:
one + two =
three four + five six =
zero seven + eight nine =
zero + zero =

样例输出:
3
90
96

Source code:

http://www.cnblogs.com/zpfbuaa/p/6838791.html

Analysis:

由于所包含的英文单词只有0~9,则可以保存到char二维数组中。如下所示:

char arr[10][8] ={ "zero","one","two","three","four","five","six","seven","eight","nine"};

通过函数find来找到每个字符串对应的数字,然后需要计算两个加数。第一个加数结束标记为'+',第二个加数结束为'='。

int find(char* str) {
    int i;
    for (i = 0; i < 10; i++) {
        if (strcmp(arr[i], str) == 0)
            return i;
    }
    return 0;
}

其中的转换如下所示:

while (scanf("%s", temp) != EOF) {
    int a = find(temp);
    scanf("%s", temp);
    if (temp[0] != '+') {// if meet '+' means a is over
        a = a * 10 + find(temp);//calculate number a
        scanf("%s", temp);
    }
    scanf("%s", temp);
    int b = find(temp);
    scanf("%s", temp);
    if (temp[0] != '=') {//if meet '=' means b is over
        b = b * 10 + find(temp);// calculate number b
        scanf("%s", temp);
    }
    if(a==0 && b==0)//str a is zero && str b is zero means jumping out of loop
        break;
    printf("%d\n", a + b);//print the answer
}

题目1011:最大连续子序列

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1011

Problem description:

输出所给序列的最大连续子序列的和,并且需要输出该子序列的第一个和最后一个元素。

输入要求:测试输入包含若干测试用例,每个测试用例占2行,第1行给出正整数K( K< 10000 ),第2行给出K个整数,中间用空格分隔。当K为0时,输入结束,该用例不被处理。

输出要求: 对每个测试用例,在1行里输出最大和、最大连续子序列的第一个和最后一个元素,中间用空格分隔。如果最大连续子序列不唯一,则输出序号i和j最小的那个(如输入样例的第2、3组)。若所有K个元素都是负数,则定义其最大和为0,输出整个序列的首尾元素。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6843045.html

Analysis:

int sum = a[0], max = a[0];
int left =0 , right = 0;
int ansLeft = 0, ansRight = 0;
int ansMax = max;
for(int i = 1 ; i < k ; i++){
    if(sum < 0){
        sum = 0;
        left = right = i;
    }
    sum += a[i];
    right = i;
    if(sum > max){//update the sum and ansLeft && ansRight
        ansLeft = left;
        ansRight = right;
        max = sum;
        ansMax = max;
    }
}

通过判断ansMax是否大于0,来判断整个序列是否都是负数,然后决定不同的输出。

if(ansMax < 0){
    printf("0 %d %d\n",a[0],a[k-1]);
}
else{
    printf("%d %d %d\n",ansMax,a[ansLeft],a[ansRight]);
}

题目1012:畅通工程

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1012

Problem description:

测试输入包含若干测试用例。每个测试用例的第1行给出两个正整数,分别是城镇数目N ( < 1000 )和道路数目M;随后的M行对应M条道路,每行给出一对正整数,分别是该条道路直接连通的两个城镇的编号。

省政府“畅通工程”的目标是使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条道路?

Source code:

http://www.cnblogs.com/zpfbuaa/p/6725082.html

Analysis:

介绍图论中的一种数据结构——集合,以及基于集合的相关操作——并查集。利用该数据结构来表示集合信息,用以实现确定某个集合含有哪些元素、判断某两个元素是否存在同一个集合中,求集合中元素的数量。

例如集合A={1,2,3,4}。可以利用树来表示:

可以保存在数组中如下所示:

进行合并操作:

合并前后的数组:

查找结点x所在的树的根结点。递归的核心代码:

int findRoot(int x){
  if(Tree[x] == -1) return x;
  else return findRoot(Tree[x]);
}

循环的核心代码:

int findRoot(int x){
  while(Tree[x]!=-1){
  	x = Tree[x];
  }
}
return x;

查找过程中的路径优化的递归代码:

int findRoot(int x){
  if(Tree[x] == -1) return x;
  else{
  	int tmp = findRoot(Tree[x]);
  	Tree[x] = tmp;
  	return tmp;
  }
}

查找过程中的路径优化的循环代码:

int findRoot(int x){
  int ret;
  int tmp = x;
  while(Tree[x]!=-1){
  	x = Tree[x];
  }
  ret = x;
  x = tmp;
  while(Tree[x]!=-1){
  	int t = Tree[x];
  	Tree[x] = t;
  	x = t;
  }
  return ret;
}

本题目需要将相互连接的城市放到相同的集合中,集合的划分规则为能够互相连接的城市在一个集合中。因此最终结果是要求出还需要修建多少条道路。也就是将num个相互独立的集合连接需要的道路最小条数,那么就是num-1条道路即可。

题目1013:开门人和关门人

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1013

Problem description:

每天第一个到机房的人要把门打开,最后一个离开的人要把门关好。现有一堆杂乱的机房签到、签离记录,请根据记录找出当天开门和关门的人。

输入要求:测试输入的第一行给出记录的总天数N ( N> 0 ),下面列出了N天的记录。
每天的记录在第一行给出记录的条目数M (M > 0 ),下面是M行,每行的格式为
证件号码 签到时间 签离时间
其中时间按“小时:分钟:秒钟”(各占2位)给出,证件号码是长度不超过15的字符串。

输出要求:对每一天的记录输出1行,即当天开门和关门人的证件号码,中间用1空格分隔。
注意:在裁判的标准测试输入中,所有记录保证完整,每个人的签到时间在签离时间之前,且没有多人同时签到或者签离的情况。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6847645.html

Analysis:

定义签到结构体,记录每个人的标号id,以及签到时间和签退时间。

struct Sign{
    char id[MAX_SIZE];
    int comeH;
    int comeM;
    int comeS;
    int leftH;
    int leftM;
    int leftS;
};

实现自定义的cmp排序函数,对时间进行排序。开门按照从小到大排序,关门从大到小排序,如下所示:

int cmpOpenDoor(const void * a , const void * b){
    Sign * c = (Sign * )a;
    Sign * d = (Sign * )b;
    if(c->comeH != d->comeH){
        return c->comeH - d->comeH;
    }
    else if(c->comeM != d->comeH){
        return c->comeM - d->comeM;
    }
    else
        return c->comeS - d->comeS;
}
 
int cmpCloseDoor(const void * a , const void * b){
    Sign * c = (Sign * )a;
    Sign * d = (Sign * )b;
    if(c->leftH != d->leftH){
        return d->leftH - c->leftH;
    }
    else if(c->leftM != d->leftM){
        return d->leftM - c->leftM;
    }
    else {
        return d->leftS - c->leftS;
    }
}

经过排序之后,输出第一个签到的id,输出最后一个签退的id即可。

题目1014:排名

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1014

Problem description:

今天的上机考试虽然有实时的Ranklist,但上面的排名只是根据完成的题数排序,没有考虑每题的分值,所以并不是最后的排名。给定录取分数线,请你写程序找出最后通过分数线的考生,并将他们的成绩按降序打印。

输入要求:测试输入包含若干场考试的信息。每场考试信息的第1行给出考生人数N ( 0 < N < 1000 )、考题数M ( 0 < M < = 10 )、分数线(正整数)G;第2行排序给出第1题至第M题的正整数分值;以下N行,每行给出一名考生的准考证号(长度不超过20的字符串)、该生解决的题目总数m、以及这m道题的题号(题目号由1到M)。
当读入的考生人数为0时,输入结束,该场考试不予处理。

输出要求:对每场考试,首先在第1行输出不低于分数线的考生人数n,随后n行按分数从高到低输出上线考生的考号与分数,其间用1空格分隔。若有多名考生分数相同,则按他们考号的升序输出。

Source code:

Analysis:

定义结构体如下所示:

struct AC{
    char id[MAX_ID];//保存考生考号
    int num;
    int problem[MAX_PROBLEM];//每道题目得分
    int score;//最总得分 sum(problem[0~n])
};

自定义成绩排序,成绩相同的考号按照字典序排序输出

int cmpScore(const void * a , const void * b){
    AC * c = (AC * )a;
    AC * d = (AC * )b;
    if(d->score!=c->score)
        return d->score - c->score;
    else
        return strcmp(c->id,d->id);
}

题目1015:还是A+B

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1015

Problem description:

读入两个小于10000的正整数A和B,计算A+B。需要注意的是:如果A和B的末尾K(不超过8)位数字相同,请直接输出-1。

输入要求:测试输入包含若干测试用例,每个测试用例占一行,格式为"A B K",相邻两数字有一个空格间隔。当A和B同时为0时输入结束,相应的结果不要输出。

输出要求:对每个测试用例输出1行,即A+B的值或者是-1。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6854039.html

Analysis:

判断后k位是否相同,相同输出-1,不同则输出a+b

int num = pow(10,k);
int tmp1 = a % num ;
int tmp2 = b % num ;
tmp1 = (tmp1+num)%num;
tmp2 = (tmp2+num)%num;
if(tmp1 == tmp2)
	printf("-1\n");
else
  printf("%d\n",a+b);

题目1016:火星A+B

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1016

Problem description:

读入两个不超过25位的火星正整数A和B,计算A+B。需要注意的是:在火星上,整数不是单一进制的,第n位的进制就是第n个素数。例如:地球上的10进制数2,在火星上记为“1,0”,因为火星个位数是2进制的;地球上的10进制数38,在火星上记为“1,1,1,0”,因为火星个位数是2进制的,十位数是3进制的,百位数是5进制的,千位数是7进制的……

输入要求:测试输入包含若干测试用例,每个测试用例占一行,包含两个火星正整数A和B,火星整数的相邻两位数用逗号分隔,A和B之间有一个空格间隔。当A或B为0时输入结束,相应的结果不要输出。

输出要求:对每个测试用例输出1行,即火星表示法的A+B的值。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6783445.html

Analysis:

首先解释一下如何转换。在火星上的1,0转为地球上的10进制: 1 * 2 + 0 = 2; 火星上的1,1,1,0转为地球上的10进制:1 * (5 * 3 * 2) + 1 * ( 3 * 2 ) + 1 * (2) + 0 = 38

提前计算素数:

#define MAX_PRIME 26
int prime[MAX_PRIME];
bool isPrime(int x){
    if(x<=1) return false;
    if(x==2) return true;
    int tmp = sqrt(x)+1;
    for(int i = 2 ; i <= tmp ; i++){
        if(x%i==0)
            return false;
    }
    return true;
}

void calPrime(){
    int num = 0;
    for(int i = 2 ; num<25 ; i++){
        if(isPrime(i)){
            num++;
            prime[num] = i;
        }
    }
}

其实不必清楚如何转换也可以做题目,只要将对应的位相加并加上进位,保存起来即可。
对应题目的输入,有一种很巧妙的方法可以避免后期对字符串的遍历判断操作,而是直接将每一位数字保存在数组中,从高位到低位分别保存在a[0] ~ a[len-1]中。

for(i = 1 ; i < MAX_SIZE ; i++){
    scanf("%d",&a[i]);
    scanf("%c",&c);
    if(c==' ')break;
}
lena = i;
for(j = 1 ; j < MAX_SIZE ; j++){
    scanf("%d",&b[j]);
    scanf("%c",&c);
    if(c=='\n')break;
}
lenb = j;
if(a[1]==0 && b[1]==0)
break;

接下来需要对每一位进行相加操作,为了实现每一位相加,可以提前确定两个数字最长的长度。
然后需要对进位进行操作,以及不断保存对应为相加的结果。

int maxLen = lena>=lenb ? lena : lenb;//最大长度
int carry=0;//进位初始化
int len = maxLen;
int x,y;
for(i = 1 ; i <= maxLen; i++){
    if(lena>=1) x=a[lena--];//倒序取数
    else x=0;
    if(lenb>=1) y=b[lenb--];//倒序取数
    else y=0;
    sum[len--]=(x+y+carry)%prime[i];//保存当前位
    carry=(x+y+carry)/prime[i];//更新进位
}
if(carry!=0){//最后一位进位不为0,则输出该进位
    printf("%d,",carry);
}
for(i = 1 ; i < maxLen ; i++){
    printf("%d,",sum[i]);//前maxLen-1项需要输出','
}
printf("%d\n",sum[maxLen]);//不输出',' 而是输出换行'\n'

题目1017:还是畅通工程

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1017

Problem description:

Source code:

http://www.cnblogs.com/zpfbuaa/p/6731021.html

Analysis:

1.初始时所有结点属于孤立的集合。

2.按照边权递增顺序遍历所有的边,若遍历到的边两个顶点仍分属不同的集合(该边即为连通这两个集合的边中权值最小的那条)则确定该边为最小生成树上的一条边,并将这两个顶点分属的集合合并。

3.遍历完所有边后,原图上所有结点属于同一个集合则被选取的边和原图中所有结点构成最小生成树;否则原图不连通,最小生成树不存在。

按照上面的定理,首先需要将所有的边按照长度从小到大进行排序操作。可以通过定义结构体,结构体的边通过两个相连接的点确定。

struct Edge{
   int a, b;
   int cost;
   bool operator < (const Edge &A) const{//重载加法运算符
       return cost < A.cost;
   }
   Edge(){
       a = 0;
       b = 0;
       cost = 0;
   }
};

接下来从小到大遍历所有的边,如果该边的两个顶点不属于同一个集合那么将这两个顶点merge,同时将结果加上该边的长度。

int ans = 0;
for(int i = 1 ; i <= n*(n-1)/2 ; i++){
  int a = findRoot(edge[i].a);
  int b = findRoot(edge[i].b);
  if(a!=b){
  	tree[a] = b;
  	ans+=edge[i].cost;
  }
}

题目1018:统计同成绩学生人数

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1018

Problem description:

读入N名学生的成绩,将获得某一给定分数的学生人数输出。

输入要求:测试输入包含若干测试用例,每个测试用例的格式为

第1行:N
第2行:N名学生的成绩,相邻两数字用一个空格间隔。
第3行:给定分数
当读到N=0时输入结束。其中N不超过1000,成绩分数为(包含)0到100之间的一个整数。

输出要求:对每个测试用例,将获得给定分数的学生人数输出。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6858911.html

Analysis:

注意到题目所给的分数均为整数,并且取值范围为[0,100],因此可以设置数组,使用idx进行统计每个分数的人数。

memset(score,0,sizeof(score));
for(int i = 0 ; i < n ; i++){
    scanf("%d",&temp);
    score[temp]++;
}

题目1019:简单计算器

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1019

Problem description:

读入一个只包含 +, -, *, / 的非负整数计算表达式,计算该表达式的值。

输入要求: 测试输入包含若干测试用例,每个测试用例占一行,每行不超过200个字符,整数和运算符之间用一个空格分隔。没有非法表达式。当一行中只有0时输入结束,相应的结果不要输出。

输出要求:对每个测试用例输出1行,即该表达式的值,精确到小数点后2位。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6680719.html

Analysis:

首先题目给出的已知信息有很多,需要认真读题。注意到一下几点:

  1. 读入的字符只有+ - * /.故考虑运算顺序时,乘法除法在前面进行计算,最后进行加法减法运算。
  2. 读入的数据肯定是非负整数
  3. 整数和符号之间存在一个空格
  4. 一行只有0时输出结束,相应结果不许输出。这句话有待考核,按照提交的AC代码来看,这里应该修改为表达式的第一个非负整数等于0时结束输出。并且只有0输入时,这个计算结果是0,但是不能把这个0输出。其实就是第一个非负整数等于0时,结束程序
  5. 输出结果精确到小数点后2位。故在中间运算结果保存时需要用double类型的变量。

看到题目首先可以想到的思路就是可以将计算表达式转化为后缀表达式,比如1 + 2 * 3 + 4 * 5 可以转换为1 2 3 * 4 5 * + + 这样每当遇到一个运算符时可以取出最后面的两个数进行运算然后放回去(这样一看就是使用到了栈)。但是考虑到前缀表达式转为后缀表达式并不是这道题的考点,因此这种方法可行但不适用。(另外推荐参考博客http://www.cnblogs.com/hust_wsh/archive/2013/01/01/2841657.html得到中缀表达式转为后缀表示式的具体方法)

但是上面的分析并不是毫无作用的,使用栈这一点对解决该问题目是很重要的。那么怎样完成使用栈完成表达式的计算呢?由于不存在小括号或其他优先级更高的运算符,那么在计算表达式时,只要遇到*或者/,那么就可以直接拿符号前后的数字进行运算即可.但是这样我们只能解决乘法和除法问题,剩下的加法和除法在最后无法判断使用加法还是减法。但是减法也是一种加法呀,等于加上一个负数嘛,虽然题目说的都是非负整数,但是我们自己可以将其转为负数来进行计算的。

因此最后我们选择使用栈来保存计算结果,逐个字符进行读入。在此需要注意的地方如下:

  1. 数字和字符之间以空格分隔
  2. 在输入第一个非负整数以及空格后,其他的数字输入都是按照这样的组合进行输入的:操作符+空格+非负整数+空格。但是当这个表达式输入结束之后,最后一个就不再是空格了,组合变成了操作符+空格+非负整数+回车。因此可以通过判断最后一个字符进行判断表达式是否输入结束。
  3. 在Xcode中遇到的问题,printf()不加'\n'时,会导致无法输出结果。具体原因和解决方法可参见博客http://www.cnblogs.com/zpfbuaa/p/6675938.html
  4. 注意输出结果精确到小数点后两位 可采用printf("%2.lf\n",yourAns);

按照上面的方法就可以完成简单的计算器了。

题目1020:最小长方形

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1020

Problem description:

给定一系列2维平面点的坐标(x, y),其中x和y均为整数,要求用一个最小的长方形框将所有点框在内。长方形框的边分别平行于x和y坐标轴,点落在边上也算是被框在内。

输入要求:测试输入包含若干测试用例,每个测试用例由一系列坐标组成,每对坐标占一行,其中|x|和|y|小于 231;一对0 坐标标志着一个测试用例的结束。注意(0, 0)不作为任何一个测试用例里面的点。一个没有点的测试用例标志着整个输入的结束。

输出要求:对每个测试用例,在1行内输出2对整数,其间用一个空格隔开。第1对整数是长方形框左下角的坐标,第2对整数是长方形框右上角的坐标。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6863666.html

Analysis:

需要确定原点(0,0)表示的含义是哪一种。第一种为结束本次测试用例,并输出本次测试用例的最小长方形。第二种含义为结束所有输入,程序退出。
因此需要设置一个flag,记录当前组内是否存在实际意义的数据,如果存在那么遇到(0,0)时,含义就是上面的第一种(要求输出本次用例的最小长方形)。如果组内第一个遇到的元素即为(0,0)那么就是表示退出程序。
进行初始化操作:

void init(){
    flag = true;
    minx = miny = 232;
    maxx = maxy = -232;
}

读取数据进行计算:

while(scanf("%d%d",&x,&y)!=EOF){
    if(x!=0 || y!=0){
        minx = min(minx,x);
        miny = min(miny,y);
        maxx = max(maxx,x);
        maxy = max(maxy,y);
        flag = false;
    }
    if(flag && x==0 && y==0){
        break;
    }
    if(x==0 && y==0){
        printf("%d %d %d %d\n",minx,miny,maxx,maxy);
        init();
    }
}

题目1021:统计字符

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1021

Problem description:

统计一个给定字符串中指定的字符出现的次数。

输入要求:测试输入包含若干测试用例,每个测试用例包含2行,第1行为一个长度不超过5的字符串,第2行为一个长度不超过80的字符串。注意这里的字符串包含空格,即空格也可能是要求被统计的字符之一。当读到'#'时输入结束,相应的结果不要输出。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6870016.html

Analysis:

注意到需要对空格也进行统计。可以使用cin.getline(strFind,STR_FIND)进行输入。其中strFind为char数组类型,STR_FIND为定义的最大字符长度。
统计每个字符出现的次数:

int maxLen = (int)strlen(str);
for(int i = 0 ; i < maxLen ; i++){
    for(int j = 0 ; j < len ; j++){
        if(strFind[j]==str[i]){
            strNum[j]++;
            break;
        }
    }
}

题目1022:游船出租

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1022

Problem description:

现有公园游船租赁处请你编写一个租船管理系统。当游客租船时,管理员输入船号并按下S键,系统开始计时;当游客还船时,管理员输入船号并按下E键,系统结束计时。船号为不超过100的正整数。当管理员将0作为船号输入时,表示一天租船工作结束,系统应输出当天的游客租船次数和平均租船时间。
注意:由于线路偶尔会有故障,可能出现不完整的纪录,即只有租船没有还船,或者只有还船没有租船的纪录,系统应能自动忽略这种无效纪录。

输入要求:测试输入包含若干测试用例,每个测试用例为一整天的租船纪录,格式为:
船号(1~100) 键值(S或E) 发生时间(小时:分钟)
每一天的纪录保证按时间递增的顺序给出。当读到船号为-1时,全部输入结束,相应的结果不要输出。

输出要求:对每个测试用例输出1行,即当天的游客租船次数和平均租船时间(以分钟为单位的精确到个位的整数时间)。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6785912.html

Analysis:

保存游船出租情况的结构体如下所示:

struct Boat{
    int id;//游船编号
    int start;//借出时间
    int time;//出租总时间
    bool rent;//是否借出
    Boat(){
        time = 0;
        rent = false;
    }
};

具体实现如下所示:

int num;
char key;
int hour,minute;
int rentCount;
double totalTime;
while(scanf("%d",&num)!=EOF && num!=-1){
    rentCount = totalTime = 0;
    while(num != 0){
        scanf(" %c %d:%d",&key,&hour,&minute);
        if(key == 'S'){
            boat[num].id = num;
            boat[num].start = hour * 60 + minute;
            boat[num].rent = true;
        }
        else if(key == 'E'){
            if(boat[num].rent == true){
                boat[num].time = (hour * 60 + minute) - boat[num].start;
                rentCount++;
                totalTime += boat[num].time;
            }
        }
        scanf("%d",&num);
    }
    scanf(" %c %d:%d",&key,&hour,&minute);
    if(rentCount!=0){
        printf("%d %.0f\n",rentCount,totalTime/rentCount);
    }
    else{
        printf("0 0\n");
    }
}

题目1024:畅通工程

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1024

Problem description:

省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可)。经过调查评估,得到的统计表中列出了有可能建设公路的若干条道路的成本。现请你编写程序,计算出全省畅通需要的最低成本。

输入要求:测试输入包含若干测试用例。每个测试用例的第1行给出评估的道路条数 N、村庄数目M (N, M < =100 );随后的 N 行对应村庄间道路的成本,每行给出一对正整数,分别是两个村庄的编号,以及此两村庄间道路的成本(也是正整数)。为简单起见,村庄从1到M编号。当N为0时,全部输入结束,相应的结果不要输出。

输出要求:对每个测试用例,在1行里输出全省畅通需要的最低成本。若统计数据不足以保证畅通,则输出“?”。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6731387.html

Analysis:

最小生成树问题,基于前面的练习本题目的区别是进行对是否能够畅通的判断。当全省畅通时,对于集合只有一个根节点,并且根节点下面的结点的个数等于城市的个数。

按照上面的分析,为了在最终结果判断是否能够畅通,需要借助另外的辅助函数,保存当前结点所在集合下的结点的总数。那么在进行相连城市的合并操作时,除了需要将费用进行相加之外,还需要将两个集合的结点数目相加并保存起来。

需要构造结构体用于保存每一条边的两个点以及这条边修建的成本。同时需要对小于号<进行操作,以便之后使用sort函数,将所有的边的长度按照从小到大排序。每次取出最短的边,然后进行边上两个点的关系判断,如果不属于同一个集合需要进行集合合并操作,同时需要更新集合所存储的结点个数。

在最终判断是否能够全市畅通时,需要找到根节点然后判断该根节点下保存的结点的个数是否等于所有城市的个数。

判断是否畅通的核心代码如下所示:

bool flag = false;
  for(int i = 1 ; i <= m ; i++){
  	if(tree[i]==-1 && num[i]==m){//the root is tree[i] == -1
  		flag = true;
  		break;
  }
}
flag == true ? printf("%d\n",ans) : printf("?\n");

题目1025:最大报销额

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1025

Problem description:

现有一笔经费可以报销一定额度的发票。允许报销的发票类型包括买图书(A类)、文具(B类)、差旅(C类),要求每张发票的总额不得超过1000元,每张发票上,单项物品的价值不得超过600元。现请你编写程序,在给出的一堆发票中找出可以报销的、不超过给定额度的最大报销额。

输入要求:测试输入包含若干测试用例。每个测试用例的第1行包含两个正数 Q 和 N,其中 Q 是给定的报销额度,N(N<=30)是发票张数。随后是 N 行输入, 每行的格式为:m Type_1:price_1 Type_2:price_2 ... Type_m:price_m
其中正整数 m 是这张发票上所开物品的件数,Type_i 和 price_i 是第 i 项物品的种类和价值。物品种类用一个大写英文字母表示。当N为0时,全部输入结束,相应的结果不要输出。

输出要求:对每个测试用例输出1行,即可以报销的最大数额,精确到小数点后2位。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6786294.html

Analysis:

看起来用贪心好像可以,但是对于下面的情况,如果报销额度为1000,并且现在有三个满足条件的发票,第一张面额为600,剩下两个面额为500。如果使用贪心则只能报销600元,而实际最大报销为500+500=1000。因此是一个背包问题。背包容量为报销额度,每件物品所占体积为发票的总面额。

设状态dp[i]表示报销面额为i分的时候,能够报销的最大的发票总额。对于0-1背包初始化dp[i]均为0。因此状态转移方程为:dp[j]=max(dp[j],dp[j-pay[i]]+pay[i]);

对于上述的状态转移方程需要说明一点:由于题目中是保存两位有效数字因此该状态数组保存的最小单位为分,而不是元,因此最终计算结果为dp[q]/100;.其中设Q为报销额度单位为元,q=(int)(Q*100);

另外不是所有的发票都符合报销的条件,因此需要提前进行筛选,并且将满足条件的发票总额保存在数组pay[21]中。

for(int i = 1 ; i <= idx ; i++){
    for(int j = q ; j >= 1 ; j--){
        if(pay[i] <= j ){
            dp[j] = max(dp[j],dp[j-pay[i]]+pay[i]);
        }
    }
}
printf("%.2lf\n",dp[q]/100.00);

题目1028:继续畅通工程

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1028

Problem description:

省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通(但不一定有直接的公路相连,只要能间接通过公路可达即可)。现得到城镇道路统计表,表中列出了任意两城镇间修建道路的费用,以及该道路是否已经修通的状态。现请你编写程序,计算出全省畅通需要的最低成本。

输入要求:测试输入包含若干测试用例。每个测试用例的第1行给出村庄数目N ( 1< N < 100 );随后的 N(N-1)/2 行对应村庄间道路的成本及修建状态,每行给4个正整数,分别是两个村庄的编号(从1编号到N),此两村庄间道路的成本,以及修建状态:1表示已建,0表示未建。当N为0时输入结束。

输出要求:每个测试用例的输出占一行,输出全省畅通需要的最低成本。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6736090.html

Analysis:

本题目和之前的畅通工程的区别在于,目前的道路有些是已经修建好的,而之前的是所有道路均没有修建。因此对于本题目而言,在构造最小生成树之前,需要提前进行一轮初始化,本次初始化的目的是为了将已经相邻的城市放到同一个集合下。

由于城市之间的道路多了一种状态,也就是已修建和未修建。所以对于之前构造的结构体而言也要做出对应的改变。

struct Edge{
   int a;
   int b;
   int cost;
   int exist;
   bool operator < (const Edge &A)const{
       return cost < A.cost;
   }
};

也就是添加了exist这个成员变量,或者可以使用布尔变量。设定exist为1时表示当前城市a和城市b已经有一条路修建好了。同理exist为0时,该道路还未修建。

下面为按照输入进行的集合初始化操作:

for(int i = 1 ; i <= n*(n-1)/2 ; i++){
   scanf("%d%d%d%d",&edge[i].a,&edge[i].b,&edge[i].cost,&edge[i].exist);
   if(edge[i].exist==1){
       int a = findRoot(edge[i].a);
       int b = findRoot(edge[i].b);
       if(a!=b){
           tree[a] = b;
       }
   }
}

对于最小生成树使用kruskal算法时需要注意将所有的边按照从小到大进行排序操作sort(edge+1,edge+1+n*(n-1)/2);

题目1029:魔咒词典

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1029

Problem description:

给你一部魔咒词典。当哈利听到一个魔咒时,你的程序必须告诉他那个魔咒的功能;当哈利需要某个功能但不知道该用什么魔咒时,你的程序要替他找到相应的魔咒。如果他要的魔咒不在词典中,就输出“what?”

输入要求:首先列出词典中不超过100000条不同的魔咒词条,每条格式为:[魔咒] 对应功能
其中“魔咒”和“对应功能”分别为长度不超过20和80的字符串,字符串中保证不包含字符“[”和“]”,且“]”和后面的字符串之间有且仅有一个空格。词典最后一行以“@END@”结束,这一行不属于词典中的词条。
词典之后的一行包含正整数N(<=1000),随后是N个测试用例。每个测试用例占一行,或者给出“[魔咒]”,或者给出“对应功能”。

输出要求:每个测试用例的输出占一行,输出魔咒对应的功能,或者功能对应的魔咒。如果魔咒不在词典中,就输出“what?”

Source code:

http://www.cnblogs.com/zpfbuaa/p/6789499.html

Analysis:

需要注意的地方:给出功能输出魔咒时不输出[]。另外注意测试的结束条件为@END@。词典中的魔咒和功能中都包含空格,需要读取到空格。但是在将魔咒和功能存放在map<string,string> myMap中时,需要注意魔咒和功能之间的空格不能存放到魔咒或者功能中。

while(gets(s)){
    str = s;
    if(str=="@END@") break;
    int len = (int)str.size();
    int i;
    for(i = 0 ; i < len ; i ++){
        if(str[i]==']')//查找']'
            break;
    }
    curse = str.substr(0,i+1);//保存魔咒,调用substr函数
    fun = str.substr(i+2,len);//不能保存魔咒和功能之间的空格
    myMap[curse]=fun;
    myMap[fun]=curse;
}

题目1035:找出直系亲属

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1035

Problem description:

如果A,B是C的父母亲,则A,B是C的parent,C是A,B的child,如果A,B是C的(外)祖父,祖母,则A,B是C的grandparent,C是A,B的grandchild,如果A,B是C的(外)曾祖父,曾祖母,则A,B是C的great-grandparent,C是A,B的great-grandchild,之后再多一辈,则在关系上加一个great-。

输入要求:输入包含多组测试用例,每组用例首先包含2个整数n(0<=n<=26)和m(0<m<50), 分别表示有n个亲属关系和m个问题, 然后接下来是n行的形式如ABC的字符串,表示A的父母亲分别是B和C,如果A的父母亲信息不全,则用-代替,例如A-C,再然后是m行形式如FA的字符串,表示询问F和A的关系。
当n和m为0时结束输入。

输出要求:如果询问的2个人是直系亲属,请按题目描述输出2者的关系,如果没有直系关系,请输出-。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6792314.html

Analysis:

题目指出亲属关系的字符串为ABC形式,并且A为孩子,B为父亲,C为母亲。因此最多只有26个关系,可以使用数组保存下每个人的孩子。然后对于查找的字符串FA,只需要去对家谱树中查找F的孩子有谁,是不是等于A,并且统计深度,或者A的孩子有谁,是不是等于F,并且统计深度。
对于输出,需要判断是不是父母亲或者孩子,也就是家谱树中的深度差为1.如果大于1,那么需要进行循环输出great-,然后输出相应的关系grandparent或者grandchild.

深度查找函数:

int find(int a, int b){
    int depth = 1;
    while(family[a]!=-1){
        if(family[a] == b) return depth;
        a = family[a];
        depth++;
    }
    return -1;
}

题目1040:Prime Number

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1040

Problem description:

Output the k-th prime number. 输出第k个素数
输入要求:多组数据,并且输入的k满足: k≤10000
输出要求:输出第k个素数,每组数据加换行

Source code:

http://www.cnblogs.com/zpfbuaa/p/6701522.html

Analysis:

素数判断可以使用求模运算符进行判断,其中核心代码如下所示:

bool isPrime(long long  n){
   if(n <= 1) return false;
   long long  x = sqrt(n)+1;
   for(long long  i = 2 ; i <= x ; i ++){
       if(0 == n % i) return false;
   }
   return true;
}

另外的一种方法就是素数标记法,利用倍数关系将所有满足倍数关系的数字标记为非素数。

void init(){
   memset(prime,1,sizeof(prime));
   prime[0] = false;
   prime[1] = false;  
   int x = sqrt(max_size) + 1 ;
   for(int i = 2; i < x ; i++){
       if(prime[i] == true ){
           for(int j = i + i ; j < max_size ; j += i)
               prime[j] = false ;
       }
   }
}


进行素数表的初始化之后,进行循环遍历,并利用计数器记录当前是第几个素数,如果满足等于输入的k,那么停止遍历,输出当前的素数并换行。

题目1042:Coincidence

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1042

Problem description:

给出两个字符串s1,s2,找到两者的最长公共公子序列。输出最长公共公子序列的长度。

输入要求:多组测试数据,每组数据两行,第一行为字符串s1,第二行为字符串s2。字符串的长度小于100。

输出要求:对于每组测试数据,输出最长公共公子序列的长度,并且每组数据换行。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6759051.html

Analysis:

设字符串s1前i位,和字符串s2前j为的最长公共子序列为dp[i][j]。那么在求dp[i][j]时分为两种情况:

第一种状态为:字符串s1的第i位和字符串s2的第j位相等。那么dp[i][j]=dp[i-1][j-1]+1。因为以字符串s1的第i为结尾,加上前面的i-1位,能够和dp[i-1][j-1]中的s2的前j位匹配。

第二种状态为:字符串s1的第i位和字符串s2的第j位不相等。那么dp[i][j]=max(dp[i][j-1], dp[i-1][j])。因为在任意一个字符串后面再加一个字符,和原来的最长公共子序列没有影响。

状态转移方程:

dp[0][j] = 0; (0<=j<=lens2)
dp[i][0] = 0; (0<=i<=lens1)
dp[i][j] = dp[i-1][j-1]+1; (s1[i]==s2[j])
dp[i][j] = max(dp[i][j-1], dp[i-1][j]); (s1[i]!=s2[j])

因此最终结果输出dp[lens1][lens2]即可。

题目1048:判断三角形类型

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1048

Problem description:

给定三角形的三条边,a,b,c。判断该三角形类型。

输入要求:测试数据有多组,每组输入三角形的三条边。

输出要求:对于每组输入,输出直角三角形、锐角三角形、或是钝角三角形。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6797739.html

Analysis:

三角形存在定理:任意两边之和大于第三边,任意两边之差小于第三边。
三角形形状判断:最长边的平方的其余两边平方和的大小比较。相等则为直角三角形,小于则为锐角三角形、大于则为钝角三角形。

最长边:int maxNum = max(a,max(b,c));
最短边:int minNum = min(a,min(b,c));
中间边:int midNum = a+b+c-maxNum-minNum;

题目1049:字符串去特定字符

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1049

Problem description:

输入字符串s和字符c,要求去掉s中所有的c字符,并输出结果。

输入要求:测试数据有多组,每组输入字符串s和字符c。

输出要求:对于每组输入,输出去除c字符后的结果。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6771377.html

Analysis:

输入字符串s,保存在数组中,遍历字符数组,如果当前字符s[i]!=c那么输出当前字符。简单的遍历判断。

int len = (int)strlen(str);
for(int i = 0 ; i < len ; i++){
    if(str[i]!=c){
        cout\<\

题目1061:成绩排序

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1061

Problem description:

有N个学生的数据,每个学生的数据包括姓名、年龄、成绩。将学生数据按成绩高低排序,如果成绩相同则按姓名字符的字母序排序,如果姓名的字母序也相同则按照学生的年龄排序,并输出N个学生排序后的信息。

Source Code:

http://www.cnblogs.com/zpfbuaa/p/6671377.html

Analysis:

学生数据的排序依次需要考虑成绩,姓名,年龄的因素。可以使用C++中STL提供的的sort函数,通过自定义的cmp函数实现自定义的学生数据排序。

将学生信息按成绩进行递增排序,成绩相同的则按姓名的字母序进行递增排序,姓名相同的则按照年龄进行递增排序。

因此该cmp函数可以写成:

bool cmp(Stu a, Stu b){
    if(a.grade!=b.grade) return a.grade < b.grade;
    int result = strcmp(a.name.c_str(),b.name.c_str());
    if(result == 0) return result < 0;
    else return a.age < b.age;
}

由于学生数据类型不符合常用数据类型,可以创建结构体,其中包括string name, int age, int grade.

最关键的就是通过STL提供的sort函数进行排序操作。代码:sort(stu,stu+n,cmp);

题目1076:N的阶乘

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1076

Problem description:

输入一个正整数N,输出N的阶乘。
输入要求:正整数N(0<=N<=1000)
输出要求:输入可能包括多组数据,对于每一组输入数据,输出N的阶乘

Source code:

http://www.cnblogs.com/zpfbuaa/p/6718904.html

Analysis:

阶乘n!中n的取值范围为[0,1000],因此最终结果会很长,使用所提供的数据类型int或long long会overflow.可以使用int数组保存计算的每一位。
乘法的操作,一个数的每一位都与另一个数相乘,产生进位,保存每一位结果。
如果进位长度超过一位,那么需要保存所有的进位结果。
数组中保存的数据为从低位到高位,因此输出时需要先找到最高位,因此在计算中可以使用一个int变量来保存当前计算结果的位数length,然后倒序输出最终计算结果。
核心代码如下所示:

for(i = 1 ; i <= n ; i++){
  int carry = 0;
  for(j = 0 ; j < length ; j++){
  	pos[j] = pos[j] * i + carry;
      if(pos[j]>=10){
  	 	carry = pos[j]/10;
      	pos[j] = pos[j]%10;
      }
      else{
      	carry = 0;
      }
  }
   while(carry!=0){
   	pos[length++] = carry % 10;
   	carry/=10;
   }
}

题目1077:最大序列和

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1077

Problem description:

给出一个整数序列S,其中有N个数,定义其中一个非空连续子序列T中所有数的和为T的“序列和”。
对于S的所有非空连续子序列T,求最大的序列和。
变量条件:N为正整数,N≤1000000,结果序列和在范围(-2^63,2^63-1)以内。

输入要求:第一行为一个正整数N,第二行为N个整数,表示序列中的数。

输出要求:输入可能包括多组数据,对于每一组输入数据,仅输出一个数,表示最大序列和。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6803306.html

Analysis:

比较坑的地方就是输入的数可能直接超过了int的范围,因此需要申请long long 的数组。另外对于初始化的最大求和sum需要初始化LONG_LONG_MIN。在这里给自己一个提醒,以后头文件就写上#include 千万不要忘记了。

并不需要记录当前最大连续字序列和的初始位置以及结束位置,只需要记录最大值即可。

memset(a,0,sizeof(a));
long long  sum = LONG_LONG_MIN;
long long tmp = LONG_LONG_MIN;
for(int i = 0 ; i < n ; i ++){
    scanf("%lld",&a[i]);
    if(tmp>0) tmp+=a[i];
    else tmp = a[i];
    if(tmp>sum) sum=tmp;
}
printf("%lld\n",sum);

题目1078:二叉树遍历

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1078

Problem description:

给定一棵二叉树的前序遍历和中序遍历,求其后序遍历(提示:给定前序遍历与中序遍历能够唯一确定后序遍历)。

输入要求:输入样例可能有多组,每组数据输入两个字符串,其长度n均小于等于26。第一行为前序遍历,第二行为中序遍历。二叉树中的结点名称以大写字母表示:A,B,C....最多26个结点。

输出要求:对于每组测试样例,输出一行,为后序遍历的字符串。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6684057.html

Analysis:

二叉树的前序、中序、后序遍历的定义:
前序遍历:对任一子树,先访问跟,然后遍历其左子树,最后遍历其右子树;
中序遍历:对任一子树,先遍历其左子树,然后访问根,最后遍历其右子树;
后序遍历:对任一子树,先遍历其左子树,然后遍历其右子树,最后访问根。
根据前序遍历可以得到每层子树的根结点。然后再去中序遍历中定位这个根结点的位置。在中序遍历中,位于根结点左边的均为左子树上的结点,位于根节点右边的均为右子树上的结点。这样每次查找都可以将其分为两棵子树,接下来分别对这两颗子树进行上述操作。进行下一次定位根节点时,需要更新查找的起止位置,同样也要注意更新每棵子树的范围(通过点位根节点来实现划分两颗子树)。

举例说明:
前序遍历为FDXEAG,中序遍历为XDEFAG。首先从前序遍历得到根节点,前序遍历第一个元素为F,因此F是整棵树的根节点,接下来从中序遍历定位根节点F。中序遍历中在在根节点前面的元素为XDE,后面的为AG。因此左子树包含的结点有XDE,右子树包含的结点有AG。

当前状态为下图所示:


确定左子树前序遍历结果为DXE,左子树中序遍历结果为XDE。
得到D为该子树的根,X为该子树的左结点,E为该子树的右结点

确定右子树前序遍历结果为AG,右子树中序遍历结果为AG。 得到A为该子树的根,该子树左结点为空,G为该子树的右结点 构造出该树如下图所示:


按照上述思路,解决由前序遍历和中序遍历得到后序遍历。

题目1079:手机键盘

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1079

Problem description:

按照手机键盘输入字母的方式,计算所花费的时间。
如:a,b,c都在“1”键上,输入a只需要按一次,输入c需要连续按三次。
如果连续两个字符不在同一个按键上,则可直接按,如:ad需要按两下,kz需要按6下.
如果连续两字符在同一个按键上,则两个按键之间需要等一段时间,如ac,在按了a之后,需要等一会儿才能按c。
现在假设每按一次需要花费一个时间段,等待时间需要花费两个时间段。
现在给出一串字符,需要计算出它所需要花费的时间。

输入要求:输入可能包括多组数据,一个长度不大于100的字符串,其中只有手机按键上有的小写字母

输出要求:对于每组数据,输出按出Input所给字符串所需要的时间

Source code:

http://www.cnblogs.com/zpfbuaa/p/6803262.html

Analysis:

对于一个使用九键输入的我而言,看到这道题的时候,愣了一下,难道不是abc在数字键2上吗?吓得我赶紧拿出手机看了一眼。
并没有很复杂,只是不熟悉这个键盘位置分布的话会比较的尴尬。

使用了两个数组,其中一个保存每个小写字母所在的数字键,另外一个数组表示输入当前字母至少需要按下几次。

int pos[26]={2,2,2,3,3,3,4,4,4,5,5,5,6,6,6,7,7,7,7,8,8,8,9,9,9,9};
int kase[26]={1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,4,1,2,3,1,2,3,4};

就是判断相邻字符是否位于同一个数字键即可,然后更新总用时sum。代码如下:

int len = (int)strlen(str);
int sum = kase[str[0]-'a'];
for(int i = 1 ; i < len ; i++){
    if(pos[str[i]-'a']!=pos[str[i-1]-'a']){
        sum+=(kase[str[i]-'a']);
    }
    else {
        sum+=2+kase[str[i]-'a'];
    }
}

题目1080:进制转换

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1080

Problem description:

将M进制的数X转换为N进制的数输出。

输入要求:输入的第一行包括两个整数:M和N(2<=M,N<=36)。
下面的一行输入一个数X,X是M进制的数,现在要求你将M进制的数X转换成N进制的数输出。
输出要求:输出X的N进制表示的数。
Tips: 输入时字母部分为大写,输出时为小写,并且有大数据。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6691038.html

Analysis:

如果仅仅是简单的进制转换,那么可以采用从起始进制转为10进制然后再转为目标进制。但是题目要求是大数据因此将数据存储在long long中也是不可行的。因此需要使用到数组,那么数组的话就需要实现从起始进制直接转换至目标进制。

参考博客http://blog.csdn.net/jaster_wisdom/article/details/52107785讲的很详细,里面讲解了如何实现任意进制的转换过程。里面每一次进行的计算,都会让前面的位逐渐变为0,并且直到最后的求和为0时结束循环。实现的功能就是直接将每一位直接转为相应的目标进制。

下面摘出重要的分析:


data[]数组里面保存的是 待转化的数,因为这里数比较大,不能直接除以2,求模。要一步一步算。首先是第一位1除以2,余数是0,模是1,然后考虑第二个数,注意第二个数的值应该是前一个数与2取模之后得到的1再乘以10,再加上2,即12。然后循环下去,当到了最后一个数的时候,将余数1保存到output数组里面去。这只是第一次相除,因为余数061728394不等于0,所以还要继续循环,模拟除以2的过程,直到各个位都为0,即sum(保存各个位的和)= 0,最终的结果就是output数组的倒序输出。

题目1081:递推数列

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1081

Problem description:

给定a0,a1,以及an=p * a(n-1) + q * a(n-2)中的p,q。这里n >= 2。 求第k个数对10000的模。

输入要求:多组数据,每组数据输入包括5个整数:a0、a1、p、q、k。

输出要求:第k个数a(k)对10000的模。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6803388.html

Analysis:

暴力求解,时间复杂度为O(n):

void cal(){
    memset(a,0,sizeof(a));
    a[0]=a0;
    a[1]=a1;
    for(int i = 2 ; i <= k ; i++){
        a[i] = ( (p * a[i-1]) % 10000 + (q * a[i-2]) % 10000 ) % 10000;
    }
}

观察上面的递推公式,可以得到以下矩阵关系。

metrixMul

| ak   |   | p q |   | ak-1 |
|      | = |     | * |      |
| ak-1 |   | 1 0 |   | ak-2 |

  metrixPow

| ak   |   | p q |(k-1)   | a1 |
|      | = |     |     *  |    |
| ak-1 |   | 1 0 |        | a0 |

矩阵求幂可以通过优化可以将时间复杂度优化至O(logn)。题目给出二阶矩阵,矩阵快速求幂首先基于两个矩阵乘法,然后按照奇偶次幂进行乘法运算。下面给出矩阵快速求幂:

void metrixPow(int p[2][2], int n){
    int tmp[2][2];
    tmp[0][0] = p[0][0];
    tmp[0][1] = p[0][1];
    tmp[1][0] = p[1][0];
    tmp[1][1] = p[1][1];
    if(n==1) return ;
    else if((n&1)==1){//odd
        metrixPow(p, n-1);
        metrixMul(p, tmp);
    }
    else{
        metrixPow(p,n/2);
        metrixMul(p, p);// mind : means metrix p*p
    }
}

题目1082:代理服务器

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1082

Problem description:

题目大致含义:给出n个代理服务器,m个服务器的IP地址,使用这n个代理服务器去访问这m个服务器,并且对于这m个服务器的访问顺序已经确定,代理服务器不能与服务器的IP地址相同,如果相同则需要进行服务器的切换。现在要求出一个最少的访问方法得到最少的切换次数。

输入要求:

每个测试数据包括 n + m + 2 行。
第 1 行只包含一个整数 n,表示代理服务器的个数。
第 2行至第n + 1行每行是一个字符串,表示代理服务器的 IP地址。这n个 IP地址两两不相同。
第 n + 2 行只包含一个整数 m,表示要访问的服务器的个数。
第 n + 3 行至第 n + m + 2 行每行是一个字符串,表示要访问的服务器的 IP 地址,按照访问的顺序给出。
每个字符串都是合法的IP地址,形式为“xxx.yyy.zzz.www”,其中任何一部分均是0–255之间的整数。输入数据的任何一行都不包含空格字符。
其中,1<=n<=1000,1<=m<=5000。

输出要求:可能有多组测试数据,对于每组输入数据, 输出数据只有一行,包含一个整数s,表示按照要求访问服务器的过程中切换代理服务器的最少次数。第一次使用的代理服务器不计入切换次数中。若没有符合要求的安排方式,则输出-1。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6803520.html

Analysis:

想法就是找到最后一个出现的代理服务器,但是由于不清楚需要访问的服务器的IP是否会出现多次,因此还是正序遍历每一个代理服务器,不断通过判断当前代理服务器最多可访问的服务器的个数来进行服务器数量上的减少操作。

需要注意的是,只有一个代理服务器时,输出结果要么是-1,要么是0。-1表示当前的唯一一台代理服务器不能访问全部的服务器。如果输出0则表示当前的一台代理服务器足够访问全部的服务器。

通过定义结构体保存IP地址,并且通过重载==运算符进行IP地址是否相同的判断。

题目1083:特殊乘法

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1083

Problem description:

写个算法,对2个小于1000000000的输入,求结果。
特殊乘法举例:123 * 45 = 1 * 4 + 1 * 5 + 2 * 4 + 2 * 5 + 3 * 4 + 3 * 5
输入要求: 两个小于1000000000的数
输出要求: 输入可能有多组数据,对于每一组数据,输出Input中的两个数按照题目要求的方法进行运算后得到的结果

Source code:

http://www.cnblogs.com/zpfbuaa/p/6686469.html

Analysis:

按照保存输入数据的类型,可以用一下两种不同方法。
第一种使用int保存输入,然后可以利用求模运算符%得到每一位的值,并将其保存在int数组中。所输入的两个int类型的数据,均进行上述操作。最后使用循环将每一位都经行相乘并将每次结果相加之后得到最后的答案。这里分析了一下由于不超过1,000,000,000 因此考虑极端情况的话,这个最终结果也不会超过int的范围。

第二种使用char数组保存,然后循环相乘每一位即可。相乘时只需要让该位的值减去'0'即可。

这里觉得第二种方法对于负数好像没有办法使用哎,不知道大家注意到了没有。可以尝试一下在OJ上提交第二种方法的代码。

题目1084:整数拆分

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1084

Problem description:

一个整数总可以拆分为2的幂的和,例如:
7=1+2+4
7=1+2+2+2
7=1+1+1+4
7=1+1+1+2+2
7=1+1+1+1+1+2
7=1+1+1+1+1+1+1
总共有六种不同的拆分方式。
再比如:4可以拆分成:4 = 4,4 = 1 + 1 + 1 + 1,4 = 2 + 2,4=1+1+2。
用f(n)表示n的不同拆分的种数,例如f(7)=6.
要求编写程序,读入n(不超过1000000),输出f(n)%1000000000。

输入要求:每组输入包括一个整数:N(1<=N<=1000000)。

输出要求:对于每组数据,输出f(n)%1000000000。

Source code:

Analysis:

通过题目得到f(1)=1,f(2)=2,f(3)=2,f(4)=4,f(5)=4,f(6)=6,f(7)=6,f(8)=10,f(9)=10。。。

得到递推公式:f(n) = f(n-1) + f((n-2)/2) + f((n-4)/4) + ... + f((n-2^t)/t) +...
因此 f(n) = f(n-1)+f(n/2)
递推公式如下所示:

void cal(){
    memset(a, 0, sizeof(a));
    a[0]=1;
    for(int i = 1 ; i < MAX_SIZE ; i++){
        a[i] = (a[i-1]+a[i/2])%MOD;
    }
}

题目1085:求root(N, k)

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1085

Problem description:

N<k时,root(N,k) = N,否则,root(N,k) = root(N',k)。N'为N的k进制表示的各位数字之和。输入x,y,k,输出root(x^y,k)的值 (这里^为乘方,不是异或),2=<k<=16,0<x,y<2000000000,有一半的测试点里 x^y 会溢出int的范围(>=2000000000)

输入要求:每组测试数据包括一行,x(0<x<2000000000), y(0<y<2000000000), k(2<=k<=16)

输出要求:输入可能有多组数据,对于每一组数据,root(x^y, k)的值

Source code:

Analysis:

二分求幂方法如下:


#define L long long
L cal(L x, L y, L k){
    L ans = 1;
    while(y!=0){
        if((y&1)==1){
            ans = (ans*x)%k;
        }
        x=(x*x)%k;
        y=y>>1;
    }
    return ans;
}

题目1089:数字反转

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1089

Problem description:

12翻一下是21,34翻一下是43,12+34是46,46翻一下是64,现在又任意两个正整数,问他们两个数反转的和是否等于两个数的和的反转。

输入要求:第一行一个正整数表示测试数据的个数n。只有n行,每行两个正整数a和b(0<a,b<=10000)。

输出要求: 如果满足题目的要求输出a+b的值,否则输出NO。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6809880.html

Analysis:

进行数字的翻转:

int reverse(int x){
    int tmp = x;
    int ans = 0;
    while(tmp!=0){
        ans = ans*10 + tmp%10;
        tmp/=10;
    }
    return ans;
}

题目1091:棋盘游戏

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1091

Problem description:

有一个6*6的棋盘,每个棋盘上都有一个数值,现在又一个起始位置和终止位置,请找出一个从起始位置到终止位置代价最小的路径:

   1、只能沿上下左右四个方向移动
   2、总代价是没走一步的代价之和
   3、每步(从a,b到c,d)的代价是c,d上的值与其在a,b上的状态的乘积
   4、初始状态为1, 每走一步,状态按如下公式变化:(走这步的代价%4)+1。

输入要求:第一行有一个正整数n,表示有n组数据。
每组数据一开始为6*6的矩阵,矩阵的值为大于等于1小于等于10的值,然后四个整数表示起始坐标和终止坐标。

输出要求:输出最小代价。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6810738.html

Analysis:

对于给定的图进行不断深度遍历,找到每个可以从起点到达终点的最短的代价。

图每个位置初始数字的保存,图中每个位置是否已经访问,到达某个位置之后的下一步移动。可以使用二维数组map保存图的初始数字,使用visited[MAX_SIZE][MAX_SIZE]记录是否已经访问,使用change[2][4]={{-1,1,0,0},{0,0,-1,1}}表示可以进行的下一步移动。

void DFS(int x, int y, int status, int sum){
    int nextX,nextY,cost;
    if(sum < ans){
        if(x==xEnd && y==yEnd){
            ans = sum;
            return ;
        }
        for(int i = 0 ; i < 4 ; i ++){
            nextX = x + change[0][i];
            nextY = y + change[1][i];
            if(!visited[nextX][nextY] && nextX>=0 && nextX<6 && nextY>=0 && nextY<6){
                cost = map[nextX][nextY]*status;
                visited[nextX][nextY]=true;
                DFS(nextX,nextY,cost%4+1,sum+cost);
                visited[nextX][nextY]=false;
            }
        }
    }
}

题目1095:2的幂次方

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1095

Problem description:

Every positive number can be presented by the exponential form.For example, 137 = 2^7 + 2^3 + 2^0
Let's present a^b by the form a(b).Then 137 is presented by 2(7)+2(3)+2(0). Since 7 = 2^2 + 2 + 2^0 and 3 = 2 + 2^0 , 137 is finally presented by 2(2(2)+2 +2(0))+2(2+2(0))+2(0).

Given a positive number n,your task is to present n with the exponential form which only contains the digits 0 and 2.

For each case, the input file contains a positive integer n (n<=20000).

Source code:

http://www.cnblogs.com/zpfbuaa/p/6812878.html

Analysis:

思路:找到数字n的二进制最高位,按照格式输出相应的括号以及数字2。之后这个最高位的位置假设为i,那么同样需要对i进行上述操作,找到i的二进制最高位,输出‘+’以及括号和数字2。

递归出口:当n==1 or n==0 时,程序结束进一步递归调用。对于括号的输出以及'+’的输出,当n是第一个操作数时,不输出'+',其他的操作数均需要输出'+',对于数字2都要输出,对于'('只有当操作数不为1时才输出,因为题目中除了1之外其余的均输出0,同时对于')'也是只有当操作数不为1时才输出。当n==0时,需要输出最小的数字0.

void print(int n){
    if(n==1) return;
    if(n==0){
        printf("0");
        return;
    }
    bool first = true;
    for(int i = 31 ; i >= 0 ; i--){
        if(((n>>i)&1)==1){//find the hightest pos
            if(first){
                first = false;
            }
            else {
                printf("+");
            }
            printf("2");
            if(i!=1){
                printf("(");
            }
            print(i);//not until i==0 || i==1
            if(i!=1){
                printf(")");
            }
        }
    }
}

题目1099:后缀子串排序

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1099

Problem description:

对于一个字符串,将其后缀子串进行排序,例如grain
其后缀子串有:grain, rain, ain, in, n
然后对各子串按字典顺序排序,即:
ain, grain, in, n, rain

Source code:

http://www.cnblogs.com/zpfbuaa/p/6813000.html

Analysis:

给定字符串只是对后缀字符串进行字典序排序操作。因此只需要将该字符串的后缀字符串找出然后排序即可。
子串函数substr(startIdx,len),可用于后缀字符串的不断选择substr(i,len-i)
对于长度为len的字符串一共有len个子串(包括自身)。
自定义cmp函数如下所示:

int cmp(const void * a, const void * b){
    string * c = (string* )a;
    string * d = (string* )b;
    return strcmp(c->c_str(), d->c_str());
}

题目1100:最短路径

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1100

Problem description:

N个城市,标号从0到N-1,M条道路,第K条道路(K从0开始)的长度为2^K,求编号为0的城市到其他城市的最短距离。

输入要求:第一行两个正整数N(2<=N<=100)M(M<=500),表示有N个城市,M条道路,接下来M行两个整数,表示相连的两个城市的编号。

输出要求:N-1行,表示0号城市到其他城市的最短路,如果无法到达,输出-1,数值太大的以MOD 100000 的结果输出。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6736482.html

Analysis:

看到题目中的2^k,第一反应就是要写一个二分求幂的函数。同时也考虑到了需要大数存储的问题,接着读题之后发现数值太大的MOD 100000。这句话帮助我们解决了大数存储的问题,现在只是需要一个简单的二分求幂函数即可。

二分求幂的函数如下所示:

int myPow(int b){//pow(a,b) == pow(2,b)
   int ans = 1;
   int a = 2;
   while(b!=0){
       if(b%2==1){
           ans = ans * a % MOD;
       }
       a = a * a % MOD;
       b = b >> 1;
   }
   return ans;
}

其实并没有必要去用二分求幂,简单的使用循环即可。但是不能使用pow函数,因为直接计算肯定会超过存储范围,只有在每一步计算中将结果进行求模才可以避免最终结果的overflow。

并查集操作,可以发现每一个边的长度都是在增加,并且如果之前所有的边的长度相加长度都小于下一条边的长度。因此如果对于x,y这两个点,如果存在一个点k, len(x,k)+ len(y,k)一定小于len(x,y)。这里可以理解为就是两点之间不再是线段最短,而是存在第三点,只有通过这第三点才满足两点距离最短

这里需要说明一下,应该在输入一组数据的时候就进行判断操作,这样就可以确保之前的任何点都可以放到(x,y)之间,如果x,y已经处于同一个集合,那么已经有了最小距离不再计算,使用continue关键字。

如果在x,y不处于相同的集合,那么在集合x中如果找到一个点j,在集合y中找到一个点k,那么点x到j的距离加上点k到y的距离再加上点x到y的距离就是集合点j到k的距离。也就是说点(x,y)的出现是位于两个不同的集合,作为这两个集合的中间连接线存在,可以不断更新两个集合内任意两点之间的距离。因为前面说过了三点的距离大于后面两点的距离。

之后需要将x,y两个对应的集合进行合并操作,因为点(x,y)的存在为这两个集合提供了连接。通过简单的代码tree[b]=a就可以完成两个集合的merge。

如果仔细考虑这道题目的数据的话,起始并不符合实际情况,比如这个数据:
3 3
0 1
1 2
2 0

按照题目的含义,那么存在3个城市,城市0到城市1距离为2^0=1,城市1到城市2距离为2^1=2,城市2到城市1的距离为2^2=4。那么如何在平面上表示出这三个点呢?如果存在这种情况,那么就是相当于有一个三角形,其中边长分别为a=1,b=2,c=4。但是通过三角形存在定理:任意两边之长大于第三边,任意两边之差小于第三边。这种情况是不存在的。

哈哈哈,当然了上面的城市肯定不是一个三角形了,因为实际道路怎么可能是一个线段呢。城市之间的道路曲曲折折,人生的道路也是如此。也许到达另一个目标的距离不如先到达另一个目标,然后再实现下一个目标。头一次这么晚还有如此清晰的思考,甚至扯到了人生。可能是最近诸事不顺吧,的确人生道路也是曲折的。

题目1101:计算表达式

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1101

Problem description:

对于一个不存在括号的表达式进行计算。

输入要求:存在多种数据,每组数据一行,表达式不存在空格

输入要求:输出计算结果

Source code:

Analysis:

由于不存在括号,只有'+'、'-'、'*'、'/',四种运算符,并且乘除运算等级最高,加减运算等级最低。
为了简化计算,可以将所有的数字都保存到栈中,在保存到栈中之前先将运算等级最高的降低为加减,但是这样依旧不能解决最后的加减运算。因此可以将遇到减去一个数时,可以讲这个数变为0-num,放入栈中。
最后只需要将栈中所有的数字经行求和运算即可,不需要经行减法运算。

注意:每次输入输入之前需要将栈清空:

while(!myStack.empty()) myStack.pop();

输入数据放入栈中:

while(scanf("%c",&op)!=EOF && op!='\n'){
    int nextNum;
    scanf("%d",&nextNum);
    switch(op){
        case '+':{
            myStack.push(nextNum);
            break;
        }
        case '-':{
            myStack.push(0-nextNum);
            break;
        }
        case '*':{
            int tmp1 = myStack.top();
            myStack.pop();
            myStack.push(tmp1 * nextNum);
            break;
        }
        case '/':{
            int tmp2 = myStack.top();
            myStack.pop();
            myStack.push(tmp2/nextNum);
            break;
        }
        default:
            break;
    }
}

输出计算结果:

int ans = 0;
while(!myStack.empty()){
    int tmp = myStack.top();
    ans += tmp;
    myStack.pop();
}
printf("%d\n",ans);

题目1102:最小面积子矩阵

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1102

Problem description:

一个N*M的矩阵,找出这个矩阵中所有元素的和不小于K的面积最小的子矩阵(矩阵中元素个数为矩阵面积)

输入要求:每个案例第一行三个正整数N,M<=100,表示矩阵大小,和一个整数K。
接下来N行,每行M个数,表示矩阵每个元素的值

输出要求:输出最小面积的值。如果出现任意矩阵的和都小于K,直接输出-1。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6822285.html

Analysis:

暴力求解时间复杂度为O(n^4),由于数据量为n=100,因此不适合使用暴力求解,需要进行优化。

其中在暴力求解的**中,需要枚举所有的矩阵,计算出结果,并记录下最小的面积同时需要设置是否能够存在满足大于k的矩阵。(枚举选择左上角和右下角两个点的坐标,其中每个坐标情况为n*m种,因此时间复杂度为O(n^4))

为了简化问题,并不是选择两个点的坐标,而是确定是在哪两个横线的之间,也就是画两条横线将矩阵切分,取出中间的部分,然后对横线中间的所有行进行求和,可以得到每行的求和结果。接下来只是选择最符合题目的一个即可,由于对两条横线的选择存在n*n的选择,并且在寻找最优的一个矩阵也就是最大连续子序列需要时间为线性时间n,因此最终的时间复杂度为O(n^4).

题目1103:二次方程计算器

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1103

Problem description:

设计一个二次方程计算器
输入要求:每个案例是关于x的一个二次方程表达式,为了简单,每个系数都是整数形式。

输出要求:每个案例输出两个实数(由小到大输出,中间由空格隔开),保留两位小数;如果无解,则输出“No Solution”。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6822359.html

Analysis:

思路:原本的想法是按照之前写过的计算器进行求解,题目中没有说是否有小括号等其他运算符,这一点存在着写疑问,也算作题目没有说明清楚。通过了解通过的代码得知并不需要进行小括号等其他的判断。简单包括加号+,等号=,负号-,指数符号^

如果使用栈进行计算,但是使用栈需要申请空间较多,需要一个保存等号左边的二次方系数的栈,还需要保存等号左边一次方系数的栈,当然还有保存等号左边常数的栈,因此对于等号右侧相同。那么一共就需要申请6个栈。但是经过对题目的符号确定,只需要声明6个int类型的变量即可。
其中变量声明如下,其中二次方系数记为为a,一次方系数为b,常数为c

int leftA, rightA;
int leftB, rightB;
int leftC, rightC;

计算等式一侧的a,b,c的系数函数如下:

void cal(string str, int &a, int &b, int &c){
    int len = (int)str.size();
    int i = 0;
    for( ; i < len ; i++){
        if(str[i]=='+') continue;
        else if(str[i]=='-'){//不操作,可能为负系数
        }
        else if(str[i]=='x'){// a或者b的系数为1或者-1
            if(i+1 < len && str[i+1]=='^'){
                if(i-1>=0 && str[i-1]=='-'){
                    a-=1;
                }
                else{
                    a+=1;
                }
                i+=3;//由于x^2 占3个字符,因此更新为i+=3
            }
            else{
                if(i-1>=0 && str[i-1]=='-'){
                    b-=1;
                }
                else{
                    b+=1;
                }
                i+=1;//x占1个字符,因此更新为i+=1
            }
        }
        else{//a,b系数不为1或者-1,但可能为负数 c为常数
            int tmp = 0;
            int j = i;//保存进入时的位置,用于判断是否为负数
            while(isdigit(str[i])){//数字
                tmp = tmp*10 + str[i]-'0';
                i++;
            }
            if(j-1>=0 && str[j-1] == '-'){//获取得到负数
                tmp = 0 - tmp;
            }
            if(i < len && str[i]=='x'){
                if(i+1 i+=3
                }
                else{
                    b+=tmp;
                    i+=1;
                }
            }
            else{
                c+=tmp;//更新常数
            }
        }
    }
}

题目1104:整除问题

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1104

Problem description:

给定n,a求最大的k,使n!可以被a^k整除但不能被a^(k+1)整除。
输入要求:两个整数n(2<=n<=1000),a(2<=a<=1000)
输出要求:一个整数

Source code:

http://www.cnblogs.com/zpfbuaa/p/6706831.html

Analysis:

考虑到n!a^k运算结果可能会overflow,所以不能用求余数判断是否能够整除。
如果不能使用求余数判断是否整除,那么需要考虑其他的方法。
如果a能整除b即(b%a==0),那么可以将a进行质因数分解a = x1^e1 * x2^e2 * x3^e3 ... * xn^en,同时将b进行质因数分解。那么b一定包含a中所有的质因数,并且b中的每一个质因数的指数大于等于a中的相应指数。
e(i)>=e'(i)k => k <= e(i)/e'(i)。要使所有的i使不等式都成立,只需求出最小的k即可。




接下来就是对a和n!进行质因数分解工作。
试着考虑n!中含有素因数p。n!中包含了从1到n内所有整数的乘积。每个p的倍数(包括p本身)都对n!至少贡献了一个p因子。
1到n中p的倍数的个数是n/p个!!
所以贡献一个p因子的整数的个数至少为n/p。
那么贡献2个p因子,就至少为n/p*p,3个p因子为n/p^3。。。。。。
对于2个p因子,原本应该算幂指数加2的,但是因为前面被一个p因子的已经计算过了一次,所以加1即可。其余多因子的也一样。
这样就能计算出n!的各素因数的幂。

题目1111:单词替换

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1111

Problem description:

输入一个字符串,以回车结束(字符串长度<=100)。该字符串由若干个单词组成,单词之间用一个空格隔开,所有单词区分大小写。现需要将其中的某个单词替换成另一个单词,并输出替换之后的字符串。

输入要求:多组数据。每组数据输入包括3行,
第1行是包含多个单词的字符串 s,
第2行是待替换的单词a,(长度<=100)
第3行是a将被替换的单词b。(长度<=100)
s, a, b 最前面和最后面都没有空格.

输出要求:每个测试数据输出只有 1 行,将s中所有单词a替换成b之后的字符串。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6769869.html

Analysis:

分析题目,题目中的单词定义为,以空格为分割的字符串为一个单词。其中第一个单词只有后置空格,最后一个单词只有前置空格。因此对于单词的替换需要进行多整个字符串进行查找操作,但是需要判断找到的是否满足是一个单词。

判断过程如下所示:

int lenstr = (int)str.size();
int lena = (int)a.size();
int pos = (int)str.find(a,0);
while(pos!=string::npos){
    if((pos!=0 && str[pos-1]!=' ') || (pos+lena < lenstr && str[pos+lena]!=' ')){
        pos = (int)str.find(a,pos+1);
        continue;
    }
    str.replace(pos,lena,b);
    pos = (int)str.find(a,pos+1);
}

题目1112:拦截导弹

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1112

Problem description:

某国为了防御敌国的导弹袭击,开发出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭,并观测到导弹依次飞来的高度,请计算这套系统最多能拦截多少导弹。拦截来袭导弹时,必须按来袭导弹袭击的时间顺序,不允许先拦截后面的导弹,再拦截前面的导弹。

输入要求:每组输入有两行:
第一行,输入雷达捕捉到的敌国导弹的数量k(k<=25);
第二行,输入k个正整数,表示k枚导弹的高度,按来袭导弹的袭击时间顺序给出,以空格分隔。

输出要求:每组输出只有一行,包含一个整数,表示最多能拦截多少枚导弹。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6757535.html

Analysis:

先介绍最长递增子序列,从所给数组中,选择若干数组成新的序列,并且新的序列中每个元素的相对顺序与原来的相同。如何求出最长递增子序列?从头开始遍历,第一个数字的长度标记为1,对于第二个数字,和其前面的数进行比较大小,如果比前面的数字大,则在原来数字长度的基础上加1,如果小与前面的数字则置为1。后面的每个数字都进行和其前面所有数字进行比较操作,得到a[i]=max{1,a[j]+1}(i>j && a[i]>a[j])。 举例说明:


相应的本题目要求的是最长非递增子序列。因此只需要将判断条件修改为a[i]=max(1,a[j]+1)(a[i]<=a[j] && i>j)即可。

题目1120:全排列

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1120

Problem description:

给定一个由不同的小写字母组成的字符串,输出这个字符串的所有全排列。

我们假设对于小写字母有'a' < 'b' < ... < 'y' < 'z',而且给定的字符串中的字母已经按照从小到大的顺序排列。

输入要求:输入只有一行,是一个由不同的小写字母组成的字符串,已知字符串的长度在1到6之间。

输出要求:输出这个字符串的所有排列方式,每行一个排列。要求字母序比较小的排列在前面。

字母序如下定义:

已知S = s1s2...sk , T = t1t2...tk,则S < T 等价于,存在p (1 <= p <= k),使得s1 = t1, s2 = t2, ..., sp - 1 = tp - 1, sp < tp成立。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6755032.html

Analysis:

全排列问题,按照字典序递增输出结果。使用数组保存原始输入数据。需要注意题目指出:所给字符串的字母已经按照从小到大的顺序排列。因此为了寻找所有的排列组合,需要不断可以从小到大进行遍历操作。

为了保存每个排列组合,需要另外的数组int ans[MAX_SIZE];保存每次的一个排列组合。

另外回溯法需要不断进行满足边界条件时的回溯。因此当每次遍历使用一个字母之后需要将其对应的位置设置为已经使用。需要数组int used[MAX_SIZE];。回溯时需要将最后加到ans数组的字母重新设置为未访问。

char str[MAX_SIZE];
char ans[MAX_SIZE];
bool used[MAX_SIZE];
int len;
void prem(int x){
   if(x == len){
       ans[x]='\0';
       printf("%s\n",ans);
       return;
   }
   for(int i = 0 ; i < len ; i++){
       if(!used[i]){
           used[i] = true;
           ans[x] = str[i];
           prem(x+1);
           used[i]=false;
       }
   }
}

题目1131:合唱队形

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1131

Problem description:

N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学不交换位置就能排成合唱队形。 合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1, 2, …, K,他们的身高分别为T1, T2, …, TK, 则他们的身高满足T1 < T2 < … < Ti , Ti > Ti+1 > … > TK (1 <= i <= K)。

你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

输入要求:输入的第一行是一个整数N(2 <= N <= 100),表示同学的总数。
第一行有n个整数,用空格分隔,第i个整数Ti(130 <= Ti <= 230)是第i位同学的身高(厘米)。

输出要求:可能包括多组测试数据,对于每组数据,
输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6757802.html

Analysis:

按照题目要求是要找到一个身高组合,身高从左右两侧往中间递增。并且这个身高组合的实现需要基于原来的升高排序并且要求移出的人数最少。

怎样才能做到移出的人数最少?要达到这个目标也就是要求留下的同学数量最多。如果对于一个已经确定结果的沈国组合来说,设置中间身高最高的同学位置为k。那么从i=0到i=k之间满足升高递增。并且从i=k到i=n-1满足身高递减。为了统一问题,后面一种可以化为从i=n-1到i=k满足升高递增。

这样问题就是求出从左向右满足升高递增以及从右向左满足升高递增的最大和位置。
当得到最大位置时,留在队列中的同学人数等于当前位置从左至右的递增人数加上从右至左到当前位置的递增人数然后再减去1。设此时人数为num

因此此时可以实现移出人数最少,移出人数最少为n-num。

通过上述分析,需要编写从左向右以及从右向左的最长递增子序列的代码:

\#define MAX_SIZE 101
int height[MAX_SIZE];
int l2r[MAX_SIZE];
int r2l[MAX_SIZE];
for(int i = 0 ; i < n ; i ++){
    scanf("%d",&height[i]);
    l2r[i]=1;
    r2l[i]=1;
}
for(int i = 0 ; i < n ; i++){
    for(int j = 0 ; j < i ; j++){
        if(height[i]>height[j]){
            l2r[i]=max(l2r[i],l2r[j]+1);
        }
    }
}
for(int i = n-1 ; i >= 0 ; i--){
    for(int j = n-1 ; j > i ; j--){
        if(height[i]>height[j]){
            r2l[i]=max(r2l[i],r2l[j]+1);
        }
    }
}

题目1137:浮点数加法

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1137

Problem description:

求2个浮点数相加的和,题目中输入输出中出现浮点数都有如下的形式:
P1P2...Pi.Q1Q2...Qj
对于整数部分,P1P2...Pi是一个非负整数
对于小数部分,Qj不等于0

输入要求:对于每组案例,第1行是测试数据的组数n,每组测试数据占2行,分别是两个加数。
每组测试数据之间有一个空行,每行数据不超过100个字符

输出要求:每组案例是n行,每组测试数据有一行输出是相应的和。
输出保证一定是一个小数部分不为0的浮点数

Source code:

http://www.cnblogs.com/zpfbuaa/p/6719293.html

Analysis:

通过之前的高精度练习,构造满足本题目的高精度结构体struct fld,其中fld为结构体名称float long add。进行初始化函数编写,然后重载运算符,进行相应逻辑运算,最后打印输出最终结果。

根据题目要求,可以得到该结构体需要存储整数部分p[MAX_SIZE]以及小数部分q[MAX_SIZE],同时要记录下整数部分的长度pSize以及小数部分的长度qSize。

初始化操作,将整数部分以及小数部分初始化为0,并且初始化整数部分长度和小数部分长度均为0。
题目指出所给浮点数均为正浮点数,因此不需要考虑浮点数减法操作。
重载加法运算符的函数体为fld operator + const(fld &a) const { return ret }。并且返回类型为fld

按照加法规则,应该从小数部分最末尾开始进行加法操作,并且需要将加法的结果保存在另外一个fld类型的变量中,同时此时的进位carry需要声明的范围需要涵盖到整数部分相加,因为小数部分相加之后可能带来整数部分的进位。
声明变量fld ret来保存返回结果。小数部分相加时,需要更新保存小数部分q,于此同时需要更新保存小数部分数据的长度。

整数部分相加,和小数部分相加相同,但是这里存储小数部分和整数部分的顺序不相同。小数部分按照正序存储,也即是0.123 存储.123 。但是对于456.的整数部分存储为654. 同时需要注意整数部分的最后一个进位需要判断是否等于0,如果不等于0,那么需要将这个进位保存下来。

输出结果需要注意0的输出个数。整数部分倒序输出,小数部分正序输出。整数部分需要将从最高位开始为0的均抛弃掉。如果整数部分的只有一位,并且为0,那么需要输出这个0.除此之外只输出非最高位相邻的0。

输出整数部分

int i = pSize - 1;
while(p[i]==0 && i>=0 ) i--;//移除高位的0
if(i==-1){
  printf("0");
}
else{
  while(i>=0){//倒序输出整数部分
  	printf("%d",p[i--]);
 }
}

输出小数部分

int j = qSize - 1;
while(q[j]==0 && j>=0) j--;//移除小数部分低位0,并保存小数部分有效长度
if(j!=-1){
  printf(".");//打印小数点
  int k = 0;
  while(k <= j){//输出小数部分
  	printf("%d",q[k++]);
  }
}

另外还需要函数来将整数部分以及小数部分分别按照逆序和正序存储在结构体类型fld的变量中,并同时保存下整数部分的长度以及小数部分的长度
按照上述方法同样可以解决一些其他的高精度问题。

题目1144:Freckles

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1144

Problem description:

题目大致意思:平面中给出n个点(x1,y1),(x2,y2)...(xn,yn)。求出将所有的点连接起来消耗最少的墨水。

输入要求:第一行为n,表示有n个点。接下来有n行,每行有x,y两个浮点类型的值。

输出要求:输出将所有点连接起来的总长度,结果保留小数点后两位。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6731112.html

Analysis:

其实也是一个最小生成树问题,主要是首先需要自己求出所有的点之间组成的边的长度。然后遇到的问题就是如何标记其中某一条边。

因为之前的都是给出了某个城市的编号,但是现在只是给出了点的坐标,因此可以构造一个点的结构体,同时可以计算出点之间的距离保存起来,并且给每一个点都进行编号。

这样就可以有n个点的编号,然后利用并查集操作。查找某条边的两个顶点是否属于同一个集合,如果不是同一个集合那么需要进行其中一个点移入另一个集合中。

struct Edge{ // define the edge which have line  a and  line b
   int a, b;
   double cost; // the length of point a to point b
   bool operator < (const Edge &A) const{
       return cost < A.cost;
   }
};

struct Point{ //define the point
   double x, y; //(x,y) the position of this dot
   double getDistance(Point A){ //get the lenght between (x,y) and (A.x, A.y)
       double tmp = (x-A.x)*(x-A.x) + (y-A.y)*(y-A.y);
       return sqrt(tmp);
   }
};

获取每个点到另外n-1个点的距离。

int line_id = 0;//define the id of the line
for(int i = 1 ; i <= n ; i++){
	for(int j = i+1 ; j <= n ; j++){
  	edge[line_id].a = i;
  	edge[line_id].b = j;
  	edge[line_id].cost = point[i].getDistance(point[j]);
  	line_id++;
  }
}

题目1153:括号匹配问题

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1153

Problem description:

在某个字符串(长度不超过100)中有左括号、右括号和大小写字母;规定(与常见的算数式子一样)任何一个左括号都从内到外与在它右边且距离最近的右括号匹配。写一个程序,找到无法匹配的左括号和右括号,输出原来字符串,并在下一行标出不能匹配的括号。不能匹配的左括号用 "$"标注,不能匹配的右括号用"?"标注.

输入要求:输入包括多组数据,每组数据一行,包含一个字符串,只包含左右括号和大小写字母,字符串长度不超过100。

输出要求:对每组输出数据,输出两行,第一行包含原始输入字符,第二行由"$","?"和空格组成,"$"和"?"表示与之对应的左括号和右括号不能匹配。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6683106.html

Analysis:

括号匹配问题,使用栈来解决。题目要求不匹配位置的括号输出对应的字符。其中当左括号不匹配时,输出'$',右括号不匹配时输出'?'。因此不仅仅是之前的判断括号匹配是否合法,而是需要记录下不匹配位置,用于结果的输出。

之前用Java写过一次,当时的做法是用两个栈,一个用于保存左括号不匹配的下标,另一个保存右括号不匹配的下标。并且在输出的时候,不是简单的判断,因为左括号不匹配以及右括号不匹配时输出的字符不相同。判断过程相对较为复杂。借助了两个list,每次输出都要判断是否存在于对应的list中,存在于左括号的list时,则输出字符为'$',存在于右括号时输出字符为'?'

可以看出这样做很复杂,原因是将左括号和右括号区分太明显了,甚至为了保存其不匹配位置各自申请了一个栈。那么可以换一种思路,之前是保存下来了不匹配的位置,现在可以换做保存左括号和右括号匹配的位置,或者在其匹配的位置上做标记。

通过上述分析,我们可以将通过一个栈以及一个数组完成上述问题。栈依旧用来判断括号匹配,并且保存着当前左括号的位置,另外的一个数组足够大,对应着输入字符串的每个位置。初始化这个flag数组为0,在括号匹配过程中,如果一个左括号和右括号匹配,那么我们可以修改对应左括号位置和右括号位置的数值,来标记出已经匹配的括号的位置。这个标记只发生在遇到一个右括号并且保存左括号位置的栈不为空时,才能够通过循环的变量i以及栈顶元素stack.top(),分别得到右括号匹配位置以及左括号匹配位置。因为都是匹配位置,所以只是两者的共同点。

在输出最终结果时,需要判断遇到的字符:

  1. 如果遇到是左括号'(',那么进一步判断该位置是否标记为已经匹配,如果匹配则输出空格' ',如果不匹配则输出'$'.
  2. 如果遇到是右括号')',那么进一步判断该位置是否标记为已经匹配,如果匹配则输出空格' ',如果不匹配则输出'?'.
  3. 其他字符军输出空格' '

题目1161:Repeater

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1161

Problem description:

给定一个模板,根据输入的迭代层数,利用模板图形输出第n次迭代后的图形。

举例:

给定模板如下

# #
 #
# #      

Level 3 picture will be


Source code:

http://www.cnblogs.com/zpfbuaa/p/6680422.html

Analysis:

观察规律,如何从模板得到第n层的输出。将模板看成一个n*n的矩阵。比较模板和第2次迭代之后的图形,首先第2层可以看做为n^2 * n^2 的矩阵。同样第3次迭代之后的图形可以看做n^3 * n^3 的矩阵。

再次比较模板和第2次迭代的图形。我们把第二次迭代的图形化为多个n * n 的小矩阵,并且划分的最小矩阵等于模板的大小。这些n * n的小矩阵中有的矩阵和模板相同,有的是n * n的空矩阵。这些小矩阵究竟什么时候等于模板呢?什么时候等于空矩阵呢?可以发现,模板的一个位置正好对应了第二次的迭代相应位置的n * n的小矩阵。

发现上述规律之后,那么第三次迭代的图形,就可以看作是模板为第二次迭代的图形。因此为了输出最后的结果,我们可以提前把每个位置的值提前保存到一个足够大的二维数组中。

怎样才能得到上述保存着最后结果的二维数组呢?首先我们去看一下原题目,在原题目中给定了输出结果最长小于等于3000,因此二维数组大小可以设置了。为了不断进行迭代操作,需要记录下上次迭代的结果(作为下次迭代的模板),并且需要记录下上次迭代的模板的矩阵边长。因此需要一个大小不小于3000*3000的额外矩阵保存上次迭代结果,同时需要一个int变量保存每一次迭代后的长度。可以发现迭代k次之后边长为pow(n,k)。

通过上述分析,可以利用给定的模板,以及所指定的迭代层数得到最终的结果。

题目1162:I Wanna Go Home

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1162

Problem description:

题目大致意思为现在有两大帮派,每个城市分别支持两个帮派中的一个。现在要从城市a到城市b,中间可能需要穿过其他的城市,要求跨越帮派的道路不能超过一条。也就是不能来回跨越不同的帮派。

输入要求:有多组数据,每组数据第一行为n取值范围为[2,100],其中n表示城市的个数,城市编号从0到n-1。

第二行为m取值范围为[0,100],其中m表示道路总条数。

接下来的m行分别为a,b,t。其中a,b,t分别为城市a,城市b,以及从城市a到城市b的距离。

最后有n个正数,其顺序分别表示第i个城市所支持的阵营的编号。(城市编号从0到n-1,阵营编号只有1或者2)。

输出要求:输出最短路径的长度。如果不存在满足题目要求的最短路径,输出-1。每组数据加换行。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6738642.html

Analysis:

最短路径的变形,可以使用dijkstra算法。由于题目要求不允许来回穿越不同的阵营,因此在更新最短路径的时候要判断是否多次穿过两个阵营。

因此除了之前求解最短路径问题所需要保存的每条边长度信息的二维数组edge外,还需要另外的数组保存每个城市所支持的阵营编号。
下面为dijkstra函数实现:

void dijkstra()
{
   int dis[601];
   bool mark[601] = {false};
   for(int i = 0 ; i < n; i++)
       dis[i] = grah[0][i];
   int s = 0;
   mark[s] = true;
   dis[s] = 0;
   int newP = s;
   for(int i = 1 ; i <= n ; i++){
       for( int j = 0 ; j < n ; j++){
           if( !mark[j] && grah[newP][j] < MAX && !(sup[newP] == 2 && sup[j] == 1)){
               if(dis[j] > dis[newP] + grah[newP][j])
                   dis[j] = dis[newP] + grah[newP][j];
           }
       }
       int min = MAX;
       for(int j = 0 ; j < n ; j++){
           if( !mark[j] && min > dis[j]){
               min = dis[j];
               newP = j;
           }
       }
       mark[newP] = true;
   }
   if(dis[1] < MAX) printf("%d\n",dis[1]);
   else  printf("-1\n");
}

题目1168:字符串的查找删除

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1168

Problem description:

给定一个短字符串(不含空格),再给定若干字符串,在这些字符串中删除所含有的短字符串。

输入要求:输入只有1组数据。输入一个短字符串(不含空格),再输入若干字符串直到文件结束为止。

输出要求:删除输入的短字符串(不区分大小写)并去掉空格,输出。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6767977.html

Analysis:

两种方法,相同点均为保存下短字符串,不同点为:第一种方法为逐个读入字符,进行匹配判断;第二种方法为存储全部的字符,然后进行查找操作。

第一种方法需要记录下当前字符是否和短字符匹配,以及匹配的长度。如果匹配则不输出,如果不匹配那么需要输出当前的字符,并且重置匹配长度为0。

第二种方法为保存字符,然后利用string中提供的find函数,以及string::npos,不断查找满足条件的字符,然后利用erase函数,擦除长度等于短字符长度的字符个数。

题目1198:a+b

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1198

Problem description:

实现一个加法器,使其能够输出a+b的值。
输入要求:输入包括两个数a和b,其中a和b的位数不超过1000位。
输出要求:可能有多组测试数据,对于每组数据,输出a+b的值。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6718697.html

Analysis:

高精度的实现,按照加法原则,从低位到高位,不断进行相应位相加并进行进位操作。由于数据较大,可以用char数组保存每一位的数据。
倒序遍历两个数组a,b并将每一位相加的结果保存在第三个int数组c中。如果数组b遍历结束之后,数组a依旧有剩余的元素,那么需要将数组a中剩余的元素添加到数组c中。注意到数组c中保存的数据是从低位到高位保存着的,因此在输出最终结果的时候需要倒序输出数组c。
个人的疑问:题目中没有指出是两个正数相加,但是提交的代码只考虑了两个大于等于0的数相加。如果其中一个数为负数那么最终计算结果将会出错。
如果真正的要实现高精度的a+b,那么需要考虑a和b的正负号。
首先判断a,b的正负号,flag1和flag2分别标记a,b的正负号,正数为true,负数为false. flag1 = (a[0]>='0' && a[0]<='9') ? true : false;
flag2 = (b[0]>='0' && b[0]<='9') ? true : false;
下面有四种情况:

  1. a为正数,b为正数;
  2. a为正数,b为负数;
  3. a为负数,b为负数;
  4. a为负数,b为负数;
    对于第1种和第4种情况,需要注意循环时数组的结束条件不同,第一种情况为i>=0&&j>=0,而第4种情况为i>=1&&j>=1。在计算是同样按照进位相加,只是最后计算结果是否添加符号的区别。
    对于第2种和第3种情况,可以看做是同一类型的问题。首先要确定最终计算结果的正负号,可以借助数组的长度进行判断(注意保存负数的那个数组长度需要减1);最终结果与数组长度较长的保持一致。
    如果长度一致,那么需要正序对每一位进行比较,最终结果要么是每一位都相同,那么最终结果为0,要么是最终结果为负数,要么最终结果为正数。
    相对复杂的就是正数和负数相加,需要进行借位操作。
    上述讨论仅是考虑到没有指出两个数均为大于等于0的数。
    如果要进一步讨论那么就更加复杂了,比如包含小数点的a+b。包含小数点的输出结果判断将会复杂许多,比如小数点前面都是0的时候,只输出紧挨小数点的一个0等等。

题目1208:10进制 VS 2进制

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1208

Problem description:

对于一个十进制数A,将A转换为二进制数,然后按位逆序排列,再转换为十进制数B,我们乘B为A的二进制逆序数。

例如对于十进制数173,它的二进制形式为10101101,逆序排列得到10110101,其十进制数为181,181即为173的二进制逆序数。

输入要求:一个1000位(即10^999)以内的十进制数。

输出要求:输入的十进制数的二进制逆序数。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6721459.html

Analysis:

思路:
第一步将十进制数转换为二进制数并保存起来。

第二步逆序保存转换的二进制数。

第三步按照二进制定义转换为对应的十进制,得到二进制逆序数。

考虑到输入的十进制数字长度取值范围为[1,1000],因此需要使用数组对其进行保存。

当考虑到取值范围时同时也要注意到十进制转为二进制之后的长度会比1000位要长,因此数组空间大小的申请需要足够大以保存转换后的二进制数。第一次提交WA,就是因为保存二进制的数组大小只申请了1010。

利用任意进制转化方法完成从十进制到二进制的转换。这里的方法在题目1080:进制转换已经介绍了。

其实就是从笔算进制转换得到的规律。每一次都将m进制数的每一位按照笔算过程进行到n进制的转换。当一次转换之后会得到m进制到n进制转换的一位,并且这一位是转换结果的最后一位。意思就是转换得到的顺序是从低位到高位的。

按照上述方法可以完成从m进制到n进制的转换。更不用说从10进制到2进制的转换。同时如果按照上述转换的顺序来保存转换结果那么所保存的恰好是题目中的逆置二进制。

接下来需要完成从上述保存的逆置二进制转为二进制逆序数。也就是从二进制转换为十进制。

将二进制数1011转换为10进制数的过程。

1011 --> ( ( (0 * 2 + 1) * 2 + 0) * 2 + 1) * 2 + 1 = 11

按照上述的过程可以完成对二进制数到十进制数的转换。但是需要注意到数据量很大无法直接保存在long long中,需要将转换结果的每一位保存在数组中。因此这里还需要完成对进位操作。

int length = 1;
ans[0] = 0;//最内层为0*2 因此ans[0] = 0
for(int i = 0; i < size ; i++){//1011 --> (((0*2+1)*2+0)*2+1)*2+1
  int carry = to[i] - '0';
  for(int j = 0 ; j < length ; j++){
  	if(j==0)
  		ans[j] = ans[j] * TO + carry;//只需要加一次即可,也就在最低位加carry即可
     else
     	ans[j] = ans[j] * TO;
     }
     for(int j = 0 ; j < length ; j++){//进位操作
     	if(j == length - 1 && ans[j] >=10){
        	ans[j] = ans[j]%10;
        	ans[++j] = 1;//这里最大的ans[j]=2*9+1 = 19因此进位只能是1
        	length++;
        }
        else if (ans[j]>=10){
        	ans[j] = ans[j]%10;
        	ans[j+1]++;//产生进位的只能是1
        }
  	}
  }
}

按照上述方法解决题目中的求二进制逆序数。

题目1438:最小公倍数

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1438

Problem description:

给定两个正整数,计算这两个数的最小公倍数。

输入要求:输入包含多组测试数据,每组只有一行,包括两个不大于1000的正整数。

输出要求:对于每个测试用例,给出这两个数的最小公倍数,每个实例输出一行。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6691681.html

Analysis:

求两个数的最小公倍数按照平常笔算的话,应该是利用短除法,短除法就不说怎么做了。最小公倍数就输乘一圈,最大公约数只乘一竖。利用最大公约数得到最小公倍数即a*b/gcd(a,b)
这里说明一点当求解两个数的最大公约数时,可能遇到下面三种情况:

  1. 两个数都是0,没有最大公约数
  2. 其中一个为0,则最大公约数为不为0的那个数
  3. 两个数均不为0,则使用自调用b->a a%b->b 即求解gcd(a,b) = gcd(b,a%b)
    证明见下图:


题目1439:Least Common Multiple

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1439

Problem description:

求m个正数的最小公倍数。

输入要求:第一行输入n,表示共有n组数据。接下来的的n行,每一行的第一个数字m表示一共之后输入m个正数,求这m个正数的最小公倍数。所有数字位于32位整数范围内。

输出要求:每组数据输出最小公倍数,带换行。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6691393.html

Analysis:

先看两个正数的最小公倍数的求解过程。通过求出两个数的最大公倍数gcd(a,b)。然后通过a*b/gcd(a,b)即可得到a和b的最小公倍数。
但是考虑到题目中的数据可能会超过存储范围,因此可以先计算除法然后再计算乘法即a/gcd(a,b)*b
本题目可以采用输入域求解同时进行,也就是每输入一个数字就进行一次最小公倍数的求解。由于题目描述的不明确,没有采用数组存储所有的输入之后进行遍历的求解方法,反倒是激发自己选择占用空间更小的方法来完成本题目。题目只是说明了所有的数字都在32位整数范围内,但是如果申请如此大的空间来保存输入是不切实际的,同样题目所测试的数据量也不会那么大。但是由于不清楚具体测试数据量的大小,还是采用变输入变求解的方法比较稳。

题目1440:Goldbach's Conjecture

Jobdu Link:

[http://ac.jobdu.com/problem.php?pid=1440]

Problem description:

一个大于等于4的合数可以拆分为两个素数之和。当然这道题不是让我们去证明这个猜想的。只要求找出一个合数,一共有多少中不同的拆分方式,满足合数n=素数a+素数b。其中组合(a,b)和组合(b,a)是同一种组合。
输入要求:多组数据,输入一个大于等于4并且不超过2^15的数,当输入0时结束。
输出要求:输出对应的组合总数。每组数据加换行。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6701440.html

Analysis:

合数也就是所谓的偶数,但是这里大于等于4,因此2不再这个范围内。哥德巴赫猜想,一个大于等于4的合数总可以拆分为两个素数之和。因此需要额外的函数进行素数的判断。
要求出所有的组合,只需要从小打到进行遍历即可,并且由于组合(a,b)和组合(b,a)是同一种组合,因此在遍历过程中,不需要从头遍历至尾。只需要遍历所给合数的一半即可。同时遍历初始化的i并不是2,因为2是偶数,所给的合数大于等于4因此最小i应该从3开始。
除上述之外,循环遍历的步长不是1,而是2。因为是从3开始,3是一个素数,每次加2一定跳过了偶数部分,可以减判断次数,防止超时。

题目1441:人见人爱 A ^ B

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1441

Problem description:

求A^B的最后三位数表示的整数。说明:A^B的含义是“A的B次方”
输入要求:输入数据包含多个测试实例,每个实例占一行,由两个正整数A和B组成(1<=A,B<=10000),如果A=0, B=0,则表示输入数据的结束,不做处理。
输出要求:对于每个测试实例,请输出A^B的最后三位表示的整数,每个输出占一行。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6715390.html

Analysis:

可能使用for循环的求幂方法:
int ans = 1;
for (int i = 1;i <= b;i ++) {
   ans *= a; 
}

但是对于本题目中A,B的取值范围为[1,10000]。首先数据结果甚至已经超过了保存范围,其次循环次数较大.
在介绍二分求幂之前看一个简单的例子。如何求2^32?按照上面的for循环需要循环32次,但是注意到在循环到16次时,已经得到了ans = 2^16结果,那么此时的ans * ans 就可以得到2^32。相类似的在得到2^16之前,可以得到2^8,2^4,2^2,2。从而将循环次数从32次降低到了6次(2->2^2->2^4->2^8->2^16->2^32). (注意32的2进制为10000)
也许上面的例子有点特殊,那么换一个再看一次,求3^10? 3^10 = 3^8 * 3^2 (注意10的二进制为1010)
再换一个呢,3^15? 3^15 = 3^8 * 3^4 * 3^2 * 3^1(注意15的二进制为1111)
找到规律了,也就是按照b的二进制逐位进行求解,其实就是将b= 2^k1 + 2^k2 + 2^k3 +...+2^kn
然后a^b = a^(2^k1 + 2^k2 + 2^k3 +...+2^kn)
其中k1,k2,k3,kn的关系就是对应着b的二进制所代表的权重。
因此只要计算出最小的k1那么不断更新当前权重即可。
本题目只要求输出最终结果的最后三位代表的整数,因此计算过程中也只保存最后三位即可。核心代码如下:

while(b!=0){//二进制转换结束条件b==0
   if(b%2==1){//b的二进制当前位为1
       ans = ans * a;
       ans%=1000;
    }
    b/=2;
    a = a * a;
    a%=1000;
}

题目1442:A sequence of numbers

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1442

Problem description:

第一行输入一个整数n,代表接下来有n组数据。
接下来的n行,每一行输入4个正数a,b,c,k.每一行为一组数据,其中a,b,c为一个非递减数列的相邻的前三个元素。并且数列要么是等差数列要么是等比数列中.
k表示要求出该数列的第k个元素。
对于每组数据输出一行,取出第k个元素的值。每组数据加换行。 输入数据:a,b,c取值范围为[0, 2^63), k取值范围为(0,10^9]
输出要求: K-th number module (%) 200907.

Source code:

http://www.cnblogs.com/zpfbuaa/p/6715544.html

Analysis:

等差数列通项公式:an = a1 + (n-1)*d
等比数列通项公式:an = a1 * q^(n-1)
由于数据较大,因此在数据保存中使用long long,并且最终结果要求对200907进行求模,那么在中间计算过程中可以直接进行求模操作,防止数据溢出。
对于等差数列,相邻的三个数a,b,c满足关系式 b-a == c-b
对于等比数列,相邻的三个数a,b,c满足关系式 b/a == c/b
对于本题来说,只需要判断是否为等差数列即可,减法计算所耗时间比除法要低。并且有些数列可能既是等差数列又是等比数列,这样可以将等差数列判断放在前面,如果满足就不需要再进入相对复杂的等比数列的计算。
等比数列计算通过上面的公式an = a1 * q^(n-1)看到可以使用二分求幂的方法,也就是题目1441:人见人爱 A ^ B所给出的方法。
需要注意在计算中需要对200907进行求模操作。可以通过宏定义#define ret 200907来简化.

题目1446:Head of a Gang

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1446

Problem description:

题目大致意思是找到一个团队集合中通话时间最长的(直接以及间接通话),通话时间最长的就是captain.题目给出所有的通话组合(姓名a,姓名b,通话时长t)。以及最低时长k,要求找到满足所有满足上述两个条件的captain。并输出captain的姓名以及该集合团队成员个数。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6728450.html

Analysis:

并查集查找父节点:

int findRoot(int x) {
   while (parent[x] != x) {
       x = parent[x];
   }
   return x;
}

并查集合并结点:

void unionSet(int a, int b) {   
   a = findRoot(a);
   b = findRoot(b);
   if (a == b) return;
   if (a > b) {
       parent[a] = b;
   } else {
       parent[b] = a;
   }
}

接下来需要每个人的通话时间:
由于使用数组保存每个通话时间,因此每个人的姓名不能放在数组中也同时保存通话时间,题目指出姓名唯一不重复,意思就是每个人的姓名是独特的。
那么可以将每个人的姓名进行编号处理,如果有n个人那么可以编号从1到n,那么需要一个map<string,map>来保存这个姓名到数字编号的记录。

将姓名与数字编号进行构造并保存在baseMap中,同时保存下来实际有多少个人在整个大的集合中即变量currNum。

int getCurrentNum(char c[]) {
   int num = 0;
   map::iterator it = baseMap.find(c);
   if (it == baseMap.end()) {
       currNum++;
       num = currNum;
       baseMap.insert(make_pair(c, num));
   } else {
       num = it->second;
   }
   return num;
}

1、如果要使用数组,需要将字母转换为数字,当然最后输出的时候要转换回去。
2、并查集,读取两个name的时候,就合并。
3、求父节点,并且算出集合个数。
4、对每个集合做计算,求成员个数。符合条件,保留,不符合条件,忽略。
5、按照字母序输出结果。

如何进行不同集合的划分也就是怎么去确定一个集合,需要遍历所有的编号(编号的个数前面保存在了currNum中),当其findRoot(x) == x 时,此时的x为一个集合根节点。同时还要保存下来哪些结点编号为父节点(需要数组fatherArr),除此之外还要记录一共有多少个集合即变量tmpk,最终的captain的个数一定小于等于集合的个数。

循环所有的父节点个数为tmpk,下面嵌套循环所有的成员个数为currNum。找到所有集合中通话时间最长的人,并且计算出和其有通话的人的个数。如果满足通话时长大于k并且通话人数大于等于3,那么将该结果保存在nodes中,同时需要保存已经存储的查询结果的个数保存在变量num中。其中nodes为结构体,其结构如下所示:

struct Node {
   char name[4];
   int size;
} nodes[maxn];

然后对最终nodes中保存的数据进行排序,按照字典序从小到大进行排序,可以编写自定义的cmp函数,然后使用C++提供的STL的sort函数,实现对nodes的排序。

bool cmp(Node node1, Node node2) {
   return strcmp(node1.name, node2.name) < 0;
}

最终结果的打印需要先输出满足条件的个数即前面的num。
然后输出姓名+space+通话联系人个数。

题目1447:最短路

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1447

Problem description:

在每年的校赛里,所有进入决赛的同学都会获得一件很漂亮的t-shirt。但是每当我们的工作人员把上百件的衣服从商店运回到赛场的时候,却是非常累的!所以现在他们想要寻找最短的从商店到赛场的路线,你可以帮助他们吗?

输入要求:输入包括多组数据。每组数据第一行是两个整数N、M(N<=100,M<=10000),N表示成都的大街上有几个路口,标号为1的路口是商店所在地,标号为N的路口是赛场所在地,M则表示在成都有几条路。N=M=0表示输入结束。接下来M行,每行包括3个整数A,B,C(1<=A,B<=N,1<=C<=1000),表示在路口A与路口B之间有一条路,我们的工作人员需要C分钟的时间走过这条路。输入保证至少存在1条商店到赛场的路线。

当输入为两个0时,输入结束。

输出要求:对于每组输入,输出一行,表示工作人员从商店走到赛场的最短时间

Source code:

http://www.cnblogs.com/zpfbuaa/p/6734644.html

Analysis:

利用floyd算法,题目所给数据很直接,存储数据时注意当前路口到自身的距离初始化为0,其他的初始化一个特殊的值,标记为不可到达。floyd算法可以求出任意两个点之间的最短距离,在最终结果输出的时候注意选择起止点。

void floyd(){
   for(int k = 1 ; k <= n ; k ++)
   	   for(int i = 1 ; i <= n ; i++)
           for(int j = 1 ; j <= n ; j++)
               if(dist[i][k]==-1 || dist[k][j]==-1) continue;
               else if(dist[i][j]==-1 || dist[i][k]+dist[k][j]< dist[i][j])
                   dist[i][j] = dist[i][k]+dist[k][j];
}

题目1448:Legal or Not

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1448

Problem description:

该例大意为,在一个qq 群里有着很多的关系,如A是B的老师,同时B是A的学生,一个老师可能有很多学生,一个学生也可能会有很多不同的老师。 输入给出该qq群里所有的关系,问是否存在这样一种非法的情况:以三个人为例,即A是B的老师,B是 C的老师,C反过来是A的老师。若我们将该qq群里的所有人都抽象成图上的结点,将所有的关系都抽象成有向边(由老师指 向学生),该实际问题就转化为一个数学问题——该图上是否存在一个环,即判断该图是否为有向无环图。

输入要求:多组数据,每组数据第一行为N,M。其中N表示成员个数,M表示关系个数。接下来有M行,每一行为一组组合(x,y)。其中x是y的老师,y是x的学生。

输出要求:对于每组数据输出关系是否合法,合法则输出"YES",不合法则输出"NO"。每组数据加换行。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6743060.html

Analysis:

一.整体思路:

判断一个有向图是否有环,首先要想到拓扑排序。如果一个有向图存在符合拓扑排序的结点序列,那么该有向图一定无环。吴国不存在符合拓扑排序的结点序列,那么该有向图一定有环。

二.拓扑排序介绍:

因此,现在需要对该有向图进行拓扑排序。拓扑排序操作:

  1. 首先,所有有入度(即以该结点为弧头的弧的个数)的结点不可能排在第一个。那么,我们选择一个入度为 0 的结点,作为序列的第一个结点。当该结点被选为序列的第一个顶点后,我们将该点从图中删去,同时删去以该结点为弧尾的所有边,得到一个新图。

  2. 那么这个新图的拓扑序列即为原图的拓扑序列中除去第一个结点后剩余的序列。同样的,我们在新图上选择一个入度为0的结点,将其作为原图的第二个结点,并在新图中删去该点以及以该点为弧尾的边。这样我们就得到了一个新图,重复同样的方法,直到所有的结点和边都从原图中删去。

  3. 若在所有结点尚未被删去时即出现了找不到入度为0的结点的情况,则说明剩余的结点形成一个环路,拓扑排序失败,原图不存在拓扑序列。

三、需要的变量和操作

  1. 为了统计每次删去的入度为0结点的个数,可以将结点编号保存在一个栈或者队列中。这里的栈或者队列只是起到保存入度为0的作用,不存在FIFO或者LIFO的区别。首先需要保存每个结点的下一个结点编号,因此需要构造一个邻接链表。因此可以使用vector<int> edge[MAX_SIZE],其中edge[i]表示编号为i的结点的后集结点有哪些,这些后继结点均保存在当前edge[i]链表中。

  2. 另外还需要数组保存每个结点的入度情况。声明int inDegree[MAX_SIZE]进行入度情况的保存,其中inDegree[i]保存着结点编号为i的结点的入度大小。

  3. 通过声明queue<int> q;来完成对入度为0的结点的保存。为何要保存入度为0的结点编号呢?

其一是为了统计一共删去的结点个数,用于最终判断是否存在满足拓扑排序的序列。

其二是为了将入度为0的结点的后继结点的入度进行更新操作。因为删去一个结点之后,与其相邻的边也就不存在了,也即是说与这个结点存在相连关系的结点的入度都应当进行减一操作inDegree[edge[nowP][j]]--

  1. 需要变量int ans = 0 ;保存入度为0的结点的个数,每当从栈或者从队列中删除一个入度为0的结点的时候,进行操作ans++操作。

  2. 在从栈或者队列删除一个入度为0的结点时候,需要先保存删除的结点的编号int nowP = q.front()。为了之后进行删除与入度为0相邻结点的入度。

其中nowP表示当前入度为0的结点,j是循环体的循环遍历,循环体为for(int j = 0 ; j < edge[nowP].size() ; j++)。因此inDegree[edge[nowP][j]]--的含义就是将入度为0的nowP结点删去之后,更新与其相邻的所有结点的入度。

注意:

  1. 需要对邻接链表进行初始化清空操作;
  2. 需要对入度数组inDegree进行初始化为0操作;
  3. 需要对队列或者栈进行初始化清空操作;
  4. 每次删除入度为0的结点时需要对统计变量ans++;
  5. 需要在删除入度为0的结点同时,更新所有相邻的结点的入度,同时判断更新后的入度是否为0,如果为0需要将该结点保存到栈或者队列中去。

题目1449:确定比赛名次

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1449

Problem description:

有N个比赛队(1<=N<=500),编号依次为1,2,3,。。。。,N进行比赛,比赛结束后,裁判委员会要将所有参赛队伍从前往后依次排名,但现在裁判委员会不能直接获得每个队的比赛成绩,只知道每场比赛的结果,即P1赢P2,用P1,P2表示,排名时P1在P2之前。现在请你编程序确定排名。

输入要求:输入有若干组,每组中的第一行为二个数N(1<=N<=500),M。其中N表示队伍的个数,M表示接着有M行的输入数据。接下来的M行数据中,每行也有两个整数P1,P2表示即P1队赢了P2队。

输出要求:给出一个符合要求的排名。输出时队伍号之间有空格,最后一名后面没有空格。

其他说明:符合条件的排名可能不是唯一的,此时要求输出时编号小的队伍在前;输入数据保证是正确的,即输入数据确保一定能有一个符合要求的排名。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6744742.html

Analysis:

很明显是一个拓扑排序问题,题目要求输出一个满足要求的拓扑顺序。按照题目所给信息,对于比赛(p1,p2),如果p1赢了p2,那么可以设置有向边为p1指向p2。

题目指出输入数据保证是正确的,这句话的意思就是所给的有向图一定是有向无环图。另外需要注意的是排名的输出时有要求的,要求编号小的队伍排在前面。因此在保存入度为0的结点时需要进一步对结点的编号大小进行排序操作。为了降低时间复杂度,可以利用STL中提供的优先队列。

优先队列的声明可以采用形式:priority_queue< int, vector<int>, greater<int> >myQueue;

由于题目明确指出队伍号之间有空格,最后一名后面没有空格。因此在输出前需要判断当前队伍是否为最后一只队伍。可以用int ans变量保存已经输出的队伍的个数,当ans==n时说明此时为最后一只队伍,不输出空格而是输出换行\n

同样,在对每组数据进行操作之前需要进行初始化操作,清空邻接链表、初始化入度数组为0、清空优先队列

题目1450:产生冠军

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1450

Problem description:

有一群人,打乒乓球比赛,两两捉对撕杀,每两个人之间最多打一场比赛。
球赛的规则如下:
如果A打败了B,B又打败了C,而A与C之间没有进行过比赛,那么就认定,A一定能打败C。
如果A打败了B,B又打败了C,而且,C又打败了A,那么A、B、C三者都不可能成为冠军。
根据这个规则,无需循环较量,或许就能确定冠军。你的任务就是面对一群比赛选手,在经过了若干场撕杀之后,确定是否已经实际上产生了冠军。

输入要求:输入含有一些选手群,每群选手都以一个整数n(n<1000)开头,后跟n对选手的比赛结果,比赛结果以一对选手名字(中间隔一空格)表示,前者战胜后者。如果n为0,则表示输入结束。

输出要求:对于每个选手群,若你判断出产生了冠军,则在一行中输出“Yes”,否则在一行中输出“No”。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6745095.html

Analysis:

题目看起来好长,叙述的很多。明白下面几点就很快明白题目的意思了:

  1. 所给的信息可以组合成一个图,并且该图可能存在环。
  2. 题目问的并不是对于所给的图是否存在一个拓扑序列。
  3. 题目求的是:对于所给的图能不能找到一个冠军。

如果存在一个冠军,那么一定满足可以战胜所有的选手,无论是间接战胜还是直接战胜。并且没有任何一个选手能够间接或者直接战胜他。这就指出了冠军一定满足的条件是:入度为0,并且入度为0的结点个数一定等于1。如果入度为0的结点个数不等于1,那么无法确定冠军是谁。因为无法比较入度为0的结点的取胜结果。

由于题目所给的是人名,为了简化处理以便能够使用int入度数组保存结点的入度信息。可以采用map<string,int> name2id;用来保存人名到id的信息。同时对于id的设置要保证同一个人的id一定唯一,并且不同人的id一定不同。那么可以采用保存当前出现了多少人来设置id。通过变量int ans = 0 ;

第i次出现的人,就将其id设置为i。因此对于每个输入的人名,需要先在map中判断是否已经存在这个名字,如果不存在则ans++;。这样就可以通过int inDegree[MAX_SIZE];来保存每个人的入度信息。

需要注意的是:题目只是给出了有n组对选手,因此选手的个数最多为2*n个。所以在设置inDegree大小的时候需要考虑到这一点。并且对inDegree初始化时一定要将所用的范围都初始化到0。

题目1451:不容易系列之一

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1451

Problem description:

题目大致意思:n张信装到n个信封中,把所有的信都装错的可能情况有多少种?

输入要求:输入数据包含多个多个测试实例,每个测试实例占用一行,每行包含一个正整数n(1<n<=20),n表示8006的网友的人数。

输出要求:对于每行输入请输出可能的错误方式的数量,每个实例的输出占用一行。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6757349.html

Analysis:

错排公式:n个信装到n个信封中,所有的信均装错的可能情况设为f(n)。对于编号为n的信封装入了编号为k的信,而n号信封的信装在了编号为m的信封中。我们按照k和m是否相等将错误情况划分为两种:

第一种情况:k和m相等。也就是说n号信封的信装到了m号信封中。m号信封的信装到了n号信封中。这样将这两个信封的信对调之后,错误的信封有n-2个。也就是要求出f(n-2),而m号信封的选择一共有n-1种。因此此类情况即k==m时,错误情况一共(n-1)*f(n-2)种。

第二种情况:k和m不相等,那么交换n号信封和m号信封中的信之后,错误的信封还有n-1个。也就是要求出f(n-1),因为m号信封的选择一共有n-1种。因此此类情况即k!=m时,错误情况一共(n-1)*f(n-1)种。
按照上述分析,可以写出递推代码:

#define MAX_SIZE 22
long long f[MAX_SIZE];
f[1] = 0;
f[2] = 1;
for(int i = 3 ; i <= n ; i++){
  f[i]= (i-1)*f[i-1]+(i-1)*f[i-2];
}

此类题目还有求解斐波那契数列第n项,以及爬楼梯等题目。递推公式可参考:f(n)=f(n-1)+f(n-2)

题目1452:搬寝室

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1452

Problem description:

题目大致意思:小茗同学开始搬家,一共有n件物品。准备徒手搬运物品,每次只拿两件物品,一手一件。由于物品n取值范围为[2,2000],因此小茗同学决定任选2*k件,其余的物品抛弃不要。经过多年搬家得到经验函数:每搬一次的疲劳度与左右手物品重量差的平方成正比。现在小茗同学先知道搬完着2 * k件物品之后,最低的疲劳度是多少?

输入要求:每组输入数据有两行,第一行有两个数n,k(2<=2*k<=n<2000).第二行有n个整数分别表示n件物品的重量(重量是一个小于2^15的正整数)。

输出要求:对应每组输入数据,输出数据只有一个表示他的最少的疲劳度,每个一行。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6759060.html

Analysis:

首先分析题目:疲劳度的函数为左右手重量差的平方。因此当两只手所持物品重量差越小时,疲劳度越低。因此可以先对给出的n件物品按照重量进校排序。那么最佳组合一定是相邻的两件物品。对物品按照重量从小到大排序之后设在前j件物品中选择i对物品,所产生的最低疲劳度记为dp[i][j]。现在分析dp[i][j]如何计算。

第一种情况,第j件物品没有被选中,如果第j件物品没有被选中,也就是说第j-1件物品没有和第j件物品匹配。此时dp[i][j]=dp[i][j-1]

第二种情况,第j件物品被选中,如果第j件物品被选中,那么一定是和第j-1件物品组合在一起,因此前面的组合为i-1对,并且从前j-2件商品中选择。那么此时的dp[i][j]=dp[i-1][j-2]+pow(weight[j]-weight[j-2],2)

因此状态转移方程为:
dp[i][j]=min(dp[i][j-1], dp[i-1][j-2]+pow(weight[j]-weight[j-1],2));

因此最终结果为dp[k][n]。

题目1453:Greedy Tino

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1453

Problem description:

题目大致意思:有一堆橘子,现在要求用一个担子来担橘子。担子左右两边的橘子必须重量相同。现在给出每个橘子的重量,现在需要求解能够担起多少重量的橘子。

输入要求:第一行为一个整数t,t表示测试数据的组数。对于每一组数据第一行为一个整数n,表示当前橘子的个数,橘子的个数n取值范围为[1,100]。每组数据的第二行为n个整数,表示每个橘子的重量。每个橘子的重量为[0,2000]。并且所有橘子的总重量小于等于2000。

输出要求:对于每组数据输出担子一边担起的橘子重量,如果一个橘子都担不了则输出-1。否则输出担子一侧的橘子重量。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6759071.html

Analysis:

这道题目怎么就算到动态规划的题目中了呢?感觉一点思路没有啊。怎么找状态呢?

现在是要担橘子,那么就是将这一堆橘子分为两堆,并且要求这两堆橘子的重量之和达到最大。于此同时要求左右两堆橘子的重量相同。

现在就假设有两个筐等待放橘子,初始时两个筐都没有放橘子,也就是两堆重量差别为0,并且重量之和为0。现在拿起第一个橘子尝试放到第一个筐中,那么两个筐重量差就变为了weight[1]。如果第一个橘子放到第二个筐中,两个筐的重量变为-weight[1]。由于所有橘子的重量之和小于等于2000。因此两堆橘子重量差取值范围为[-2000,2000]。并且橘子个数取值范围为[1,100]。那么现在就有了一个状态量,将第i个橘子放到第1个筐或者第二个筐或者不放入筐中后,两个筐之间重量的差别设为dp[i][j]。

按照上面的分析,最终要求解的为:dp[n][0]。对于这n个橘子放入或者不放入筐中,达到左右两边的重量差为0。

由于状态中的重量是有负数存在,因此为了使用二维数组保存状态,需要使用偏移量,将所有的数据向右平移2000个单位,这样最终求解的为:dp[n][0+OFFSET]; OFFSET=2000

对于一个橘子,可以选择放入第一个筐中,也可以放入到第二个筐中,也可以不放入筐中。

状态转移方程为:

dp[i][j+OFFSET]=max(max(dp[i-1][j+weight[i]+OFFSET]+weight[i], 
  				  dp[i-1][j-weight[i]+OFFSET]+weight[i],),
  				  dp[i-1][j+OFFSET]))

题目1454:Piggy-Bank

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1454

Problem description:

现在有一个小猪存钱罐,每当有零钱时就会把零钱放到存钱罐中。由于存钱罐的设计原因,只有打碎存钱罐才能知道当前已存数目。但是总会发生打碎存钱罐去购买物品却发现当前钱币总额不够。因此为了避免这种提前打碎存钱罐的情况,现在需要根据已知信息求出当前存钱罐内最少的总额。

输入要求:第一行为t,表示测试数据组数为t组。接下来一行为空的存钱罐的质量e以及当前存钱罐的质量f。并且e,f取值范围均为[1,10000]接下来一行为正数n,表示当前存钱罐内钱币的种类,n的取值范围为[1,500]。接下来的n行,每一行都有两个正数p和w,其中p表示钱币的面值,w表示这种面值的钱币的质量。并且p的取值范围为[1,50000],w的取值范围为[1,10000]。

输出要求:如果存在满足题目要求的钱币组合那么给出最低总额ans,输出格式如下:The minimum amount of money in the piggy-bank is ans.
如果不存在满足题目要求的组合,则输出:This is impossible.

Source code:

http://www.cnblogs.com/zpfbuaa/p/6763141.html

Analysis:

由于现在不知道每一种钱币的数量,并且题目要求的是总额最小,因此每种钱币的数量可视为无限个。因此该题目为完全背包问题。另外需要注意的一点为:已知空的存钱罐的质量e和当前存钱罐的质量f,那么求出的钱币组合一定满足质量=f-e。

因此需要设置初始化条件为:

for(int i = 0 ; i <= space ; i++){
   i == 0 ? dp[i] = 0 : dp[i] = INT_MAX;
}

那么在进行循环时需要顺序循环,满足每个物品有无限个(对于0-1背包问题则是逆序循环,每个物品要么放入为1,要么不放入为0):

struct Coin{
   int value;
   int space;
};
for(int i = 1 ; i <= n ; i++){
   for(int j = coin[i].space ; j <= space ; j++){
       if(dp[j-coin[i].space] != INT_MAX)
           dp[j] = min(dp[j], dp[j-coin[i].space]+coin[i].value);
  	}
}

需要注意:输出时候别丢掉了判断!=INT_MAX以及每句话结尾的字符'.'。如果不加这个'.'报Wrong Answer,而不是Presentation Error。真的好坑的小数点。

题目1455:珍惜现在,感恩生活

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1455

Problem description:

为了挽救灾区同胞的生命,心系灾区同胞的你准备自己采购一些粮食支援灾区,现在假设你一共有资金n元,而市场有m种大米,每种大米都是袋装产品,其价格不等,并且只能整袋购买。请问:你用有限的资金最多能采购多少公斤粮食呢?

输入要求:输入数据首先包含一个正整数C,表示有C组测试用例,每组测试用例的第一行是两个整数n和m(1<=n<=100, 1<=m<=100),分别表示经费的金额和大米的种类,然后是m行数据,每行包含3个数p,h和c(1<=p<=20,1<=h<=200,1<=c<=20),分别表示每袋的价格、每袋的重量以及对应种类大米的袋数。

输出要求:对于每组测试数据,请输出能够购买大米的最多重量,你可以假设经费买不光所有的大米,并且经费你可以不用完。每个实例的输出占一行。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6763894.html

Analysis:

多重背包问题,每件物品的个数不止一件但是也不是无穷件,对于每件物品都要各自的重量、价格、数目。选择合适的物品组合,实现最大化收益。注意对于背包问题要先明确是哪一种类型的背包(0-1背包、0-1背包改进版、完全背包、多重背包等)。

多重背包问题可以按照每个物品的件数num,进行按照0-1背包进行求解,但是这样造成的时间复杂度不再是之前的O(n * m)。其中n为背包的容纳最大重量,m为物品的个数。而现在为O(n * m * num)。因此时间复杂度会很高,对于某些问题而言会超时。因此需要寻找新的求解方法。

为了简化物品的数目,可以将某一类型的物品按照1、2、4、8、。。。划分在一起,这样物品的件数会从num减少至log(num)。从而减低了时间复杂度。

按照上述分析编写多重背包的代码如下所示:

\#define MAX_SIZE 101
\#define MAX_NUM 2001
struct Rice{
    int price;
    int weight;
};
int dp[MAX_SIZE];
Rice rice[MAX_NUM];
scanf("%d%d",&n,&m);// n means the money yout have, m means there are m different kinds of rice in market.
int cnt = 0;//divide the rice into different heap
for(int i = 1 ; i <= m ; i++){
    scanf("%d%d%d",&price,&weight,&num);//input each kind of rice's price, weight, num
    int x = 1;
    while(num-x>0){
        num-=x;
        cnt++;
        rice[cnt].price = x * price;
        rice[cnt].weight = x * weight;
        x*=2;
    }
    cnt++;
    rice[cnt].price = num * price;
    rice[cnt].weight = num * weight;
}
//initation the dp[j] means: with money j the total weight rice you can buy.
for(int i = 1 ; i <= n ; i++){
    dp[i]=0;
}
//change the problem into 0-1 backpage
for(int i = 1 ; i <= cnt ; i++){
    for(int j = n ; j >= rice[i].price ; j--){
        dp[j] = max(dp[j],dp[j-rice[i].price]+rice[i].weight);
    }
}

题目1456:胜利大逃亡

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1456

Problem description:

题目大致意思,存在一个A * B * C 的城堡,现在需要从坐标(0,0,0)到出口(A-1,B-1,C-1),城堡坐标用(x,y,z)表示。其中当坐标值为1是表示为墙壁,为0时表示道路。每分钟能从一个坐标走到相邻的六个坐标中的其中一个。并且现在要求在T分钟之内走出城堡。给定城堡的结构图,求出如果能在T分钟内走出城堡则输出最少的分钟,如果不能走出则输出-1。

输入要求:输入数据的第一行是一个正整数K,表明测试数据的数量.每组测试数据的第一行是四个正整数A,B,C和T(1<=A,B,C<=50,1<=T<=1000),它们分别代表城堡的大小和魔王回来的时间.然后是A块输入数据(先是第0块,然后是第1块,第2块......),每块输入数据有B行,每行有C个正整数,代表迷宫的布局,其中0代表路,1代表墙。

输出要求:对于每组测试数据,如果Ignatius能够在魔王回来前离开城堡,那么请输出他最少需要多少分钟,否则输出-1。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6747954.html

Analysis:

广度优先搜索即对由状态间的相互转移构成的解答树进行的按层次遍历。

对于题目设置状态为四元组(x,y,z,t)。其中x,y,z表示坐标,t表示从入口(0,0,0)到当前坐标(x,y,z)所有最短时间。

因此最终搜索的目标为(A-1,B-1,C-1,t)。对于每一个状态(x,y,z,t)其下一个状态有六种分别为:(x+1,y,z,t+1),(x-1,y,z,t+1),(x,y+1,z,t+1),(x,y-1,z,t+1),(x,y,z+1,t+1),(x,y,z-1,t+1)

为了找到到达点(A-1、B-1、C-1)的最短时间,我们从初始状态 (0,0,0,0)开始,按照状态的不断扩展转移查找每一个状态。将初始状态视为根节点,并将每一个状态扩展得到的新状态视为该状态的子结点,那么状态的转移与生成就呈现出了树的形态,如下图所示:


因此可以采用队列来实现先进先出,从而实现广度遍历。为了减少遍历的次数,可以采用剪枝法,判断之前时候已经遍历过当期结点,另外判断当前位置是否合法包括超过城堡范围或者当前位置为墙壁。

可以使用change数组实现下一个位置的移动:

int change[][3]={
   1,0,0,
   -1,0,0,
   0,1,0,
   0,-1,0,
   0,0,1,
   0,0,-1
};

结构体保存状态信息:

struct N{
   int x;
   int y;
   int z;
   int t;
};
queuemyQueue;

广度优先搜索的代码:

#define WALL 1
#define MOVE 6
int BFS(int a, int b , int c){
   while(!myQueue.empty()){
       N nowP = myQueue.front();
       myQueue.pop();
       for(int i = 0 ; i < MOVE ; i ++){
           int nx = nowP.x + change[i][0];
           int ny = nowP.y + change[i][1];
           int nz = nowP.z + change[i][2];
           if(nx<0 || nx>=a || ny<0 || ny>=b || nz<0 || nz>=c) continue;
           if(space[nx][ny][nz] == WALL) continue;
           if(visit[nx][ny][nz] == true) continue;
           N tmp;
           tmp.x = nx;
           tmp.y = ny;
           tmp.z = nz;
           tmp.t = nowP.t + 1;
           myQueue.push(tmp);
           visit[nx][ny][nz] = true;
           if(nx==a-1 && ny==b-1 && nz==c-1) return tmp.t;
       }
   }
   return -1;
}

题目1457:非常可乐

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1457

Problem description:

大家一定觉的运动以后喝可乐是一件很惬意的事情,但是seeyou却不这么认为。因为每次当seeyou买了可乐以后,阿牛就要求和seeyou一起分享这一瓶可乐,而且一定要喝的和seeyou一样多。但seeyou的手中只有两个杯子,它们的容量分别是N 毫升和M 毫升 可乐的体积为S (S<101)毫升(正好装满一瓶) ,它们三个之间可以相互倒可乐 (都是没有刻度的,且 S==N+M,101>S>0,N>0,M>0) 。聪明的ACMER你们说他们能平分吗?如果能请输出倒可乐的最少的次数,如果不能输出"NO"。

输入要求:三个整数 : S 可乐的体积 , N 和 M是两个杯子的容量,以"0 0 0"结束。

输出要求:如果能平分的话请输出最少要倒的次数,否则输出"NO"。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6750319.html

Analysis:

首先明确题目的问题:给出S可乐体积,N杯子容量,M杯子容量,能够通过不断倾倒可乐实现平分可乐。如果可以则输出最少倾倒可乐的次数。如果不能实现平分则输出NO。

第一个需要注意多组数据,结束标志为0 0 0,由于题目给出S==N+M。因此当S==0时,也就是数据结束输入的时刻。

第二个需要注意的是,两个杯子和可乐瓶的容量均为正数。因此如果要实现平分那么当S%2==1,也就是可乐总量为奇数时,平分结果一定不是正数。因此不可能用两个正数的杯子去容纳不是正数的可乐。所以当可乐总容量S为奇数时,直接输出NO。

接下来就是针对偶数的容量进行讨论:

  1. 设置搜索空间为四元组(s,a,b,t)。其中s表示可乐瓶中可乐量,a表示容量为N的杯子的可乐量,b表示容量为M的杯子的可乐量,t表示从初始状态到当前状态最小倾倒次数。四元组初始化(S,0,0,0),目标四元组为(S/2,S/2,0,t)或者(S/2,0,S/2,t)或者(0,S/2,S/2,t)。

  2. 为了实现倾倒,需要编写倾倒可乐的函数。现在倾倒可乐的情况有六种:a->b、a->c、b->a、b->c、c->a、c->b。每次倾倒都是从一个容器到另外一个容器,因此需要传递的参数有杯子1和杯子2当前可乐量,以及杯子1和杯子2各自的容量。并且杯子1和杯子2的可乐量应当随着倾倒后发生改变,因此参数需要传递引用,对于杯子容量是一个常数不需要传递引用。
    下面为倾倒函数,从杯子x导入杯子y的过程如下所示:

void x2y(int &x,int size_x, int &y,int size_y){
   if(size_y - y >= x){
       y+=x;
       x = 0;
   }else{
       x -=(size_y-y);
       y = size_y;
   }
}
  1. 为了实现对状态的保存,需要利用结构体保存三个容器中此刻状态的可乐量以及从初始状态到当前所倾倒的最小次数,并且将状态保存在队列中用于广度优先遍历。
struct N{
   int a;
   int b;
   int c;
   int t;
};
queue myQueue;
  1. 为了判断每个状态是否已经进行过判断,需要数组bool visit[MAX_SIZE][MAX_SIZE][MAX_SIZE];其中MAX_SIZE为宏定义常量101。至少MAX_SIZE需要大于等于100,为了满足保存的需求。

  2. 需要注意的是要对visit数组进行初始化操作,同时对于每组数据进行操作之前需要清空while(!myQueue.empty()) myQueue.pop();

题目1458:汉诺塔III

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1458

Problem description:

约19世纪末,在欧州的商店中出售一种智力玩具,在一块铜板上有三根杆,最左边的杆上自上而下、由小到大顺序串着由64个圆盘构成的塔。目的是将最左边杆上的盘全部移到右边的杆上,条件是一次只能移动一个盘,且不允许大盘放在小盘的上面。现在我们改变游戏的玩法,不允许直接从最左(右)边移到最右(左)边(每次移动一定是移到中间杆或从中间移出),也不允许大盘放到下盘的上面。Daisy已经做过原来的汉诺塔问题和汉诺塔II,但碰到这个问题时,她想了很久都不能解决,现在请你帮助她。现在有N个圆盘,她至少多少次移动才能把这些圆盘从最左边移到最右边?

输入要求:包含多组数据,每次输入一个N值(1<=N=35)。

输出要求:对于每组数据,输出移动最小的次数。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6752631.html

Analysis:

现在要求不允许直接从最左边移动到最右边或者是从最右边移动到最左边。因此不能按照之前的计算方法2^(n-1)为最小移动次数。

现在考虑将n个圆盘从最左边移动到最右边。首先需要将n-1个圆盘移动到最右边圆盘,然后最左边剩下最大的一个圆盘,中间为空,右边为n-1个圆盘。然后将左边的最大圆盘移动到中间。然后再将n-1个圆盘移动到最左边。此时左边剩下n-1个圆盘,中间为最大圆盘,最右边为空。之后将中间圆盘移动到最右边。此时状态为最左边为n-1个圆盘,中间为空,右边已经移动放好了最大的圆盘。

因此按照上面的过程,假设移动n个圆盘需要的次数为f(n),那么f(n)=3*f(n-1)+2。这里的3*f(n-1)和+2分别为:最左边n-1个圆盘移动到最右边;最大圆盘从左边移动到中间+1;最右边n-1个圆盘移动到最左边;最大圆盘从中间移动到最右边+1;最左边n-1个圆盘移动到最右边。

long long hanoi(int n){
   if(n==1) return 2;
   return 3*hanoi(n-1)+2;
}

题目1459:Prime ring problem

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1459

Problem description:

素数环问题:n个正数组成一个环,要求每个相邻的两个正数之和为素数。所给n个正数为1,2,3...n。

输入要求:多组数据,n的取值范围为(1,17)。

输出要求:首先输出是第几次测试用例,然后输出当前所有满足素数环的组合,要求第一个数字一直为1,并且每个数字之间空格分隔,同时每种情况需换行。并且每组测试样例用空行分隔。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6752701.html

Analysis:

为了解决该问题,我们可以采用回溯法枚举每一个值。当第一个数位为1确定时,我们尝试放入第二个数,使其与1 的和为素数,放入后再尝试放入第三个数,使其与第二个数的和为素数,直到所有的数全部被放入环中,且最后一个 数与1的和也是素数,那么这个方案即为答案,输出答案;若在尝试放数的过程中,发现当前位置无论放入任何之前没有被使用的数都不可能满足条件,那么我们回溯改变其上一个数,直到产生我们所需要的答案,或者确实不再存在更多的解。

由于所给范围为(1,17),因此两个数相加最大的值不超过41.因此可以提前保存下来这个范围内的所有素数用于判断,int prime[] = {2,3,5,7,11,13,17,19,23,29,31,37,41};

如果当期插入的数字个数等于n,需要判断其与1的和是否是素数,如果是素数则输出一种排序结果。

void check(){
   if(!judge(ans[n]+ans[1])) return;
   for(int i = 1 ; i <= n ; i++){
       if(i!=1) printf(" ");
       printf("%d",ans[i]);
   }
   printf("\n");
}

下面需要回溯法进行判断,能够继续插入数组,回溯所有的情况。

void DFS(int num){
   if(num>1){
       if(!judge(ans[num] + ans[num - 1])) return;
   }
   if(num==n){
       check();
       return;
   }
   for(int i = 2 ; i <= n ; i++){
       if(!used[i]){
           used[i] = true;
           ans[num+1] = i;
           DFS(num+1);
           used[i] = false;
       }
   }
}

其中数组int ans[MAX_SIZE]用于保存每一次的结果。数组int used[MAX_SIZE]用于保存数字是否以及插入到当前环中。

题目1460:Oil Deposit

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1460

Problem description:

题目大致意思:存在一个矩形油田,油田被划分为n*m的小区域,每块区域通过仪器探测之后进行标记,如果该区域存在石油则标记为'@',如果不存在则标记为' * '。其中每一个有油田的小区域的前后左右四个对角都是相邻的,也就是说小油田组成了大的油田。现在给出油田的分布情况,求出一共有多少块不同油田。

输入要求:多组数据,每组数据第一行为n,m。其中n和m分别表示矩形油田的长和宽。并且n,m的取值范围均为[1,100]。接下来有n行,每一行有m个字符,分别为'*'或者'@'当中的一个。

输出要求:输出油田的个数。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6753231.html

Analysis:

首先对图上所有位置设置一个标记位,表示该位置是否已经被计算过,且该标记仅对地图上为@的点有效。这样我们按从左至右、从上往下的顺序依次遍历地图上所有位置,若遍历到@,且该点未被标记,则所有与其直接相邻、或者间接相邻的@点与其一起组成一个油田,该油田即为一个我们需要计算的油田,将该油田中所有的@位标记为已经计算。这样,当所有的位置被遍历过后,我们即得到了所需的答案。

char plot[MAX_SIZE][MAX_SIZE];
bool visited[MAX_SIZE][MAX_SIZE];
int change[2][8]={{-1,-1,-1,0,0,1,1,1},{-1,0,1,-1,1,-1,0,1}};
int n, m;
void DFS(int x, int y){
   for(int i = 0 ; i < 8 ; i++){
       int nx = x + change[0][i];
       int ny = y + change[1][i];
       if(nx<0 || nx>=n || ny<0 || ny>=m) continue;
       if(plot[nx][ny]=='*') continue;
       if(visited[nx][ny]==true) continue;
       visited[nx][ny]=true;
       DFS(nx,ny);
   }
   return;
}

题目1461:Tempter of the bone

Jobdu Link:

http://ac.jobdu.com/problem.php?pid=1461

Problem description:

题目大致意思:一只可爱的狗狗被困在了矩形迷宫之中。迷宫只有一个出口,标记为'D'。狗狗所在位置标记为'S'。迷宫中的墙壁标记为'X'。迷宫中的道路标记为'.'。由于迷宫设置了机关,每次离开当前位置之后,该位置就会产生塌陷,无法再次回到该位置。此外,从当前时刻开始,迷宫的出口只有在t秒后才开启,如果超过t时刻则出口不再开启,如果提前到达出口也无法成功逃离。狗狗每次移动可以向左或右或者向上或向下移动一个方格,狗狗每秒只能移动一个方格。狗狗只有当其恰好t时刻出口时才能成功逃离迷宫。现在给出迷宫分布图以及狗狗的位置,请问狗狗能否安全逃离迷宫。

输入要求:多组数据,每组数据第一行为n, m,t。其中n,m分别表示迷宫的长和宽,t表示出口在t秒后开启。接下来的n行分别为迷宫的分布图。其中的字符只包含'X'、'S'、’D‘、'.'。另外n,m的取值范围为(1,7),t的取值范围为(0,50)。

Source code:

http://www.cnblogs.com/zpfbuaa/p/6756101.html

Analysis:

为了判断狗狗能否逃离,需要遍历所有的路径。从当前位置开始,下一个位置可以从上下左右进行选择,选择之后,判断时候达到出口并且判断时间是否等于所给的t。然后继续从这个位置选择下一个位置,不断重复上述操作,同时将已经遍历的位置设置为已经遍历。如果当该位置无法选择下一个位置之后,回溯至上一个位置,选择上下左右位置的另外三种(因为有一种已经不能够通过)。

因此该方法为深度优先遍历。对于所有路径组成的树进行深度优先遍历,找到解则成功,如果找不到则进行回溯。直到所有路径均遍历。如果遍历结束仍没有找到则失败。因此需要设置默认值为false,当找到时修改为true。

同时为了简化位置移动的计算,可以利用change二维数组保存坐标(x,y)接下来的四种移动方式:(0,-1)、(0,1)、(-1,0)、(1,0),分别表示向左右上下四种移动。int change[2][4]={{0,0,-1,1},{-1,1,0,0}};//left right up down

为了保存某个是否已经遍历需要声明数组int visited[MAX_SIZE][MAX_SIZE];来进行保存。

此外对于本题目而言,由于出口开启时间固定,并且初始位置S和出口位置D可以求出。因此从初始位置S(x1,y1)到出口位置(x2,y2)需要移动的次数可以确定是奇数次还是偶数次。如果(x1+y1)%2==(x2+y2)%2,即初始位置和出口位置的奇偶相同,那么移动通过偶数次移动,那么所给t必须为偶数次。因此判断可以写为:(start.x+start.y)%2 == ((over.x+over.y)%2+t%2) %2

可以通过下面的深度优先遍历DFS进行求解:

struct Pos{
    int x;
    int y;
};
Pos start;
Pos over;
bool flag;
void DFS(Pos pos,int time){
    Pos nextP;
    for(int i = 0 ; i < 4 ; i ++){
        nextP.x = pos.x + change[0][i];
        nextP.y = pos.y + change[1][i];
        if(nextP.x<0 || nextP.x>=n || nextP.y<0 || nextP.y>=m) continue;
        if(plot[nextP.x][nextP.y] == 'X' || visited[nextP.x][nextP.y]==true) continue;
        if(plot[nextP.x][nextP.y] == 'D'){
            if(time+1==t){
                flag = true;
                return;
            }
            else{
                continue;
            }
        }
        visited[nextP.x][nextP.y]=true;
        DFS(nextP,time+1);
        visited[nextP.x][nextP.y]=false;
        if(flag==true) return;
    }
}

About

Solutions to Jobdu problems based on C++

http://ac.jobdu.com/

License:MIT License


Languages

Language:C++ 100.0%