备战蓝桥杯--历届试题题解

来源:互联网 发布:创建表时外键的sql语句 编辑:程序博客网 时间:2024/06/05 15:33

3月20号将是我第一次外出比赛。 虽然是蓝桥杯。但是还是刷新题目下热热身

蓝桥杯--历年试题 【编程大题】

PREV-1 核桃的数量  最小公倍数

问题描述

小张是软件项目经理,他带领3个开发组。工期紧,今天都在加班呢。为鼓舞士气,小张打算给每个组发一袋核桃(据传言能补脑)。他的要求是:

1. 各组的核桃数量必须相同

2. 各组内必须能平分核桃(当然是不能打碎的)

3. 尽量提供满足1,2条件的最小数量(节约闹革命嘛)

输入格式
输入包含三个正整数a, b, c,表示每个组正在加班的人数,用空格分开(a,b,c<30)
输出格式
输出一个正整数,表示每袋核桃的数量。
样例输入1
2 4 5
样例输出1
20
样例输入2
3 1 1
样例输出2
3

思路:太水了。 就是找一个数并且能同时整除abc,最小公倍数

#include<iostream>#include<stdio.h>#include<cmath>using namespace std;int gcd(int a,int b){return b==0?a:gcd(b,a%b);}int lcm(int a,int b){return a/gcd(a,b)*b;}int main(){int num,ans=1;for(int i=0;i<3;i++){scanf("%d",&num);ans=lcm(ans,num);}printf("%d\n",ans);return 0;}


PREV-2 打印十字图 文字图

[暂时做不出来。。。讨厌这种画图题。ORZ。]



PREV-3 带分数 搜索 

问题描述

100 可以表示为带分数的形式:100 = 3 + 69258 / 714。

还可以表示为:100 = 82 + 3546 / 197。

注意特征:带分数中,数字1~9分别出现且只出现一次(不包含0)。

类似这样的带分数,100 有 11 种表示法。

输入格式

从标准输入读入一个正整数N (N<1000*1000)

输出格式

程序输出该数字用数码1~9不重复不遗漏地组成带分数表示的全部种数。

注意:不要求输出每个表示,只统计有多少表示法!

样例输入1
100
样例输出1
11
样例输入2
105
样例输出2
6

思路:将式子看出N=A+B/C的形式,观测到数字1~9分别出现且只出现一次这一条件就可以想到枚举全排列。然后将全排列分成3个部分。分别

分别是A B C三个部分,然后判断(N-A)*C==B,注意ABC都是至少需要一个数来构成。全排列的9的数字中[记为1-9],A只能从1枚举到7

B只能从2枚举到8,C只能从3枚举到9. 简单优化一下:当A组成的数字大于N时就不应该继续枚举了。因为永远得不到结果。


#include<iostream>#include<stdio.h>#include<cmath>#include<time.h>#include<string>#include<cstring>#include<algorithm>using namespace std;const int MAXN=1000*1000+5;int num[10],vis[MAXN];int get_num(int st,int ed){//全排列的st到ed组成的数字 int m=0;for(int i=st;i<=ed;i++){m=m*10+num[i];}return m;}int main(){int n,ans;while(scanf("%d",&n)!=EOF){ans=0;int A,B,C;for(int i=0;i<9;i++){num[i]=i+1;}do{//枚举全排列 for(int i=0;i<9;i++){A=get_num(0,i);if(A>=n){//优化。 break;}for(int j=i+1;j<8;j++){B=get_num(i+1,j);C=get_num(j+1,8);if(B==(n-A)*C){ans++;}}}}while(next_permutation(num,num+9));printf("%d\n",ans);}return 0;}

PREV-4 剪格子 搜索

问题描述

如下图所示,3 x 3 的格子中填写了一些整数。

+--*--+--+
|10* 1|52|
+--****--+
|20|30* 1|
*******--+
| 1| 2| 3|
+--+--+--+

我们沿着图中的星号线剪开,得到两个部分,每个部分的数字和都是60。

本题的要求就是请你编程判定:对给定的m x n 的格子中的整数,是否可以分割为两个部分,使得这两个区域的数字和相等。

如果存在多种解答,请输出包含左上角格子的那个区域包含的格子的最小数目。

如果无法分割,则输出 0。

输入格式

程序先读入两个整数 m n 用空格分割 (m,n<10)。

表示表格的宽度和高度。

接下来是n行,每行m个正整数,用空格分开。每个整数不大于10000。

输出格式
输出一个整数,表示在所有解中,包含左上角的分割区可能包含的最小的格子数目。
样例输入1
3 3
10 1 52
20 30 1
1 2 3
样例输出1
3
样例输入2
4 3
1 1 1 1
1 30 80 2
1 1 1 100
样例输出2
10

思路:因为记录的是最上角的的分割区可能包含的最小的格子数目,所以很容易就会想到从左上角开始深搜。很可惜。这样做案例2是无法得到正确答案的。因为一般的深搜只能按一个方向[上下左右]一直搜索,而不能想案例2那样"拐弯"搜索,但是不管怎么搜索,正确结果的搜索区域即不管怎么"拐弯",得到的区域都是连续的,那么我们可以从所有位置开始搜索,当满足条件并且同时搜索到左上角的位置时就可以记录了。虽然是100分过,但是我还是觉得这题测试数据不够严谨,即使暴力所有点开始dfs搜索都还是可以找反例。即结果区域呈"十字架/T字形"的话,暴力起点搜还是不会得到正确结果的。不过没想到更好的办法了。

#include<iostream>#include<stdio.h>#include<cmath>#include<time.h>#include<string>#include<cstring>#include<algorithm>using namespace std;const int MAXN=15;int num[MAXN][MAXN],n,m,s,ans;int vis[MAXN][MAXN],dist[4][2]={0,1,0,-1,1,0,-1,0};//标记数组[回溯用],方向向量 bool check(int x,int y){//是否越界 if(x>=0&&x<n&&y>=0&&y<m){return true;}return false;}void dfs(int x,int y,int val,int total){//x,y:当前位置 val:当前的数字和 total:当前搜索的区域包括多少个数字 if(val>s||total>=ans){//剪枝:当前数已经大于整体的一般,或者 return;//包括的区域数字已经大于当前最优解 }if(val==s&&vis[0][0]){//到最终状态并且搜索到左上角 ans=min(ans,total);}for(int i=0;i<4;i++){int nextx=x+dist[i][0];int nexty=y+dist[i][1];if(check(nextx,nexty)&&!vis[nextx][nexty]){vis[nextx][nexty]=1;dfs(nextx,nexty,val+num[nextx][nexty],total+1);vis[nextx][nexty]=0;}}}int main(){while(scanf("%d%d",&m,&n)!=EOF){ans=105;int sum=0;memset(vis,0,sizeof(vis));for(int i=0;i<n;i++){for(int j=0;j<m;j++){scanf("%d",&num[i][j]);sum+=num[i][j];}}if(sum%2)//如果不能分成2部分 {printf("0\n");continue;}s=sum/2;//最终需要得到的结果S for(int i=0;i<n;i++){for(int j=0;j<n;j++){//暴力每个起点搜索 memset(vis,0,sizeof(vis));vis[i][j]=1;dfs(0,0,num[i][j],1);}}if(ans==105)//无法平分 {printf("0\n");}else{printf("%d\n",ans);}}return 0;}

PREV-5  错误票据  排序

问题描述

某涉密单位下发了某种票据,并要在年终全部收回。

