目录:
T1:积木
T2:扫雷
T3:无根树
T4:幸运数
T5:子矩阵
T1:积木
题目描述
味味有一个A×B×C 的长方体积木,积木是有1×1×1 的小积木块组成的。我们设定这个长方体的高为A,宽为B,长为C。(为方便起见,长方体的长不一定要比宽的数值大)。
现在味味在这个长方体中的的左上角挖去了一个(A-1)×(B-2)×(C-2)的小长方体。并且告诉你被挖去长方体的体积为n,即n=(A-1)×(B-2)×(C-2)。现在问你,被挖去小长方体后,原有长方体积木中剩下的1×1×1的小积木块最少和最多分别是多少个。也就是说,在告诉你n值的前提下,求min{A×B×C-n}和max{A×B×C-n}。
输入
输入文件名为 block.in。
输入共1行,仅一个正整数n。
输出
输出文件名为 block.out。
输出共1行包含两个用空格隔开的正整数,依次表示最少剩余小积木块和最多剩余小积木块个数。
样例输入
【样例输入1】
4
【样例输入2】
7
样例输出
【样例输出1】
28 41
【样例输出2】
47 65
分析:
这道题做法有很多种。我的做法是:
先求出全部能整除的数,再根据题目给出的公式判断,赋值给minn,maxx的值找规律是8*n+9,开头可以直接求出。
CODE:
#include<cmath>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<iostream>
using namespace std;
typedef long long LL;
LL i,j,x;
LL minn=0x7fffffff,maxx,n;
LL a[100001];
int main(){
freopen("block.in","r",stdin);
freopen("block.out","w",stdout);
scanf("%d",&n);
maxx=8*n+9; //直接求maxx
for(i=1;i<=sqrt(n);i++)
if(n%i==0)
{
x++;
a[x]=i;
if(n/i!=i)
{
x++; //找能整除的数
a[x]=n/i;
}
}
for(i=1;i<=x;i++)
for(j=1;j<=x;j++)
if(a[i]*a[j]*(n/a[i]/a[j])==n&&(n/a[i]/a[j]+2)*(a[i]+2)*(a[j]+1)-n<minn) //题目公式
minn=(n/a[i]/a[j]+2)*(a[i]+2)*(a[j]+1)-n; //赋值
cout<<minn<<" "<<maxx; //输出
return 0;
}
T2:扫雷
题目描述
Windows中的扫雷游戏是大家都熟悉的小游戏,今天,味味也设计了一个简易的扫雷游戏。味味设计的扫雷游戏功能如下:
一、程序一开始会读入扫雷区域大小n,表示游戏区域有n*n个小方格组成,接下来会读入n行信息,每行有n个整数(每个整数可能是0,也可能是1),每两个整数之间用一个空格分隔。其中0 所在位置表示该小方格内没有地雷,1 所在位置表示该小方格内有地雷(游戏开始时,扫雷区域中必定包含至少一个地雷)。接下来每行输入两个用空格分开的正整数i和j,每一行的一对i和j表示用户用鼠标单击扫雷区域中第i 行第j 列位置上的小方格(就象我们使用Windows 中扫雷游戏一样),i 和j 表示的位置必定在扫雷区域内。程序每输入一对i和j,就马上进行相应的处理(就象我们在Windows 中鼠标单击某个小方块就会出现结果一样)。
二、程序将根据读入的一组i 和j的值来对扫雷区域作相应处理,具体的处理规则如下:
(1)如果i和j 表示的小方格内没有地雷、而且也没有被处理过(就是第i行第j 列的数值是0),那么将以该小方格为中心的一个正方形区域内所有没有地雷的小方格都赋值为-1(表示该区域的地砖已经被掀开了)。如果在当前正方形区域内有一个位置号为i1和j1(注意:i1<>i并且j1<>j)的小方格内恰好有地雷,则此地雷就被顺利扫除,将该位置标记为-2。如果该正方形区域内某些小方格已经被处理过,则对这些小方格不再做任何处理。举个例子来说明一下,假如输入信息如上图左边所示,那么处理结果就如右边所示。
(2)如果i 和j 表示的小方格已经被处理过(就是第i 行第j 列的数值是-1 或者是-2),那么不作任何处理,继续去读取下一行的i和j 的值。
(3)如果i和j 表示的小方格刚好有地雷,并且该小方格没有被处理过(就是第i 行第j 列的数值是1),那么表示用户触雷,马上输出信息“GAME OVER!”,程序结束。
三、如果在读入i和j的过程中一直没有触雷,那么就一直按照位置信息处理下去,直到满足下列条件之一,就输出相应信息并结束程序:
(1)读入的i和j的值都是0(表示用户不再在某个小方格内单击鼠标右键了),则输出处理后整个扫雷区域的状态(就是输出n行n列的方阵,每行中两个整数之间用一个空格分隔,末尾没有多余空格),然后程序结束。
(2)如果某次处理完后,游戏区域内所有的地雷都被扫除了,那么不必再读入下一行的信息,输出信息“YOU ARE WINNER!”,程序结束。
输入
输入文件名为mine.in。第一行一个整数n(n<=50),接下来是一个n*n 的方阵。再接下来是若干行,每行空格分隔的两个整数,表示i和j,以0 0结束。
输出
输出文件名为mine.out。包含一行,可能输出“YOU ARE WINNER!”,可能输出“GAME OVER!”,
也可能输出一个处理后的方阵。
样例输入
【样例输入1】
6
0 0 0 0 0 0
0 0 1 0 0 0
1 0 0 0 1 0
0 0 0 0 0 0
0 1 0 0 0 1
0 0 0 0 0 0
1 1
3 4
5 5
4 6
5 2
2 3
0 0
【样例输入2】
6
0 0 0 0 0 0
0 0 1 0 0 0
1 0 0 0 1 0
0 0 0 0 0 0
0 1 0 0 0 1
0 0 0 0 0 0
1 1
3 4
5 5
4 6
2 3
0 0
样例输出
【样例输出1】
GAME OVER!
【样例输出2】
-1 -1 0 0 0 0
-1 -1 -2 -1 -1 0
1 0 -1 -1 -2 0
0 0 -1 -1 -1 -1
0 1 0 -1 -1 -2
0 0 0 -1 -1 -1
分析:
这道题是一道模拟题。
利用一个深搜,遍历整张地图,根据题目四个条件改变地图状态,
判断是否符合Game Over或You Are Winner。如果都不是,就输出当前地图。
CODE:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int x,y,n,a[51][51];
int dx[9]={0,-1,-1,-1,0,0,1,1,1}; //导航
int dy[9]={0,-1,0,1,-1,1,-1,0,1};
bool check()
{
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (a[i][j]>0) return 0;
return 1; //判断是否扫雷成功
}
int main()
{
freopen("mine.in","r",stdin);
freopen("mine.out","w",stdout);
scanf("%d",&n);
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
scanf("%d",&a[i][j]); //读入
while (scanf("%d %d",&x,&y)==2) //非0读入
{
if (a[x][y]==1) //碰到雷了
{
printf("GAME OVER!"); //失败
return 0;
}
if (!x&&!y)
{
for (int i=1;i<=n;i++) //要输出地图了
{
printf("%d",a[i][1]); //怕多输空格,先输出第一列
for (int j=2;j<=n;j++)
printf(" %d",a[i][j]); //输出其它
printf("\n");
}
return 0;
}
else if (a[x][y]>=0)
for (int i=0;i<9;i++) //深搜
{
int xx=x+dx[i];
int yy=y+dy[i];
if (xx>0&&yy>0&&xx<=n&&yy<=n&&a[xx][yy]>=0) //合法
{
if (a[xx][yy]==0) a[xx][yy]=-1; //改变状态
else a[xx][yy]=-2; //另个状态
}
}
if (check()) //全部都不是雷
{
printf("YOU ARE WINNER!"); //成功
return 0;
}
}
return 0;
}
T3:无根树
题目描述
味味最近对树很感兴趣,什么是树呢?树就是有n个点和n-1条边形成的无环连通无向图。
今年2012年浙江省队选拔赛中味味发现了一个树中最长链(就是树当中距离最远的点对)试题,于是她着手对树进行了一些研究和思考。
味味在研究过程中想知道,对于一个无根树,当节点i作为根的时候树的高是多少。所谓树高指的是从根节点出发,到离根节点最远叶子节点所经过的节点的总数,详见输入输出样例1。
味味现在遇到了一些烦心的事情,不想再继续思考了,请你帮助她解决这个问题。
输入
输入文件名为 tree.in,共 N 行。第一行为一个正整数 N,表示树的节点个数。第2 行到第 N行里,每行两个用空格隔开的正整数a 和b,表示a 与b有连边。
输出
输出文件 tree.out 共N 行,第i行表示以节点i为根时的树高。
样例输入
【样例输入1】
3
1 2
2 3
【样例输入2】
4
1 4
2 4
3 4
样例输出
【样例输出1】
3
2
3
【样例输出2】
3
3
3
2
分析:
这道题还以为是什么树型结构题,结果根本与树无关 ……
这道题可以用深搜和广搜,很多人都用深搜,那我说广搜做法:
这不是等价于求树上每个点到树上其他点距离的最大值嘛。
这就用到了一个知识点:树上任一点到树直径的其中一个端点的距离最远。 直径:树的直径是指树的最长简单路。
先求直径,然后分别从直径的两端点bfs,对于求得的两个距离取最大值即可。
直径怎么求?
两遍bfs。先随便选一个点做起点bfs找到距它距离最大的点,再从那个点进行bfs,则这次bfs找到的最长路即为树的直径(即这次找到的距它距离最大的点就是直径的另一个端点)
蕴藏在其中的原理: 设起点为x,第一次bfs找到的终点y一定是树的直径的一个端点(根据某知识点),然后再bfs找到的显然是另一个端点。
然后分别以直径的两个端点为起点进行bfs遍历树,更新每个节点的距离,然后从两距离中取最大值即可。
细心的你一定发现了,求直径时的第二遍bfs已经是从直径的一个端点出发了,我们完全可以顺带着更新路上的值了,这样我们又少了一遍bfs,这真令人高兴(并没有)
别看做了三遍bfs,其实时间复杂度只有O(n)。比dfs做法还快
CODE:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
vector<int>edge[1005]; //存树
queue<int>q; //广搜队列
int n,dep[1005],dep2[1005]; //求直径前两个广搜表示状态的数组
int bfs1()
{
memset(dep,-1,sizeof(dep));
dep[1]=0;int xx=1;
q.push(1);
while(!q.empty()) //广搜第一次,找一个端点
{
int x=q.front();q.pop();
for(int i=0;i<edge[x].size();i++)
if(dep[edge[x][i]]<0)
{
dep[edge[x][i]]=dep[x]+1;
xx=edge[x][i];
q.push(edge[x][i]);
//因为是树形结构,所以不需要像真的最短路一样加判断,一旦搜到一定是最远的(之一)
}
}
return xx;
}
int bfs2(int s) //找直径广搜,更新距这个端点的距离
{
memset(dep,-1,sizeof(dep));
dep[s]=1;int xx=s;
q.push(s);
while(!q.empty())
{
int x=q.front();q.pop();
for(int i=0;i<edge[x].size();i++)
if(dep[edge[x][i]]<0)
{
dep[edge[x][i]]=dep[x]+1;
xx=edge[x][i];
q.push(edge[x][i]);
}
}
return xx;
}
void bfs3(int s)//更新距另一个端点的距离
{
memset(dep2,-1,sizeof(dep2));
dep2[s]=1;
q.push(s);
while(!q.empty())
{
int x=q.front();q.pop();
for(int i=0;i<edge[x].size();i++)
if(dep2[edge[x][i]]<0)
{
dep2[edge[x][i]]=dep2[x]+1;
q.push(edge[x][i]);
if(dep2[edge[x][i]]>dep[edge[x][i]])
dep[edge[x][i]]=dep2[edge[x][i]];
//要是这个距离大于从另一端的距离就直接覆盖掉..
//反正树形结构一个点只会跑一次..
}
}
}
int main()
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
scanf("%d", &n);
for(int i=1;i<n;i++)
{
int a, b;
scanf("%d%d", &a, &b);
edge[a].push_back(b);
edge[b].push_back(a);
}
int x=bfs1();int y=bfs2(x);bfs3(y); //连续一波广搜,直接找出直径
for(int i=1;i<=n;i++)
printf("%d\n", dep[i]);
return 0;
}
T4:幸运数
题目描述
4和7是味味的幸运数字。幸运数是那些只由幸运数字组成的正整数。如47,477是幸运数,而5,17,417 就不是幸运数。
定义next(x)为大于或等于x的最小的幸运数。
味味对以下表达式的值很感兴趣 :
next(L)+next(L+1)+…+next(R-1)+next®。
现在告诉你L和R的值,希望你能帮助味味计算出这个表达式的值。
输入
输入文件sum.in仅一行包含两个正整数L和R(1≤L≤R≤10^9 ),L和R的值之间用一个空格分隔。
输出
输出文件sum.out 只有一行一个整数,表示表达式的值。
样例输入
【样例输入1】
2 7
【样例输入2】
7 7
样例输出
【样例输出1】
33
【样例输出2】
7
分析:
这道题一开始我打表+O2优化凑90分。
正解是统计所有幸运数再枚举。
CODE:
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
long long cnt,ret,k,b[100001][3],a[10001],l,r,sum;
int map_[3]={4,7}; //幸运数
void bfs(){
int head=0,tail=2;
b[1][0]=4;b[2][0]=7;b[1][1]=b[2][1]=1;
cnt=2;
a[1]=4;
a[2]=7;
while(head<=tail){
head++;
for(int i=0;i<=1;i++){
if(b[head][1]>=10)break;
tail++;
b[tail][0]=b[head][0]*10+map_[i];
b[tail][1]=b[head][1]+1;
cnt++; //找幸运数
a[cnt]=b[tail][0];
for(int j=1;j<cnt;j++){
if(b[tail][0]==a[j]){ //不合法
tail--;
cnt--; //减掉幸运数
break;
}
}
}
}
}
int main(){
freopen("sum.in","r",stdin);
freopen("sum.out","w",stdout);
bfs();
cin>>l>>r;
for(int i=1;i<=cnt;i++){
if(a[i]>l){
ret=i-1; //枚举
break;
}
}
if(a[ret]!=l)ret++;
sum=0;
while(l<=r){ //二分答案
int p=min(r,a[ret])-l+1;
sum=sum+a[ret]*p; //表达式
l=a[ret]+1; //缩小范围
ret++;
}
cout<<sum;
return 0;
}
T5:子矩阵
题目描述
给出如下定义:
1. 子矩阵: 从一个矩阵当中选取某些行和某些列交叉位置所组成的新矩阵(保持行与列的相对顺序) 被称为原矩阵的一个子矩阵。
例如,下面左图中选取第 2、 4 行和第 2、 4、 5 列交叉位置的元素得到一个 2*3 的子矩阵如右图所示。
- 相邻的元素:矩阵中的某个元素与其上下左右四个元素(如果存在的话)是相邻的。
- 矩阵的分值: 矩阵中每一对相邻元素之差的绝对值之和。
本题任务:给定一个 n 行 m 列的正整数矩阵,请你从这个矩阵中选出一个 r 行 c 列的子矩阵,使得这个子矩阵的分值最小,并输出这个分值。
输入
输入文件名为 submatrix.in。
第一行包含用空格隔开的四个整数 n, m, r, c,意义如问题中所述,每两个整数之间用一个空格隔开。
接下来的 n 行, 每行包含 m 个用空格隔开的整数,用来表示问题中那个 n 行 m 列的矩阵。
输出
输出文件名为 submatrix.out。
输出共 1 行,包含 1 个整数,表示满足题目描述的子矩阵的最小分值。
样例输入
Sample Input1
5 5 2 3
9 3 3 3 9
9 4 8 7 4
1 7 4 6 6
6 8 5 6 9
7 4 5 6 1
Sample Input2
7 7 3 3
7 7 7 6 2 10 5
5 8 8 2 1 6 2
2 9 5 5 6 1 7
7 9 3 6 1 7 8
1 9 1 4 7 8 8
10 5 9 1 1 8 10
1 3 1 5 4 8 6
样例输出
Sample Output1
6
Sample Output2
16
分析:
这道题是有难度的,注意理解。
边搜索边dp,枚举选那些行,然后算出每列之间的分数w,两列之间的分数v。
就可以开始dp了,f[i][j]表示已经选了i列,最后一列是j的最小分数。
状态转移方程:f[i][j]=min(f[i-1][k[+w[j]+v[j][k])。
CODE:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
#define min(a,b)(a<b?a:b) //手动写函数好像更快?
int n,m,l,r;
int a[20][20],f[20][20];
//f[i][j]表示已经选了i列,最后一列是j的最小分数
int w[20],v[20][20],pre[20];
//每列之间的分数w,两列之间的分数v
int ans=1<<30;
void dp()
{
memset(f,127,sizeof(f));
memset(w,0,sizeof(w));
memset(v,0,sizeof(v));
int i,j,k;
for(i=1;i<=m;i++)
{
for(j=1;j<l;j++)
{
w[i]=w[i]+abs(a[pre[j]][i]-a[pre[j+1]][i]);
}
//cout<<w[i]<<"w"<<endl;
}
for(i=1;i<=m;i++)
{
for(j=i+1;j<=m;j++)
{
for(k=1;k<=l;k++)
{
v[i][j]=v[i][j]+abs(a[pre[k]][i]-a[pre[k]][j]);
}
//cout<<v[i][j]<<"v";
}
}
f[0][0]=0;
for(i=1;i<=r;i++) //选了第i列
{
for(j=i;j<=m;j++) //最后一列为j
{
for(k=0;k<j;k++) //枚举上一次选的列数
{
f[i][j]=min(f[i-1][k]+w[j]+v[k][j],f[i][j]); //动态能量转移方程
}
}
}
for(i=r;i<=m;i++)
{
ans=min(ans,f[r][i]);
}
}
void dfs(int x,int y)
{
if(y>l)
{
dp();
return;
}
if (x>n) return;
dfs(x+1,y); //这一列不选 ,下一个
pre[y]=x; //记录x的值在pre[y]中体现
dfs(x+1,y+1); //选他
}
int main()
{
int i,j;
cin>>n>>m>>l>>r;
for(i=1;i<=n;i++)
{
for(j=1;j<=m;j++)
{
scanf("%d",&a[i][j]);
}
}
dfs(1,1);
cout<<ans;
}