目录
(n+1)/2 :
问题描述
题目简述
给出n个数,zjm想找出出现至少(n+1)/2次的数, 现在需要你帮忙找出这个数是多少?
输入/输出格式
输入格式:
本题包含多组数据:
每组数据包含两行。
第一行一个数字N(1<=N<=999999) ,保证N为奇数。
第二行为N个用空格隔开的整数。
数据以EOF结束。
输出格式:
对于每一组数据,你需要输出你找到的唯一的数。
样例
输入样例:
5
1 3 2 3 3
11
1 1 1 1 1 5 5 5 5 5 5
7
1 1 1 1 1 1 1
输出样例:
3
5
1
问题分析
解题思路
当一个数出现次数大于等于(n+1)/2时,可以发现,如果将该数与其他数做“消去”的话,那么最终剩余的那个数一定就是我们想要找的数。因此,根据这个思路即可求出该数。
参考代码
#include <iostream>
using namespace std;
int a[1000010];
int main()
{
int n;
int size=0;
while(scanf("%d",&n)!=EOF)
{
size=0;
for(int i=1;i<=n;i++)
{
int t;
scanf("%d",&t);
if(size>0&&a[size]!=t)
{
size--;
}
else
{
size++;
a[size]=t;
}
}
printf("%d\n",a[1]);
}
return 0;
}
心得体会
需要稍微想一下,但总体来说并不难。我自己在做这个题的时候也是愣了好长时间才想到该怎么做比较的简便。还是对这类题的做法不太熟悉吧。
ZJM想逃生:
问题描述
题目简述
zjm被困在一个三维的空间中,现在要寻找最短路径逃生!
空间由立方体单位构成。
zjm每次向上下前后左右移动一个单位需要一分钟,且zjm不能对角线移动。
空间的四周封闭。zjm的目标是走到空间的出口。
是否存在逃出生天的可能性?如果存在,则需要多少时间?
输入/输出格式
输入格式:
输入第一行是一个数表示空间的数量。
每个空间的描述的第一行为L,R和C(皆不超过30)。
L表示空间的高度,R和C分别表示每层空间的行与列的大小。
随后L层,每层R行,每行C个字符。
每个字符表示空间的一个单元。’#‘表示不可通过单元,’.‘表示空白单元。
zjm的起始位置在’S’,出口为’E’。每层空间后都有一个空行。
L,R和C均为0时输入结束。
输出格式:
每个空间对应一行输出。
如果可以逃生,则输出如下
Escaped in x minute(s).
x为最短脱离时间。
如果无法逃生,则输出如下
Trapped!
样例
输入样例:
3 4 5
S….
.###.
.##…
###.#
##.##
##…
#.###
####E
1 3 3
S##
#E#
0 0 0
输出样例:
Escaped in 11 minute(s).
Trapped!
问题分析
解题思路
拓展到了三维空间的bfs。这样搜索的话,可能的相邻点就变成了6个:前后,左右和上下。其他的思路和bfs相同,并且数据规模并不是非常的大,最多只有303030,因此不需要剪枝就可以通过。
参考代码
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
char space[32][32][32];
int dx[6]={1,-1,0,0,0,0};
int dy[6]={0,0,1,-1,0,0};
int dz[6]={0,0,0,0,1,-1};
struct msg
{
int x_pos;
int y_pos;
int z_pos;
int times;
};
int l,r,c;
msg start;
void bfs()
{
//printf("message: %d %d %d %d\n",start.x_pos,start.y_pos,start.z_pos,start.times);
queue<msg> q;
int vis[32][32][32];
memset(vis,0,sizeof(vis));
q.push(start);
vis[start.x_pos][start.y_pos][start.z_pos]=1;
while(!q.empty())
{
int a,b,p,d;
a=q.front().x_pos;
b=q.front().y_pos;
p=q.front().z_pos;
d=q.front().times+1;
//printf("search: %d %d %d %d\n",a,b,p,d);
if(space[a][b][p]=='E')
{
printf("Escaped in %d minute(s).\n",q.front().times);
while(!q.empty()) q.pop();
return ;
}
for(int i=0;i<6;i++)
{
if(a+dx[i]>0&&a+dx[i]<=l&&b+dy[i]>0&&b+dy[i]<=r&&p+dz[i]>0&&p+dz[i]<=c)
{
//printf("search pos:%d %d %d,value:%c\n",a+dx[i],b+dy[i],p+dz[i],space[a+dx[i]][b+dy[i]][p+dz[i]]);
if((space[a+dx[i]][b+dy[i]][p+dz[i]]=='.'||space[a+dx[i]][b+dy[i]][p+dz[i]]=='E')&&vis[a+dx[i]][b+dy[i]][p+dz[i]]==0)
{
msg temp;
temp.x_pos=a+dx[i];
temp.y_pos=b+dy[i];
temp.z_pos=p+dz[i];
temp.times=d;
vis[a+dx[i]][b+dy[i]][p+dz[i]]=1;
q.push(temp);
}
}
}
q.pop();
}
printf("Trapped!\n");
}
int main()
{
while(1)
{
cin>>l>>r>>c;
if(l==0&&r==0&&c==0) break;
for(int i=1;i<=l;i++)
{
for(int j=1;j<=r;j++)
{
for(int k=1;k<=c;k++)
{
cin>>space[i][j][k];
//printf("%d %d %d:%c\n",i,j,k,space[i][j][k]);
if(space[i][j][k]=='S')
{
start.x_pos=i;
start.y_pos=j;
start.z_pos=k;
start.times=0;
}
}
}
}
bfs();
}
return 0;
}
心得体会
做这个题的时候莫名的想到了《港诡实录》,可能最近比较火吧。(划掉)这个题看上去挺吓人的,但实际上仔细分析就会发现只是bfs的一个简单的变形,其实不难。
东东扫宿舍:
问题描述
题目简述
东东每个学期都会去寝室接受扫楼的任务,并清点每个寝室的人数。
每个寝室里面有ai个人(1<=i<=n)。从第i到第j个宿舍一共有sum(i,j)=a[i]+…+a[j]个人
这让宿管阿姨非常开心,并且让东东扫楼m次,每一次数第i到第j个宿舍sum(i,j)
问题是要找到sum(i1, j1) + … + sum(im,jm)的最大值。且ix <= iy <=jx和ix <= jy <=jx的情况是不被允许的。也就是说m段都不能相交。
注:1 ≤ i ≤ n ≤ 1e6 , -32768 ≤ ai ≤ 32767 人数可以为负数。。。。(1<=n<=1000000)
输入/输出格式
输入格式:
输入m,输入n。后面跟着输入n个ai
输出格式:
输出最大和
样例
输入样例:
1 3 1 2 3
2 6 -1 4 -2 3 -2 3
输出样例:
6
8
问题分析
解题思路
题目给的提示是要求使用dp解决。但是这个状态好难想啊。想了半天,也没想到好方法,最后只能参考网上的题解的思路,看了之后恍然大悟。我们设f[i][j]为在选取第j个数的条件下,将前j个数分为i段所能得到的最大和,那么它有两种可能的取值,第一种是num[j]加入到了前一次的最后一段中,此时得到的结果时f[i][j-1]+num[j];第二种是num[j]作为单独的新的一段,此时得到的结果是f[i-1][k]+num[j]。两者取最大值即为结果。进一步可以发现,f[i][j]的取值只与f[i]和f[i-1]有关,因此可以使用滚动数组进行空间优化。之后还可以发现不需要记录不需要j-1之前的最大和具体发生在k位置,只需要在j-1处记录最大和,就可以算出f[i-1][k]+num[j]。用一个数组pre专门记录该值即可。
参考代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int dp[1000010];
int num[1000010];
int pre[1000010];
int m,n;
void init()
{
memset(dp,0,sizeof(dp));
memset(num,0,sizeof(num));
memset(pre,0,sizeof(pre));
}
int main()
{
while(scanf("%d %d",&m,&n)!=EOF)
{
int t;
init();
for(int i=1;i<=n;i++)
{
scanf("%d",&num[i]);
}
for(int i=1;i<=m;i++)
{
t=-1e8;
for(int j=i;j<=n;j++)
{
dp[j]=max(pre[j-1],dp[j-1])+num[j];
pre[j-1]=t;
t=max(t,dp[j]);
}
}
cout<<t<<endl;
}
return 0;
}
心得体会
对于dp问题,找状态是一个非常困难的过程,一旦找到状态,问题也就得到了不小的简化。所以,对于dp问题还需要多加练习。
括号序列:
问题描述
题目简述
We give the following inductive definition of a “regular brackets” sequence:
the empty sequence is a regular brackets sequence,
if s is a regular brackets sequence, then (s) and [s] are regular brackets sequences, and
if a and b are regular brackets sequences, then ab is a regular brackets sequence.
no other sequence is a regular brackets sequence
For instance, all of the following character sequences are regular brackets sequences:
(), [], (()), ()[], ()[()]
while the following character sequences are not:
(, ], )(, ([)], ([(]
Given a brackets sequence of characters a1a2 … an, your goal is to find the length of the longest regular brackets sequence that is a subsequence of s. That is, you wish to find the largest m such that for indices i1, i2, …, im where 1 ≤ i1 < i2 < … < im ≤ n, ai1ai2 … aim is a regular brackets sequence.
Given the initial sequence ([([]])], the longest regular brackets subsequence is [([])].
输入/输出格式
输入格式:
The input test file will contain multiple test cases. Each input test case consists of a single line containing only the characters (, ), [, and ]; each input test will have length between 1 and 100, inclusive. The end-of-file is marked by a line containing the word “end” and should not be processed.
输出格式:
For each input case, the program should print the length of the longest possible regular brackets subsequence on a single line.
样例
输入样例:
((()))
()()()
([]])
)[)(
([][][)
end
输出样例:
6
6
4
0
6
问题分析
解题思路
这个题和课上的例题稍有不同,但是大致思路是一样的,因为要求最长的合法括号序列的长度,因此,定义状态f[i][j]为子序列[i,j]的最长合法括号序列的长度。那么这个状态可能有以下两种取值方法:1.该状态的左右两端括号是匹配的,那么,f[i][j]=f[i+1][j-1]+2。2.f[i][j]=f[i][k]+f[k+1][j]。以上两种情况取最大值即为f[i][j]的结果。最终的结果为f[1][n]。
参考代码
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
int dp[105][105];
string str;
void init()
{
memset(dp,0,sizeof(dp));
}
int main()
{
while(cin>>str)
{
init();
if(str=="end") break;
int n=str.length();
for(int length=1;length<n;length++)
{
for(int i=0;i<=n-2&&i+length<=n-1;i++)
{
if(str[i]=='('&&str[i+length]==')'||str[i]=='['&&str[i+length]==']')
{
dp[i][i+length]=dp[i+1][i+length-1]+2;
}
for(int k=i;k<i+length;k++)
{
dp[i][i+length]=max(dp[i][i+length],dp[i][k]+dp[k+1][i+length]);
}
}
}
cout<<dp[0][n-1]<<endl;
str.clear();
}
return 0;
}
心得体会
稍微有一点变形的括号序列问题,主要还是熟悉一下区间dp问题的做法。不算很难,但是如果要我独立思考方法的话感觉还是比较有难度的。
作业调度:
问题描述
题目简述
马上假期就要结束了,zjm还有 n 个作业,完成某个作业需要一定的时间,而且每个作业有一个截止时间,若超过截止时间,一天就要扣一分。
zjm想知道如何安排做作业,使得扣的分数最少。
Tips: 如果开始做某个作业,就必须把这个作业做完了,才能做下一个作业。
输入/输出格式
输入格式:
有多组测试数据。第一行一个整数表示测试数据的组数
第一行一个整数 n(1<=n<=15)
接下来n行,每行一个字符串(长度不超过100) S 表示任务的名称和两个整数 D 和 C,分别表示任务的截止时间和完成任务需要的天数。
这 n 个任务是按照字符串的字典序从小到大给出。
输出格式:
每组测试数据,输出最少扣的分数,并输出完成作业的方案,如果有多个方案,输出字典序最小的一个。
样例
输入样例:
2
3
Computer 3 3
English 20 1
Math 3 2
3
Computer 3 3
English 6 3
Math 6 3
输出样例:
2
Computer
Math
English
3
Computer
English
Math
问题分析
解题思路
本题是状态压缩dp的练习题。使用二进制压缩的方法,是每一位的二进制数代表一项作业,其中1代表做该作业,0代表不做。定义f[S]为做完S对应的作业时,扣的最少的分数,由此分析可知,相邻的完成情况之间,其对应状态值的二进制数仅有一位在当前状态为1,之前状态为0。因此每次我们遍历相邻两个状态,将f[S]置为f[last_S]+cost[j]的最小值即可。最终结果为f[1111111…](n个1)。由于需要按照字典序最小的顺序输出,因此,每次遍历时,记录此状态之前完成的作业的状态值和迁移到本状态时完成的作业,从最终状态开始向前遍历,将前序的作业对应标签存入栈中,最后输出栈中对应作业标签的名称即为最小字典序的方案。
参考代码
#include <iostream>
#include <cstring>
#include <string>
#include <stack>
using namespace std;
const int maxn = 18, INF = 1e8;
int t, n;
struct work
{
string name;
int deadline, cost;
} a[maxn];
struct node1
{
int pre, time, score, ind;
} dp[1 << 15];
int main ()
{
cin >> t;
while(t --)
{
cin >> n;
for(int i = 0; i < n; i ++)
cin >> a[i].name >> a[i].deadline >> a[i].cost;
memset(dp, 0, sizeof(dp));
int nbit = 1 << n;
for(int i = 1; i < nbit; i ++)
{
dp[i].score = INF;
for(int j = n - 1; j >= 0; j --)
{
int temp = 1 << j;
if(!(temp & i)) continue;
int tem = i - temp;
int t = dp[tem].time + a[j].cost - a[j].deadline;
t = t > 0 ? t : 0;
if(t + dp[tem].score < dp[i].score)
{
dp[i].score = t + dp[tem].score;
dp[i].pre = tem;
dp[i].ind = j;
dp[i].time = dp[tem].time + a[j].cost;
}
}
}
cout << dp[nbit - 1].score << endl;
stack<int> q;
int t = nbit - 1;
while(dp[t].time)
{
q.push(dp[t].ind);
t = dp[t].pre;
}
while(!q.empty())
{
int k = q.top();
cout << a[k].name << endl;
q.pop();
}
}
return 0;
}
心得体会
还是一道难想但比较好做的题目。状压dp是比较抽象的一种问题,但是本身的解题过程和状态转移方程不算很复杂,也比较的好懂。还是那句话,dp问题能不能想到状态是什么,是问题解决的关键所在。