每张票据有唯一的ID号。全年所有票据的ID号是连续的,但ID的开始数码是随机选定的。

因为工作人员疏忽,在录入ID号的时候发生了一处错误,造成了某个ID断号,另外一个ID重号。

你的任务是通过编程,找出断号的ID和重号的ID。

假设断号不可能发生在最大和最小号。

输入格式

要求程序首先输入一个整数N(N<100)表示后面数据行数。

接着读入N行数据。

每行数据长度不等,是用空格分开的若干个(不大于100个)正整数(不大于100000),请注意行内和行末可能有多余的空格,你的程序需要能处理这些空格。

每个整数代表一个ID号。

输出格式

要求程序输出1行,含两个整数m n,用空格分隔。

其中,m表示断号ID,n表示重号ID

样例输入1
2
5 6 8 11 9 
10 12 9
样例输出1
7 9
样例输入2
6
164 178 108 109 180 155 141 159 104 182 179 118 137 184 115 124 125 129 168 196
172 189 127 107 112 192 103 131 133 169 158 
128 102 110 148 139 157 140 195 197
185 152 135 106 123 173 122 136 174 191 145 116 151 143 175 120 161 134 162 190
149 138 142 146 199 126 165 156 153 193 144 166 170 121 171 132 101 194 187 188
113 130 176 154 177 120 117 150 114 183 186 181 100 163 160 167 147 198 111 119
样例输出2
105 120

思路:水题。按照题目意思模拟即可,主要考察字符串的处理[字符串->数字的转换]。断点和重复只有一个。记录数字的最大值和最小值。开个数组记录那些数字已经出现过,然后暴力找最小值到最大值那个没出现过。

#include<iostream>#include<stdio.h>#include<string>#include<cstring>#include<algorithm>#include<vector>#include<cmath>using namespace std;const int MAXN=100000+5;const int MAXL=20000000;int num[MAXN],minnum,maxnum;char str[MAXL];int main(){int n,repeat,vacancy;while(scanf("%d",&n)!=EOF){memset(num,0,sizeof(num));minnum=MAXN+1;maxnum=-1;getchar();for(int i=0;i<n;i++){gets(str);int len=strlen(str);for(int j=0;j<len;j++){if(str[j]!=' '){int val=0;while(str[j]!=' '&&j<len){val=val*10+str[j]-'0';j++;}maxnum=max(maxnum,val);minnum=min(minnum,val);if(num[val]==0){//是否出现过 num[val]=1;}else{//重复出现 repeat=val;}}}}for(int i=minnum;i<=maxnum;i++){//找断点 if(num[i]==0){vacancy=i;break;}}printf("%d %d\n",vacancy,repeat);}return 0;}


PREV-6  翻硬币  贪心

问题描述

小明正在玩一个“翻硬币”的游戏。

桌上放着排成一排的若干硬币。我们用 * 表示正面,用 o 表示反面(是小写字母,不是零)。

比如,可能情形是:**oo***oooo

如果同时翻转左边的两个硬币,则变为:oooo***oooo

现在小明的问题是:如果已知了初始状态和要达到的目标状态,每次只能同时翻转相邻的两个硬币,那么对特定的局面,最少要翻动多少次呢?

我们约定:把翻动相邻的两个硬币叫做一步操作,那么要求:

输入格式

两行等长的字符串,分别表示初始状态和要达到的目标状态。每行的长度<1000

输出格式

一个整数,表示最小操作步数。

样例输入1
**********
o****o****
样例输出1
5
样例输入2
*o**o***o***
*o***o**o***
样例输出2
1

思路:因为只能一次翻2个,所以想匹配最左边,如果匹配则继续匹配下一个位置,否则就必须翻这个位置的硬币[同时改变他下一个位置的硬币,因为一翻就会改动2个位置],模拟就好了

#include<iostream>#include<stdio.h>#include<string>#include<cstring>#include<algorithm>#include<vector>#include<cmath>using namespace std;const int MAXL=1000+5;char st[MAXL],ed[MAXL];char chang(char a){return a=='*'?'o':'*';}int main(){while(scanf("%s",st)!=EOF){scanf("%s",ed);int step=0,len=strlen(st);for(int i=0;i<len;i++){if(st[i]!=ed[i])//不匹配 {step++;st[i+1]=chang(st[i+1]);//改变下一个位置 }}printf("%d\n",step);}return 0;}

PREV-7  连号区间数 并查集 

问题描述

小明这些天一直在思考这样一个奇怪而有趣的问题:

在1~N的某个全排列中有多少个连号区间呢?这里所说的连号区间的定义是:

如果区间[L, R] 里的所有元素(即此排列的第L个到第R个元素)递增排序后能得到一个长度为R-L+1的“连续”数列,则称这个区间连号区间。

当N很小的时候,小明可以很快地算出答案,但是当N变大的时候,问题就不是那么简单了,现在小明需要你的帮助。

输入格式

第一行是一个正整数N (1 <= N <= 50000), 表示全排列的规模。

第二行是N个不同的数字Pi(1 <= Pi <= N), 表示这N个数字的某一全排列。

输出格式

输出一个整数,表示不同连号区间的数目。

样例输入1
4
3 2 4 1
样例输出1
7
样例输入2
5
3 4 2 5 1
样例输出2
9

思路:起初我是暴力的,没暴力每个起点和终点。然后将起点和终点的数排序。记L=起点位置,R=终点位置。那么用个vector把这个区间的数都push进去,然后排序一遍,然后最大值-最小值+1=size()的话就满足条件。然后这样做只能得60分。因为是O(n*n*nlogn)。但是想一下。只会出现1-N的数字。所以只需要继续L,R区域里的最大值和最小值。如果区域最大值-最小值=区域个数,那么就一定满足要求。


#include<iostream>#include<stdio.h>#include<string>#include<cstring>#include<algorithm>#include<vector>#include<cmath>using namespace std;const int MAXN=50000+5;int num[MAXN];int main(){int n;while(scanf("%d",&n)!=EOF){int ans=0;for(int i=0;i<n;i++){scanf("%d",&num[i]);}for(int i=0;i<n;i++){int maxnum=num[i];int minnum=num[i];for(int j=i+1;j<n;j++){maxnum=max(maxnum,num[j]);minnum=min(minnum,num[j]);if(maxnum-minnum==j-i){ans++;}}}printf("%d\n",ans+n);//+n表示区间[L,L]即本身一定满足条件 }return 0;}

PREV-8 买不到的数目 数论 动态规划

问题描述

小明开了一家糖果店。他别出心裁:把水果糖包成4颗一包和7颗一包的两种。糖果不能拆包卖。

小朋友来买糖的时候,他就用这两种包装来组合。当然有些糖果数目是无法组合出来的,比如要买 10 颗糖。

你可以用计算机测试一下,在这种包装情况下,最大不能买到的数量是17。大于17的任何数字都可以用4和7组合出来。

本题的要求就是在已知两个包装的数量时,求最大不能组合出的数字。

输入格式

两个正整数,表示每种包装中糖的颗数(都不多于1000)

输出格式

一个正整数,表示最大不能买到的糖数

样例输入1
4 7
样例输出1
17
样例输入2
3 5
样例输出2
7

思路:估算结果范围+暴力DP。。。因为没想到其他比较好的办法,所以就估算结果会在1000*1000内。假设输入的数为a,b 先dp筛掉a的倍数。然后i从b-1000*1000筛选b的倍数的i和能够凑成i-b的i,看代码吧。

