算法训练 方格取数
资源限制
时间限制:1.0s 内存限制:256.0MB
问题描述
设有N*N的方格图(N<=10),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字0。
某人从图的左上角的A 点(1,1)出发,可以向下行走,也可以向右走,直到到达右下角的B点(N,N)。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。
此人从A点到B 点共走两次,试找出2条这样的路径,使得取得的数之和为最大。
输入格式
输入的第一行为一个整数N(表示N*N的方格图),接下来的每行有三个整数,前两个表示位置,第三个数为该位置上所放的数。一行单独的0表示输入结束。
输出格式
只需输出一个整数,表示2条路径上取得的最大的和。
样例输入
8
2 3 13
2 6 6
3 5 7
4 4 14
5 2 21
5 6 4
6 3 15
7 2 14
0 0 0
样例输出
67
此题是一个经典的动态规划问题,我们用 m p mp mp数组存储方格中的数值。在每次行走中,只能选择向右或向下,因此,容易想到可以定义一个二位数组 d p [ i ] [ j ] dp[i][j] dp[i][j],表示从 ( 1 , 1 ) (1,1) (1,1)格子走到 ( i , j ) (i,j) (i,j)格子可取得的最大和,此时状态转移方程为 d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) + m p [ i ] [ j ] dp[i][j]=max(dp[i-1][j],dp[i][j-1])+mp[i][j] dp[i][j]=max(dp[i−1][j],dp[i][j−1])+mp[i][j],由此求出的结果 d p [ n ] [ n ] dp[n][n] dp[n][n]即表示从 ( 1 , 1 ) (1,1) (1,1)格子走到 ( n , n ) (n,n) (n,n)格子可取得的最大和,这只是走一次的最大答案,而题目要求行走两次。
第一想法考虑是否可以按以上方法再计算一次取得最大值求和,这显然是不行的,举个反例:
设某次
m
p
mp
mp数组为
0 5 0
0 0 1
2 5 1
当我们分别走两次取最大值时,第一次行走路径为:
0 5 0
0 0 1
2 5 1
第二次行走路径为(第一次走过元素变为
0
0
0):
0 0 0
0 0 1
2 0 0
此时还有元素
1
1
1未被选中。
而实际上我们可以看出,如果两次行走路径为(第一次走过元素变为
0
0
0):
0 5 0
0 0 1
2 5 1
和
0 0 0
0 0 0
2 5 0
可以将所有元素取走。第一想法无法准确求出两次行走的整体最优解。
因此我们考虑是否存在一种解法可以准确求出两次行走的整体最优解。答案是肯定的,我们在以上思考基础上,将 d p dp dp数组扩充至四维即 d p [ i ] [ j ] [ k ] [ f ] dp[i][j][k][f] dp[i][j][k][f],表示以 ( 1 , 1 ) (1,1) (1,1)格子为起点,一条路径走到 ( i , j ) (i,j) (i,j)格子,另一条路径走到 ( k , f ) (k,f) (k,f)格子可取得的最大和,此时状态转移方程为 d p [ i ] [ j ] [ k ] [ f ] = m a x ( m a x ( d p [ i − 1 ] [ j ] [ k − 1 ] [ f ] , d p [ i − 1 ] [ j ] [ k ] [ f − 1 ] ) , m a x ( d p [ i ] [ j − 1 ] [ k − 1 ] [ f ] , d p [ i ] [ j − 1 ] [ k ] [ f − 1 ] ) ) + m p [ i ] [ j ] + m p [ k ] [ f ] dp[i][j][k][f]=max(max(dp[i-1][j][k-1][f],dp[i-1][j][k][f-1]),max(dp[i][j-1][k-1][f],dp[i][j-1][k][f-1]))+mp[i][j]+mp[k][f] dp[i][j][k][f]=max(max(dp[i−1][j][k−1][f],dp[i−1][j][k][f−1]),max(dp[i][j−1][k−1][f],dp[i][j−1][k][f−1]))+mp[i][j]+mp[k][f]相较于二维,我们只需要特殊考虑当 i = = k 、 j = = f i==k、j==f i==k、j==f,路径重合时,此时 m p [ i ] [ j ] = = m p [ k ] [ f ] mp[i][j]==mp[k][f] mp[i][j]==mp[k][f],只需减去其中一个即可。此时结果为 d p [ n ] [ n ] [ n ] [ n ] dp[n][n][n][n] dp[n][n][n][n],表示以 ( 1 , 1 ) (1,1) (1,1)格子为起点,两条路径均走到 ( n , n ) (n,n) (n,n)格子的最大和,与题目答案定义一致。
代码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
const int N=12;
int n,m;
int a,b,c;
int mp[N][N];
int ans;
int dp[N][N][N][N];
int main(){
cin>>n;
while(~scanf("%d%d%d",&a,&b,&c)){
if(a==0&&b==0&&c==0)break;
mp[a][b]=c;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
for(int k=1;k<=n;k++){
for(int f=1;f<=n;f++){
dp[i][j][k][f]=max(max(dp[i-1][j][k-1][f],dp[i-1][j][k][f-1]),max(dp[i][j-1][k-1][f],dp[i][j-1][k][f-1]))+mp[i][j]+mp[k][f];
if(i==k&&j==f)dp[i][j][k][f]-=mp[i][j];
}
}
}
}
cout<<dp[n][n][n][n]<<endl;
return 0;
}
算法训练 关联矩阵
资源限制
时间限制:1.0s 内存限制:512.0MB
问题描述
有一个n个结点m条边的有向图,请输出他的关联矩阵。
输入格式
第一行两个整数n、m,表示图中结点和边的数目。n<=100,m<=1000。
接下来m行,每行两个整数a、b,表示图中有(a,b)边。
注意图中可能含有重边,但不会有自环。
输出格式
输出该图的关联矩阵,注意请勿改变边和结点的顺序。
样例输入
5 9
1 2
3 1
1 5
2 5
2 3
2 3
3 2
4 3
5 4
样例输出
1 -1 1 0 0 0 0 0 0
-1 0 0 1 1 1 -1 0 0
0 1 0 0 -1 -1 1 -1 0
0 0 0 0 0 0 0 1 -1
0 0 -1 -1 0 0 0 0 1
关联矩阵定义
这题有点恶心人,我找了好久bug,发现原来答案在答案里,只有wa才能ac,不破必不立,没什么好讲的,数组按定义来,%2d输出。
代码
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
const int MOD=0x3f3f3f3f;
const int N=1234;
int ans[N][N];
int main(){
int n,m;
//freopen("C:\\Users\\39663\\Desktop\\input1.txt","r",stdin);
//freopen("C:\\Users\\39663\\Desktop\\o.txt","w",stdout);
cin>>n>>m;
for(int i=1;i<=m;i++){
int a,b;cin>>a>>b;
ans[a][i]=1;
ans[b][i]=-1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
printf("%2d ",ans[i][j]);
}
if(i!=n)cout<<'\n';
}
return 0;
}
算法训练 传球游戏
资源限制
时间限制:1.0s 内存限制:256.0MB
【问题描述】
上体育课的时候,小蛮的老师经常带着同学们一起做游戏。这次,老师带着同学们一起做传球游戏。
游戏规则是这样的:n个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球,每个同学可以把球传给自己左右的两个同学中的一个(左右任意),当老师再次吹哨子时,传球停止,此时,拿着球没传出去的那个同学就是败者,要给大家表演一个节目。
聪明的小蛮提出一个有趣的问题:有多少种不同的传球方法可以使得从小蛮手里开始传的球,传了m次以后,又回到小蛮手里。两种传球的方法被视作不同的方法,当且仅当这两种方法中,接到球的同学按接球顺序组成的序列是不同的。比如有3个同学1号、2号、3号,并假设小蛮为1号,球传了3次回到小蛮手里的方式有1->2->3->1和1->3->2->1,共2种。
输入格式
共一行,有两个用空格隔开的整数n,m(3<=n<=30,1<=m<=30)。
输出格式
t共一行,有一个整数,表示符合题意的方法数。
样例输入
3 3
样例输出
2
数据规模和约定
40%的数据满足:3<=n<=30,1<=m<=20
100%的数据满足:3<=n<=30,1<=m<=30
又是一道动态规划,因为同学们围成一个圈,所以小蛮无论站在哪,最终结果都是一样的,在此题解中,设小蛮站在 1 1 1号位置。
设二维数组 d p [ i ] [ j ] dp[i][j] dp[i][j]表示传了 i i i次球到 j j j号同学手里的方式数量,由题意可知,某一次传到同学手里的方式数量,实际上等于上一次传到该同学左右两同学的数量之和,如:在第二次传球中,只能由 2 2 2号位和 n n n号位传球至 1 1 1号位,所以 1 1 1号位在第二次传球中获得球的方式数量,就等于 2 2 2号位和 n n n号位在第一次传球中获得球的方式数量之和。状态转移方程为: d p [ i ] [ j ] = d p [ i − 1 ] [ m o d ( j + 1 ) ] + d p [ i − 1 ] [ m o d ( j − 1 ) ] dp[i][j]=dp[i-1][mod(j+1)]+dp[i-1][mod(j-1)] dp[i][j]=dp[i−1][mod(j+1)]+dp[i−1][mod(j−1)],用 m o d mod mod函数特殊处理 1 1 1号位前一位和 n n n号位后一位,初始球在 1 1 1号位,故设 d p [ 0 ] [ 1 ] = 1 dp[0][1]=1 dp[0][1]=1,结果为 d p [ m ] [ 1 ] dp[m][1] dp[m][1]表示传了 m m m次球到 1 1 1号同学手里的方式数量。
代码
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
const int N=34;
int dp[N][N];
int n,m;
int mod(int x){
return x%n?x%n:n;
}
int main(){
cin>>n>>m;
dp[0][1]=1;
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
dp[i][j]=dp[i-1][mod(j+1)]+dp[i-1][mod(j-1)];
}
}
cout<<dp[m][1]<<endl;
}
算法训练 数组查找及替换
资源限制
时间限制:1.0s 内存限制:512.0MB
问题描述
给定某整数数组和某一整数b。要求删除数组中可以被b整除的所有元素,同时将该数组各元素按从小到大排序。如果数组元素数值在A到Z的ASCII之间,替换为对应字母。元素个数不超过100,b在1至100之间。
输入格式
第一行为数组元素个数和整数b
第二行为数组各个元素
输出格式
按照要求输出
样例输入
7 2
77 11 66 22 44 33 55
样例输出
11 33 55 M
按题意模拟即可,如果数字位于’A’~'Z’的ASCII码之间,注意强转为字符类型输出。
代码
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
const int MOD=0x3f3f3f3f;
const int N=123456;
int a[N];
int main(){
int n,b;cin>>n>>b;
for(int i=1;i<=n;i++)cin>>a[i];
sort(a+1,a+1+n);
for(int i=1;i<=n;i++){
if(a[i]%b==0)continue;
else if(a[i]>='A'&&a[i]<='Z')cout<<(char)a[i]<<' ';
else cout<<a[i]<<' ';
}
return 0;
}
算法训练 反置数
资源限制
时间限制:1.0s 内存限制:512.0MB
问题描述
一个整数的“反置数”指的是把该整数的每一位数字的顺序颠倒过来所得到的另一个整数。如果一个整数的末尾是以0结尾,那么在它的反置数当中,这些0就被省略掉了。比如说,1245的反置数是5421,而1200的反置数是21。请编写一个程序,输入两个整数,然后计算这两个整数的反置数之和sum,然后再把sum的反置数打印出来。要求:由于在本题中需要多次去计算一个整数的反置数,因此必须把这部分代码抽象为一个函数的形式。
输入格式:输入只有一行,包括两个整数,中间用空格隔开。
输出格式:输出只有一行,即相应的结果。
输入输出样例
样例输入
435 754
样例输出
199
不用高精的代码@20km-shimakaze提供
#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#define ll long long
using namespace std;
int fan(int num)
{
int ans=0;
while(num){
ans*=10;
ans+=num%10;
num/=10;
}
return ans;
}
int main()
{
int a,b;
cin>>a>>b;
cout<<fan(fan(a)+fan(b))<<endl;
return 0;
}
没给数据范围,保险起见,用高精写的,模拟加法进位就行,注意每次反置都要处理前导零。
代码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
const int N=123456;
vector<int>q,w,e;
void add(){
int t=0;
for(int i=0;i<q.size()||i<w.size();i++){
if(i<q.size()){
t+=q[i];
}
if(i<w.size()){
t+=w[i];
}
e.push_back(t%10);
t/=10;
}
if(t){
e.push_back(1);
}
reverse(e.begin(),e.end());
while(e.back()==0&&e.size()!=1)e.pop_back();
}
int main(){
string a,b;
cin>>a>>b;
for(int i=0;i<a.size();i++){
q.push_back(a[i]-'0');
}
for(int j=0;j<b.size();j++){
w.push_back(b[j]-'0');
}
while(q.back()==0&&q.size()!=1)q.pop_back();
while(w.back()==0&&w.size()!=1)w.pop_back();
add();
for(int i=e.size()-1;i>=0;i--){
cout<<e[i];
}
return 0;
}
算法训练 新生舞会
资源限制
时间限制:1.0s 内存限制:512.0MB
问题描述
新生舞会开始了。n名新生每人有三个属性:姓名、学号、性别。其中,姓名用长度不超过20的仅由大小写字母构成的字符串表示,学号用长度不超过10的仅由数字构成的字符串表示,性别用一个大写字符‘F’或‘M’表示。任意两人的姓名、学号均互不相同。换言之,每个人可被其姓名或学号唯一确定。给出m对两人的信息(姓名或学号),判断他们是否能共舞。两人能共舞的充要条件为两人性别相异。
输入
第一行一个整数n(2<=n<=1000),表示学生人数。接下来的n行每行依次包含一名新生的姓名、学号、性别,分别用一个空格隔开。
之后的一行是一个整数m(1<=m<=1000),表示询问的数目。接着的m行每行包含两个信息(姓名或学号),保证两个信息不属于同一人,中间用一个空格隔开。
输出
对于每个询问输出一行,如果两人可以共舞,输出一个大写字母‘Y’,否则输出一个大写字母‘N’。
样例输入
4
John 10 M
Jack 11 M
Kate 20 F
Jim 21 M
3
John 11
20 Jack
Jim Jack
样例输出
N
Y
N
提示
可以把名字和学号都当成字符串处理。可以按以下流程实现。
#include<iostream>
#include<cstring>
using namespace std;
struct tstudent
{
char name[21];
char num[21];
char sex;
};
void readdata(tstudent student[], int n)
{
输入N个学生的信息
}
int findstudent(tstudent student[], int n, char* data)
{
if (data == NULL) return -1;
判断是否有某个学生的学号或名字等于data,如果有,函数返回该学生在student数组中的序号,否则返回-1
}
void solve(tstudent student[], int n, int m)
{
char x[21], y[21];
for (int i=0; i<m; i++) {
输入两个人的信息X、Y。通过调用findstudent函数判断这两个人能否成为舞伴
}
}
int main()
{
int n, m;
tstudent student[1010];
cin>>n;
readdata(student, n);
cin>>m;
solve(student, n, m);
}
因为学号和姓名都可以唯一确认,所以只要将这两个属性分别和性别链接就可,暴力配对,没什么说的,看代码。
代码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
const int N=1234;
string name[N],id[N],fm[N];
int n,m;
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>name[i]>>id[i]>>fm[i];
cin>>m;
while(m--){
string a,b;cin>>a>>b;
string f;
string ans="N";
for(int i=1;i<=n;i++){
if(name[i]==a||id[i]==a){
f=fm[i];break;
}
}
for(int i=1;i<=n;i++){
if(name[i]==b||id[i]==b){
if(fm[i]==f)ans="N";
else ans="Y";
break;
}
}
cout<<ans<<endl;
}
return 0;
}