#include<iostream>#include<stdio.h>#include<string>#include<cstring>#include<algorithm>#include<vector>#include<cmath>#include<time.h>using namespace std;const int MAXN=1000*1000+1;bool dp[MAXN];int main(){int n,m;while(scanf("%d%d",&n,&m)!=EOF){if(n==1||m==1){//不知道会不会有1这样的数据出现,因为1是能组成任何大于1数printf("0\n");continue;}memset(dp,false,sizeof(dp));if(n>m){swap(n,m);}for(int i=n;i<MAXN;i+=n){//筛选a的倍数 dp[i]=true;}for(int i=m;i<MAXN;i++){//筛选b的倍数和能凑成i-b的数 if(dp[i-m]||i%m==0){dp[i]=true;}}int ans=-1;//找最大的不能组成的数 for(int i=0;i<MAXN;i++){if(!dp[i]){ans=max(ans,i);}}printf("%d\n",ans);}return 0;}

PREV-9 大臣的旅费 深度优先遍历

问题描述

很久以前,T王国空前繁荣。为了更好地管理国家,王国修建了大量的快速路,用于连接首都和王国内的各大城市。

为节省经费,T国的大臣们经过思考,制定了一套优秀的修建方案,使得任何一个大城市都能从首都直接或者通过其他大城市间接到达。同时,如果不重复经过大城市,从首都到达每个大城市的方案都是唯一的。

J是T国重要大臣,他巡查于各大城市之间,体察民情。所以,从一个城市马不停蹄地到另一个城市成了J最常做的事情。他有一个钱袋,用于存放往来城市间的路费。

聪明的J发现,如果不在某个城市停下来修整,在连续行进过程中,他所花的路费与他已走过的距离有关,在走第x千米到第x+1千米这一千米中(x是整数),他花费的路费是x+10这么多。也就是说走1千米花费11,走2千米要花费23。

J大臣想知道:他从某一个城市出发,中间不休息,到达另一个城市,所有可能花费的路费中最多是多少呢?

输入格式

输入的第一行包含一个整数n,表示包括首都在内的T王国的城市数

城市从1开始依次编号,1号城市为首都。

接下来n-1行,描述T国的高速路(T国的高速路一定是n-1条)

每行三个整数Pi, Qi, Di,表示城市Pi和城市Qi之间有一条高速路,长度为Di千米。

输出格式

输出一个整数,表示大臣J最多花费的路费是多少。

样例输入1
5
1 2 2
1 3 1
2 4 5
2 5 4
样例输出1
135
输出格式

大臣J从城市4到城市5要花费135的路费。

思路:看到题目输入是n-1条边。就说明这是树形结构,然后又要求找两个点求这两点的最远距离。 树DP最远点对/树的直径。模板即可。

#include<iostream>#include<stdio.h>#include<string>#include<cstring>#include<algorithm>#include<vector>#include<cmath>#include<time.h>using namespace std;const int MAXN=1000005;long long int dist[MAXN]; struct Node{int to,val;Node(int a,int b):to(a),val(b){}};vector<Node>G[MAXN];void init(){dist[0]=0;int len=11;for(int i=1;i<MAXN;i++){dist[i]=dist[i-1]+len;len++;}}int diameter; //保存最长路径int DFS(int x, int px){ // px是x的父親int h1 = 0, h2 = 0; // 紀錄最高與次高的高度int d = G[x].size();for (int i = 0; i < d; i++){int v = G[x][i].to,w=G[x][i].val;if (v != px){int h = DFS(v, x) + w;if (h > h1) h2 = h1, h1 = h;else if (h > h2) h2 = h;}}diameter = max(diameter, h1 + h2); return h1;}int main(){int n;init();while(scanf("%d",&n)!=EOF){for(int i=1;i<=n;i++){G[i].clear();}int u,v,w;for(int i=1;i<n;i++){scanf("%d%d%d",&u,&v,&w);G[u].push_back(Node(v,w));G[v].push_back(Node(u,w));}diameter=0;DFS(1, -1);cout<<dist[diameter]<<endl;}return 0;}




PREV-10 幸运数 

思路:无脑按照题目要求把表打出来。1000*150这个范围内还是很快可以把表打出来的。但是极限数据就非常慢。 题目要求的1000*1000卡了好几秒才能读数据。但是估计数据真心水,1000*150的表都能100分过。注意下不能包括n,m。 暂时没想到什么能解决1000*1000的高效办法。有人说可以用打素数表的方法,但是我是没想出来。这个和素数关系好像不大,因为要筛的是按位置来筛,而不是按照数字本身来特点来筛。 无力,小数据水过。


#include<iostream>#include<stdio.h>#include<string>#include<cstring>#include<algorithm>#include<vector>#include<cmath>#include<time.h>using namespace std;const int MAXN=1000*150;//小数据。 vector<int>lucky;//幸运数表 void init(){for(int i=1;i<MAXN;i+=2)//先将1 3 5 7...加入表 {lucky.push_back(i);}for(int i=1;i<lucky.size();i++){int num=lucky[i];//获得第i个幸运数 if(num-1>=lucky.size()){//剪枝,当前数已经大于表的大小,后面的数都无法筛 break;}for(int j=1;j*num-j<lucky.size();j++){//筛掉位置为index的数,-j时因为 erase删除数据的同时会将后面的数进行 int index=j*num-j;//补齐操作。相当于简单前面已经删过的数的个数才是 lucky.erase(lucky.begin()+index);//要筛的正确位置 }}//cout<<"size():"<<lucky.size()<<endl;}int main(){//clock_t s1,s2;//s1=clock();init();int n,m;while(scanf("%d%d",&n,&m)!=EOF){int L=upper_bound(lucky.begin(),lucky.end(),n)-lucky.begin();//找到大于n的数的位置 int R=lower_bound(lucky.begin(),lucky.end(),m)-lucky.begin();//找到小于等于m的数的位置 if(R>0)//因为不能包括m所以减掉 {R--;//因为可能r--后出现l>r的情况}printf("%d\n",max(0,R-L+1));//所以输出时判断结果不为负数 //s2=clock();//printf("%f ms\n", (double)(s2 - s1));}return 0;}


PREV-12 危险系数 割点

问题描述

抗日战争时期,冀中平原的地道战曾发挥重要作用。

地道的多个站点间有通道连接,形成了庞大的网络。但也有隐患,当敌人发现了某个站点后,其它站点间可能因此会失去联系。

我们来定义一个危险系数DF(x,y):

对于两个站点x和y (x != y), 如果能找到一个站点z,当z被敌人破坏后,x和y不连通,那么我们称z为关于x,y的关键点。相应的,对于任意一对站点x和y,危险系数DF(x,y)就表示为这两点之间的关键点个数。

本题的任务是:已知网络结构,求两站点之间的危险系数。

输入格式

输入数据第一行包含2个整数n(2 <= n <= 1000), m(0 <= m <= 2000),分别代表站点数,通道数;

接下来m行,每行两个整数 u,v (1 <= u, v <= n; u != v)代表一条通道;

最后1行,两个数u,v,代表询问两点之间的危险系数DF(u, v)。

输出格式
一个整数,如果询问的两点不连通则输出-1.
样例输入
7 6
1 3
2 3
3 4
3 5
4 5
5 6
1 6
样例输出
2

思路:模拟暴力删除点[除了起点和终点],然后跑一次DFS看看连不连通。

#include<iostream>#include<stdio.h>#include<string>#include<cstring>#include<algorithm>#include<cmath>#include<vector>using namespace std;const int MAXN=1000+5;#define INF 0x3f3f3f3fvector<int>G[MAXN];int ans,st,ed,vis[MAXN];bool flag;//判断是否连通 void dfs(int u){if(u==ed||flag){flag=true;return;}for(int i=0;i<G[u].size()&&!flag;i++){int v=G[u][i];if(!vis[v]){vis[v]=1;dfs(v);vis[v]=0;}}}int main(){int n,m;while(scanf("%d%d",&n,&m)!=EOF){for(int i=1;i<=n;i++){G[i].clear();}for(int i=0;i<m;i++){int u,v;scanf("%d%d",&u,&v);G[u].push_back(v);G[v].push_back(u);}memset(vis,0,sizeof(vis));scanf("%d%d",&st,&ed);vis[st]=1; ans=0;dfs(st);//先跑一边看看原图是否可达 if(flag==false){printf("-1\n");}else{for(int i=1;i<=n;i++){if(i==st||i==ed)//枚举删除的点 {continue;}flag=false;vis[i]=1;dfs(st);if(!flag){ans++;}vis[i]=0;//记得取消标记。因为每次只删除一个点 }printf("%d\n",ans);}}return 0;}

PREV-13 网络寻路   构图

问题描述

X 国的一个网络使用若干条线路连接若干个节点。节点间的通信是双向的。某重要数据包,为了安全起见,必须恰好被转发两次到达目的地。该包可能在任意一个节点产生,我们需要知道该网络中一共有多少种不同的转发路径。

源地址和目标地址可以相同,但中间节点必须不同。

如下图所示的网络。

1 -> 2 -> 3 -> 1 是允许的

1 -> 2 -> 1 -> 2 或者 1 -> 2 -> 3 -> 2 都是非法的。

输入格式

输入数据的第一行为两个整数N M,分别表示节点个数和连接线路的条数(1<=N<=10000; 0<=M<=100000)。

接下去有M行,每行为两个整数 u 和 v,表示节点u 和 v 联通(1<=u,v<=N , u!=v)。

输入数据保证任意两点最多只有一条边连接,并且没有自己连自己的边,即不存在重边和自环。

输出格式
输出一个整数,表示满足要求的路径条数。
样例输入1
3 3
1 2
2 3
1 3
样例输出1
6
样例输入2
4 4
1 2
2 3
3 1
1 4
样例输出2
10
思路:刚开始还以为是网络流,发现想多了。 就是找长度为3的路径有多少条,并且路径不能在两个点徘徊。直接暴力每个点为起点DFS长度为3就记录。

#include<iostream>#include<stdio.h>#include<string>#include<cstring>#include<algorithm>#include<cmath>#include<vector>#include<queue>#include<set>using namespace std;const int MAXN=10000+5;#define INF 0x3f3f3f3fvector<int>G[MAXN];int ans;void dfs(int u,int fa,int len){//u为当前节点,fa为上一个结点,len为长度 if(len==3){ans++;return;}for(int i=0;i<G[u].size();i++){int v=G[u][i];if(v!=fa)//不为上一个结点,即不产生2个点徘徊的情况 {dfs(v,u,len+1);}}}int main(){int n,m;while(scanf("%d%d",&n,&m)!=EOF){ans=0;for(int i=0;i<=n;i++){G[i].clear();}for(int i=0;i<m;i++){int u,v;scanf("%d%d",&u,&v);G[u].push_back(v);G[v].push_back(u);}for(int i=1;i<=n;i++){if(G[i].size()>0){dfs(i,-1,0);}}printf("%d\n",ans);} return 0;}


PREV-19  九宫重排  搜索 

问题描述
  如下面第一个图的九宫格中,放着 1~8 的数字卡片,还有一个格子空着。与空格子相邻的格子中的卡片可以移动到空格中。经过若干次移动,可以形成第二个图所示的局面。

  我们把第一个图的局面记为:12345678.
  把第二个图的局面记为:123.46758
  显然是按从上到下,从左到右的顺序记录数字,空格记为句点。
  本题目的任务是已知九宫的初态和终态,求最少经过多少步的移动可以到达。如果无论多少步都无法到达,则输出-1。
输入格式
  输入第一行包含九宫的初态,第二行包含九宫的终态。
输出格式
  输出最少的步数,如果不存在方案,则输出-1。
样例输入
12345678.
123.46758
样例输出
3
样例输入
13524678.
46758123.
样例输出
22
思路:最简单的八数码问题。处理方法有很多。主要在判重。可以用hash,奥拓展开,字符串集合等一系列方法。我这里就是把八数码看出一个字符串。用集合判断字符串是否已经出现过。 其他和普通的BFS最短路差不多。

#include<iostream>#include<stdio.h>#include<string>#include<cstring>#include<algorithm>#include<cmath>#include<vector>#include<queue>#include<set>using namespace std;const int MAXN=1000+5;#define INF 0x3f3f3f3f#define MOD 1000000007set<string>vis;//判重集合 int dist[4][2]={0,1,0,-1,1,0,-1,0};//方向向量 bool check(int x,int y)//是否越界 {if(x>=0&&x<3&&y>=0&&y<3){return true;}return false;}void init()//预处理每个位置能移动的下一个位置 {for(int i=0;i<3;i++){for(int j=0;j<3;j++){for(int k=0;k<4;k++){int x=i+dist[k][0];int y=j+dist[k][1];if(check(x,y)){G[i*3+j].push_back(x*3+y);}}}}}struct Node{//状态结果,保存当前的字符串[八数码的状态]和步数 string str;int step;};int bfs(string st,string ed){queue<Node>Q;Node start;start.str=st;start.step=0;Q.push(start);vis.insert(st);while(!Q.empty()){Node top,next;top=Q.front();Q.pop();if(top.str==ed){return top.step;}int index;for(int i=0;i<top.str.size();i++){//找到.的位置。 if(top.str[i]=='.'){index=i;break;}}for(int i=0;i<G[index].size();i++)//枚举.的移动方向 {next.step=top.step+1;next.str=top.str;swap(next.str[index],next.str[G[index][i]]);//移动后交换.和数字位置 if(vis.find(next.str)==vis.end())//判重 {Q.push(next);vis.insert(next.str);}}}return -1;}int main(){init();string st,ed;cin>>st;cin>>ed;cout<<bfs(st,ed)<<endl;return 0;}


PREV-21 回文数字

问题描述
  观察数字:12321,123321 都有一个共同的特征,无论从左到右读还是从右向左读,都是相同的。这样的数字叫做:回文数字。

  本题要求你找到一些5位或6位的十进制数字。满足如下要求:
  该数字的各个数位之和等于输入的整数。
输入格式
  一个正整数 n (10<n<100), 表示要求满足的数位和。
输出格式
  若干行,每行包含一个满足要求的5位或6位整数。
  数字按从小到大的顺序排列。
  如果没有满足条件的,输出:-1
样例输入
44
样例输出
99899
499994
589985
598895
679976
688886
697796
769967
778877
787787
796697
859958
868868
877778
886688
895598
949949
958859
967769
976679
985589
994499
样例输入
60
样例输出
-1
思路:预处理构造5位和6位的所以回文数字,然后排序。然后暴力判断就可。

#include<iostream>#include<stdio.h>#include<string>#include<cstring>#include<algorithm>#include<cmath>#include<vector>#include<queue>#include<set>using namespace std;const int MAXN=10000+5;vector<int>five;vector<int>six;void init(){int num=0;for(int i=1;i<10;i++){for(int j=0;j<10;j++){for(int k=0;k<10;k++){num=i*10000+j*1000+k*100+j*10+i;five.push_back(num);num=i*100000+j*10000+k*1000+k*100+j*10+i;six.push_back(num);}}}sort(five.begin(),five.end());sort(six.begin(),six.end());}int sum_of_num(int n){int s=0;while(n){s+=n%10;n/=10;}return s;}int main(){init();int n;while(scanf("%d",&n)!=EOF){int cnt=0;for(int i=0;i<five.size();i++){if(sum_of_num(five[i])==n){printf("%d\n",five[i]);cnt++;}}for(int i=0;i<six.size();i++){if(sum_of_num(six[i])==n){printf("%d\n",six[i]);cnt++;}}if(cnt==0){printf("-1\n");}} return 0;}

PREV-22 国王的烦恼

问题描述
  C国由n个小岛组成,为了方便小岛之间联络,C国在小岛间建立了m座大桥,每座大桥连接两座小岛。两个小岛间可能存在多座桥连接。然而,由于海水冲刷,有一些大桥面临着不能使用的危险。

  如果两个小岛间的所有大桥都不能使用,则这两座小岛就不能直接到达了。然而,只要这两座小岛的居民能通过其他的桥或者其他的小岛互相到达,他们就会安然无事。但是,如果前一天两个小岛之间还有方法可以到达,后一天却不能到达了,居民们就会一起抗议。

  现在C国的国王已经知道了每座桥能使用的天数,超过这个天数就不能使用了。现在他想知道居民们会有多少天进行抗议。
输入格式
  输入的第一行包含两个整数n, m,分别表示小岛的个数和桥的数量。
  接下来m行,每行三个整数a, b, t,分别表示该座桥连接a号和b号两个小岛,能使用t天。小岛的编号从1开始递增。
输出格式
  输出一个整数,表示居民们会抗议的天数。
样例输入
4 4
1 2 2
1 3 2
2 3 1
3 4 3
样例输出
2
样例说明
  第一天后2和3之间的桥不能使用,不影响。
  第二天后1和2之间,以及1和3之间的桥不能使用,居民们会抗议。
  第三天后3和4之间的桥不能使用,居民们会抗议。
数据规模和约定
  对于30%的数据,1<=n<=20,1<=m<=100;
  对于50%的数据,1<=n<=500,1<=m<=10000;
  对于100%的数据,1<=n<=10000,1<=m<=100000,1<=a, b<=n, 1<=t<=100000。

思路:起初以为要各点都可达,发现不是,只要删掉的边的两个城市不可达就会发生暴动,以为是按时间顺序删除边的。那么我们可以反过来,起初全部边都有->按时间顺序的删除边==起初全部边都没有->按逆序顺序的添加边。 就是逆序使用并查集。注意下可能一天添加多条边但是别重复计算了。

#include<iostream>#include<stdio.h>#include<string>#include<cstring>#include<algorithm>#include<cmath>#include<vector>#include<queue>#include<set>using namespace std;const int MAXN=10000+5;const int MAXM=100000+5;#define INF 0x3f3f3f3fstruct UF{private:int count;//不相交的集合数目int size[MAXN];//结点所在集合的大小,用于优化子树合并后的高度int path[MAXN];//对应根结点。public:void Init(int n){for (int i = 1; i <= n; i++){path[i] = i;size[i] = 1;}count=n;}bool connected(int p, int q)//是否在一个集合{return Find(p) == Find(q);}int Count(){return count;}int Find(int x)//找根结点 O(lgN){while (x != path[x]){// 将p节点的父节点设置为它的爷爷节点  path[x] = path[path[x]];x = path[x];}return x;}bool Union(int p, int q)//如果p,q本来就在同一个连通分量返回true; {//合并q,p集合int i = Find(p);int j = Find(q);if (i == j){return true;}// 将小树作为大树的子树  if (size[i] < size[j]){ path[i] = j; size[j] += size[i]; }else { path[j] = i;size[i] += size[j]; }return false;}};struct Edge{int u,v,t;};Edge e[MAXM];bool cmp(Edge a,Edge b){return a.t>b.t;}int main(){int n,m;while(scanf("%d%d",&n,&m)!=EOF){for(int i=0;i<m;i++){scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].t);}sort(e,e+m,cmp);//按时间递减排序。 UF tmp;//并查集 tmp.Init(n);int ans=0,today=-1;for(int i=0;i<m;i++)//逆序进行并查集的合并 {if(tmp.Union(e[i].u,e[i].v)==false){//如果不在同一个连通分量,发生暴动 if(today!=e[i].t)//可能有一天多条路的情况 {ans++;}today=e[i].t;}}printf("%d\n",ans);} return 0;}


PREV-23 数字游戏 

问题描述
  栋栋正在和同学们玩一个数字游戏。

  游戏的规则是这样的:栋栋和同学们一共n个人围坐在一圈。栋栋首先说出数字1。接下来,坐在栋栋左手边的同学要说下一个数字2。再下面的一个同学要从上一个同学说的数字往下数两个数说出来,也就是说4。下一个同学要往下数三个数,说7。依次类推。

  为了使数字不至于太大,栋栋和同学们约定,当在心中数到 k-1 时,下一个数字从0开始数。例如,当k=13时,栋栋和同学们报出的前几个数依次为:
  1, 2, 4, 7, 11, 3, 9, 3, 11, 7。

  游戏进行了一会儿,栋栋想知道,到目前为止,他所有说出的数字的总和是多少。
输入格式
  输入的第一行包含三个整数 n,k,T,其中 n 和 k 的意义如上面所述,T 表示到目前为止栋栋一共说出的数字个数。
输出格式
  输出一行,包含一个整数,表示栋栋说出所有数的和。
样例输入
3 13 3
样例输出
17
样例说明
  栋栋说出的数依次为1, 7, 9,和为17。
数据规模和约定
  1 < n,k,T < 1,000,000;
思路:最开始想到的是暴力O(n*T),大数据铁定TLE,但是看下数据发现是有规律的。 加上有n个人[先不管求余],那么第一个人报的数是1, 二:1+1 三1+1+2 四 1+1+2+3 五 1+1+2+3+4 六 1+1+2+3+4+5 七 1+1+2+3+4+5+6 八 1+1+2+3+4+5+6+7 九 1+1+2+3+4+5+6+7+8 十 1+1+2+3+4+5+6+7+8+9  发现除了最前面的1之外。后面的是i位置的数是 1+(1+....+i-1), 如果把第2到n+1这n个人看出一组, 那么第n个人[即原n+1位置的人]的数字为1+(1+...+n),这也是栋栋要报的数字。所以O(T)就可以计算出结果了。  每一列+n即可, 注意下精度问题和乘法的取模问题即可。

#include<iostream>#include<stdio.h>#include<string>#include<cstring>#include<algorithm>#include<cmath>#include<vector>#include<queue>#include<set>using namespace std;typedef long long int LL;LL n,k,t;LL sum(LL a0,LL an,LL cnt){//求等差数列的和(a0:第一项 an:最后一项 cnt:项数 if(cnt%2){//注意乘法取模: (a*b)%c== (a%c * b%c)%creturn ((((a0+an)/2)%k)*(cnt%k))%k;}else{return (((cnt/2)%k)*((a0+an)%k))%k;}}int main(){while(scanf("%lld%lld%lld",&n,&k,&t)!=EOF){LL ans=1,st=1,ed=n;for(int i=1;i<t;i++){LL num=(1+sum(st,ed,ed))%k;ans+=num;ed+=n; //循环一次数组+n。 }printf("%lld\n",ans);}return 0;}




PREV-24 邮局

问题描述
  C村住着n户村民,由于交通闭塞,C村的村民只能通过信件与外界交流。为了方便村民们发信,C村打算在C村建设k个邮局,这样每户村民可以去离自己家最近的邮局发信。

  现在给出了m个备选的邮局,请从中选出k个来,使得村民到自己家最近的邮局的距离和最小。其中两点之间的距离定义为两点之间的直线距离。
输入格式
  输入的第一行包含三个整数n, m, k,分别表示村民的户数、备选的邮局数和要建的邮局数。
  接下来n行,每行两个整数x, y,依次表示每户村民家的坐标。
  接下来m行,每行包含两个整数x, y,依次表示每个备选邮局的坐标。
  在输入中,村民和村民、村民和邮局、邮局和邮局的坐标可能相同,但你应把它们看成不同的村民或邮局。
输出格式
  输出一行,包含k个整数,从小到大依次表示你选择的备选邮局编号。(备选邮局按输入顺序由1到m编号)
样例输入
5 4 2
0 0
2 0
3 1
3 3
1 1
0 1
1 0
2 1
3 2
样例输出
2 4
数据规模和约定
  对于30%的数据,1<=n<=10,1<=m<=10,1<=k<=5;
  对于60%的数据,1<=m<=20;
  对于100%的数据,1<=n<=50,1<=m<=25,1<=k<=10。
思路: 按照要求暴力每一种情况。但是得70分。大数据还是TLE过不去。 应该还能剪枝优化的。但是暂时没想到。

#include<iostream>#include<stdio.h>#include<string>#include<cstring>#include<algorithm>#include<cmath>#include<vector>#include<queue>#include<set>using namespace std;const int MAXN=10000+5;#define INF 0x3f3f3f3fstruct Point {double x,y;}peo[55],post[35];double dist(Point a,Point b){return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));}double mindist,dis[35][55];//当前最优距离和,初始化每个人到每个邮局的距离 int n,m,k;int post_num[15],ans_post_num[15];//中间结果,最终结果 void dfs(int index,int cnt){//当前考虑邮局index,已经选了cnt个邮局 if(cnt==k)//已经选了k个邮局 {double sum_dist=0;for(int i=0;i<n;i++)//统计每个人离他最近的邮局的距离 {double min_i_dist=INF;for(int j=0;j<k;j++){min_i_dist=min(min_i_dist,dis[post_num[j]][i]);}sum_dist+=min_i_dist;}if(sum_dist<mindist)//更新最小值 {for(int i=0;i<k;i++){ans_post_num[i]=post_num[i];}mindist=sum_dist;}return;}for(int i=index+1;i<=m-k+cnt;i++){post_num[cnt]=i;dfs(i,cnt+1);}}void init(){mindist=INF*1.0;memset(dis,0,sizeof(dis));for(int i=0;i<m;i++){//计算每个人到每个邮局的距离 for(int j=0;j<n;j++){dis[i][j]=dist(post[i],peo[j]);}}}int main(){while(scanf("%d%d%d",&n,&m,&k)!=EOF){for(int i=0;i<n;i++){scanf("%lf%lf",&peo[i].x,&peo[i].y);}for(int i=0;i<m;i++){scanf("%lf%lf",&post[i].x,&post[i].y);}init();//初始化 for(int i=0;i<=(m-k);i++)//枚举起点 {post_num[0]=i;dfs(i,1);}for(int i=0;i<k;i++)//输出结果。 {printf("%d",ans_post_num[i]+1);if(i!=k-1){printf(" ");}else{printf("\n");}}} return 0;}

PREV-26 最大子阵 

问题描述
  给定一个n*m的矩阵A,求A中的一个非空子矩阵,使这个子矩阵中的元素和最大。

  其中,A的子矩阵指在A中行和列均连续的一块。
输入格式
  输入的第一行包含两个整数n, m,分别表示矩阵A的行数和列数。
  接下来n行,每行m个整数,表示矩阵A。
输出格式
  输出一行,包含一个整数,表示A中最大的子矩阵中的元素和。
样例输入
3 3
-1 -4 3
3 4 -1
-5 -2 8
样例输出
10
样例说明
  取最后一列,和为10。
数据规模和约定
  对于50%的数据,1<=n, m<=50;
  对于100%的数据,1<=n, m<=500,A中每个元素的绝对值不超过5000。
思路:如果会一维的最大连续子序列和[入门dp]的话,这题不难,就是这个的二维实现。预先保存列的前缀和。然后O(n^3)暴力找最大值子阵,注意下因为最大连续和的初始化是0的,如果矩阵不存在大于0的子矩阵的话。是不会更新答案的,这时就是矩阵元素全为负数的情况。只有找到最大的负数输出即可。 代码时间复杂是O(n^3),按照题目的最大数据是会TLE,但是可能测试数据问题满分AC。

#include<iostream>#include<string>#include<cstring>#include<algorithm>#include<cmath>#include<vector>#include<stdio.h>using namespace std;const int MAXN=500+5;int g[MAXN][MAXN],d[MAXN][MAXN];int main(){int n,m;while(scanf("%d%d",&n,&m)!=EOF){int maxval=-5005;memset(d,0,sizeof(d));for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){scanf("%d",&g[i][j]);d[i][j]=d[i-1][j]+g[i][j];//保存列的前缀和。 maxval=max(maxval,g[i][j]);}}/*for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){cout<<d[i][j]<<' ';}cout<<endl;}*/int ans=0,tmp,flag=0;for(int i=1;i<=n;i++){for(int j=i;j<=n;j++){tmp=0;//计算子矩阵 g[i][k]到g[j][k]的值 for(int k=1;k<=m;k++){int val=d[j][k]-d[i-1][k];if(tmp+val<val){tmp=val;}else{tmp+=val;}if(ans<tmp)//更新过结果,即存在子矩阵大于0 {ans=tmp;flag=true;}}}}if(flag)//如果不为全负数的矩阵 {printf("%d\n",ans);}else//矩阵全部元素小于等于0 {printf("%d\n",maxval);}}return 0;}


PREV-27 蚂蚁感冒 

问题描述
  长100厘米的细长直杆子上有n只蚂蚁。它们的头有的朝左,有的朝右。

  每只蚂蚁都只能沿着杆子向前爬,速度是1厘米/秒。

  当两只蚂蚁碰面时,它们会同时掉头往相反的方向爬行。

  这些蚂蚁中,有1只蚂蚁感冒了。并且在和其它蚂蚁碰面时,会把感冒传染给碰到的蚂蚁。

  请你计算,当所有蚂蚁都爬离杆子时,有多少只蚂蚁患上了感冒。
输入格式
  第一行输入一个整数n (1 < n < 50), 表示蚂蚁的总数。

  接着的一行是n个用空格分开的整数 Xi (-100 < Xi < 100), Xi的绝对值,表示蚂蚁离开杆子左边端点的距离。正值表示头朝右,负值表示头朝左,数据中不会出现0值,也不会出现两只蚂蚁占用同一位置。其中,第一个数据代表的蚂蚁感冒了。
输出格式
  要求输出1个整数,表示最后感冒蚂蚁的数目。
样例输入
3
5 -2 8
样例输出
1
样例输入
5
-10 8 -20 12 25
样例输出
3

思路:先分别存两边的蚂蚁的位置,两个蚂蚁相遇且同时调头方向行走可以看出两个蚂蚁擦肩而过继续按原来的方向行走,然后分类讨论,如果感冒的蚂蚁是朝右走的,那么朝左走并且位置大于感冒的蚂蚁的话都会被感染[然后因为方向相反并且位置大于它,所以肯定会相遇],然后,这些新感染感冒的蚂蚁[往左走的]当中位置最小的,将变成"新的起初感染的蚂蚁",然后找往右走的蚂蚁中,位置小于它的,这些也会被感染感冒。    同理可得开始感冒蚂蚁朝左。注意这些蚂蚁别重复计算了。

#include<iostream>#include<string>#include<cstring>#include<algorithm>#include<cmath>#include<vector>#include<stdio.h>using namespace std;vector<int>Left;vector<int>Right;int main(){int n;while(scanf("%d",&n)!=EOF){int fst=0;Left.clear(),Right.clear();for(int i=0;i<n;i++){int val;scanf("%d",&val);if(fst==0)//保存第一个蚂蚁的位置 {fst=val;}if(val>0){Right.push_back(val);}else{Left.push_back(val);}}int ans=1;//因为有一只蚂蚁已经感冒了,即第一只 if(fst>0)//分类讨论 ,大于0即感冒的蚂蚁朝右 {//那么往左走的位置大于它的蚂蚁都会被感染 int tmp=1000;for(int i=0;i<Left.size();i++){if(Left[i]*-1>=fst){ans++;tmp=min(tmp,Left[i]*-1);}}if(tmp!=1000)//如果有往走的蚂蚁被感染到。 {//那么这只蚂蚁就会感染往右走并且位置小于它的其他蚂蚁 for(int i=0;i<Right.size();i++){if(Right[i]<=tmp&&Right[i]!=fst){//注意别重复计算fst了,因为fst肯定在它左边,不然它不会 ans++;//被感染到。 }}}}else//同理 {int tmp=-1;for(int i=0;i<Right.size();i++){if(Right[i]<=fst*-1){ans++;tmp=max(tmp,Right[i]);}}for(int i=0;i<Left.size();i++){if(Left[i]*-1>=tmp&&Left[i]!=fst){ans++;}}}printf("%d\n",ans);}return 0;}

PREV-28 地宫取宝 

问题描述
  X 国王有一个地宫宝库。是 n x m 个格子的矩阵。每个格子放一件宝贝。每个宝贝贴着价值标签。

  地宫的入口在左上角,出口在右下角。

  小明被带到地宫的入口,国王要求他只能向右或向下行走。

  走过某个格子时,如果那个格子中的宝贝价值比小明手中任意宝贝价值都大,小明就可以拿起它(当然,也可以不拿)。

  当小明走到出口时,如果他手中的宝贝恰好是k件,则这些宝贝就可以送给小明。

  请你帮小明算一算,在给定的局面下,他有多少种不同的行动方案能获得这k件宝贝。
输入格式
  输入一行3个整数,用空格分开:n m k (1<=n,m<=50, 1<=k<=12)

  接下来有 n 行数据,每行有 m 个整数 Ci (0<=Ci<=12)代表这个格子上的宝物的价值
输出格式
  要求输出一个整数,表示正好取k个宝贝的行动方案数。该数字可能很大,输出它对 1000000007 取模的结果。
样例输入
2 2 2
1 2
2 1
样例输出
2
样例输入
2 3 2
1 2 3
2 1 5
样例输出
14

记忆化思维DP,不太会写,看了别人的报告才写出来。 详细点这里  注意几个点,初始最大值应该是-1因为可能存在价值为0的位置。 所以数字需要maxv+1,


#include<iostream>#include<stdio.h>#include<cstring>#include<string>#include<algorithm>using namespace std;const int MAXN=55;const int MAXK=15;const int MAXV=15;const int MOD=1000000007;int v[MAXN][MAXN],dp[MAXN][MAXN][MAXK][MAXV];int n,m,k;int dfs(int x,int y,int kk,int maxv){//当前位置(x,y),手上有kk件宝物,这些宝物的最大值 if(dp[x][y][kk][maxv+1]!=-1){return dp[x][y][kk][maxv+1];}if(x==n-1&&y==m-1){//边界 if(kk==k){return dp[x][y][kk][maxv+1]=1;}else if(kk==k-1&&v[x][y]>maxv){return dp[x][y][kk][maxv+1]=1;}else{return dp[x][y][kk][maxv+1]=0;}}long long int cnt=0;if(y<m-1)//右边 {cnt=(cnt+dfs(x,y+1,kk,maxv))%MOD;//不拿 if(v[x][y]>maxv)//拿 {cnt=(cnt+dfs(x,y+1,kk+1,v[x][y]))%MOD;}} if(x<n-1)//下边 {cnt=(cnt+dfs(x+1,y,kk,maxv))%MOD;//不拿 if(v[x][y]>maxv)//拿 {cnt=(cnt+dfs(x+1,y,kk+1,v[x][y]))%MOD;}}return dp[x][y][kk][maxv+1]=cnt%MOD;}int main(){while(scanf("%d%d%d",&n,&m,&k)!=EOF){memset(dp,-1,sizeof(dp));for(int i=0;i<n;i++){for(int j=0;j<m;j++){scanf("%d",&v[i][j]);}}dp[0][0][0][0]=dfs(0,0,0,-1);printf("%d\n",dp[0][0][0][0]);}return 0;}


PREV-31 小朋友排队

问题描述
  n 个小朋友站成一排。现在要把他们按身高从低到高的顺序排列,但是每次只能交换位置相邻的两个小朋友。

  每个小朋友都有一个不高兴的程度。开始的时候,所有小朋友的不高兴程度都是0。

  如果某个小朋友第一次被要求交换,则他的不高兴程度增加1,如果第二次要求他交换,则他的不高兴程度增加2(即不高兴程度为3),依次类推。当要求某个小朋友第k次交换时,他的不高兴程度增加k。

  请问,要让所有小朋友按从低到高排队,他们的不高兴程度之和最小是多少。

  如果有两个小朋友身高一样,则他们谁站在谁前面是没有关系的。
输入格式
  输入的第一行包含一个整数n,表示小朋友的个数。
  第二行包含 n 个整数 H1 H2 … Hn,分别表示每个小朋友的身高。
输出格式
  输出一行,包含一个整数,表示小朋友的不高兴程度和的最小值。
样例输入
3
3 2 1
样例输出
9
样例说明
  首先交换身高为3和2的小朋友,再交换身高为3和1的小朋友,再交换身高为2和1的小朋友,每个小朋友的不高兴程度都是3,总和为9。
数据规模和约定
  对于10%的数据, 1<=n<=10;
  对于30%的数据, 1<=n<=1000;
  对于50%的数据, 1<=n<=10000;
  对于100%的数据,1<=n<=100000,0<=Hi<=1000000。
思路:暴力能拿60分, 学过树状数组的应该知道树状数组的一个常用的应用,就是求逆袭数, 此题就是逆序数的问题。但是一般的求逆序数只需求当前位置前面有多少比自己大, 而此题是相对的, 每一个逆序对是计算2次了。  即当前位置左边比自己大的+当前位置右边比自己小的。  

#include<iostream>#include<stdio.h>#include<cstring>#include<string>#include<algorithm>using namespace std;#define Lowbit(x)(x&(-x)) const int MAXN=1000000+5;typedef long long int LL;LL c[MAXN],n,h[MAXN],l_max[MAXN],r_min[MAXN],cost[MAXN];//c:树状数组,n:人数,h:对应身高,l_max[i]:站在i左边并且身边比i高的人数//r_min[i]:站在i右边并且身高比i低的人数,cost:愤怒值 LL sum(int x){LL s=0;while(x>0){s+=c[x];x-=Lowbit(x);}return s;}void updata(int x,LL val){if(x==0){return;}while(x<=MAXN){c[x]+=val;x+=Lowbit(x);}}void init(){//初始化愤怒值。 cost[0]=0;for(int i=1;i<=100000;i++){cost[i]=cost[i-1]+i;}}int main(){while(scanf("%lld",&n)!=EOF){init();memset(c,0,sizeof(c));for(int i=1;i<=n;i++){scanf("%d",&h[i]);h[i]++;//防止出现h[i]=0的情况。因为树状数组不能有下标0的项 }LL ans=0;for(int i=n;i>=1;i--) {//逆序的求右边比它低的人数[逆序数] r_min[i]=sum(h[i]-1);updata(h[i],1);}memset(c,0,sizeof(c));for(int i=1;i<=n;i++){//顺序的求左边比它高的人数 l_max[i]=sum((MAXN-1))-sum(h[i]);updata(h[i],1);}for(int i=1;i<=n;i++){//计算总愤怒值 ans+=cost[l_max[i]+r_min[i]];}printf("%lld\n",ans);}return 0;}



PREV-32 分糖果

问题描述
  有n个小朋友围坐成一圈。老师给每个小朋友随机发偶数个糖果,然后进行下面的游戏:

  每个小朋友都把自己的糖果分一半给左手边的孩子。

  一轮分糖后,拥有奇数颗糖的孩子由老师补给1个糖果,从而变成偶数。

  反复进行这个游戏,直到所有小朋友的糖果数都相同为止。

  你的任务是预测在已知的初始糖果情形下,老师一共需要补发多少个糖果。
输入格式
  程序首先读入一个整数N(2<N<100),表示小朋友的人数。
  接着是一行用空格分开的N个偶数(每个偶数不大于1000,不小于2)
输出格式
  要求程序输出一个整数,表示老师需要补发的糖果数。
样例输入
3
2 2 4
样例输出
4
思路:按照题意模拟吧。不难。

#include<iostream>#include<stdio.h>#include<cstring>#include<string>#include<algorithm>using namespace std;const int MAXN=100+5;int val[MAXN],tmp[MAXN];//val:每个人的糖果数, tmp:每个人需要分给左边的人的糖果数 int main(){int n;while(scanf("%d",&n)!=EOF){for(int i=0;i<n;i++){scanf("%d",&val[i]);}int ans=0;while(1){int maxv=-1,minv=10000;//这n个人的糖果数目的最大值和最小值 for(int i=0;i<n;i++){maxv=max(maxv,val[i]);minv=min(minv,val[i]);}if(maxv==minv)//相等即说明每个人的糖果数是一样的。 {break;}for(int i=0;i<n;i++){tmp[i]=val[i]/2;//拿出一半的糖果准备分给左边的人 val[i]=val[i]/2;//拿出一半糖果后剩下多少糖果 }for(int i=0;i<n;i++)//开始分给左边的人 {if(i==n-1){ val[i]+=tmp[0];}else{val[i]+=tmp[i+1];}}for(int i=0;i<n;i++){//老师要补发的糖果 if(val[i]%2){val[i]+=1;ans++;}}}printf("%d\n",ans);}return 0;}


PREV-33 兰顿蚂蚁

问题描述


  兰顿蚂蚁,是于1986年,由克里斯·兰顿提出来的,属于细胞自动机的一种。

  平面上的正方形格子被填上黑色或白色。在其中一格正方形内有一只“蚂蚁”。
  蚂蚁的头部朝向为:上下左右其中一方。

  蚂蚁的移动规则十分简单:
  若蚂蚁在黑格,右转90度,将该格改为白格,并向前移一格;
  若蚂蚁在白格,左转90度,将该格改为黑格,并向前移一格。

  规则虽然简单,蚂蚁的行为却十分复杂。刚刚开始时留下的路线都会有接近对称,像是会重复,但不论起始状态如何,蚂蚁经过漫长的混乱活动后,会开辟出一条规则的“高速公路”。

  蚂蚁的路线是很难事先预测的。

  你的任务是根据初始状态,用计算机模拟兰顿蚂蚁在第n步行走后所处的位置。
输入格式
  输入数据的第一行是 m n 两个整数(3 < m, n < 100),表示正方形格子的行数和列数。
  接下来是 m 行数据。
  每行数据为 n 个被空格分开的数字。0 表示白格,1 表示黑格。

  接下来是一行数据:x y s k, 其中x y为整数,表示蚂蚁所在行号和列号(行号从上到下增长,列号从左到右增长,都是从0开始编号)。s 是一个大写字母,表示蚂蚁头的朝向,我们约定:上下左右分别用:UDLR表示。k 表示蚂蚁走的步数。
输出格式
  输出数据为两个空格分开的整数 p q, 分别表示蚂蚁在k步后,所处格子的行号和列号。
样例输入
5 6
0 0 0 0 0 0
0 0 0 0 0 0
0 0 1 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
2 3 L 5
样例输出
1 3
样例输入
3 3
0 0 0
1 1 1
1 1 1
1 1 U 6
样例输出
0 0

思路:按照题意模拟吧。 好像不需要考虑会不会越界的问题。 而且题目也没说越界要怎么处理。。

#include<iostream>#include<stdio.h>#include<cstring>#include<string>#include<algorithm>using namespace std;const int MAXN=100+5;int g[MAXN][MAXN],n,m;struct Node{int x,y,step;char s;Node(int a=0,int b=0,char c=0,int d=0):x(a),y(b),s(c),step(d){}};Node move(Node p,int temp){//按照题目移动,并返回移动后的状态[位置,步数,朝向] Node next;next.step=p.step-1;if(temp==1)//黑格{if(p.s=='U'){next.x=p.x; next.y=p.y+1; next.s='R';}else if(p.s=='D'){next.x=p.x; next.y=p.y-1; next.s='L';}else if(p.s=='L'){next.x=p.x-1;next.y=p.y;next.s='U';}else{next.x=p.x+1;next.y=p.y;next.s='D';}g[p.x][p.y]=0;}else{if(p.s=='U'){next.x=p.x; next.y=p.y-1; next.s='L';}else if(p.s=='D'){next.x=p.x; next.y=p.y+1; next.s='R';}else if(p.s=='L'){next.x=p.x+1;next.y=p.y;next.s='D';}else{next.x=p.x-1;next.y=p.y;next.s='U';}g[p.x][p.y]=1;} return next;}int main(){while(scanf("%d%d",&n,&m)!=EOF){for(int i=0;i<n;i++){for(int j=0;j<m;j++){scanf("%d",&g[i][j]);}}Node start;scanf("%d %d %s %d",&start.x,&start.y,&start.s,&start.step);while(start.step){start=move(start,g[start.x][start.y]);}printf("%d %d\n",start.x,start.y);}return 0;}


3 0
原创粉丝点击