1.特殊的正方形
输入n,输出n行n列的由+
和.
组成的正方形,其中最外面一圈全是+
,第二圈全是.
,…,对于第i圈,如果i是奇数,那么全是+
,否则全是.
。
输入格式
一行,一个整数n。
输出格式
n行,为满足题目要求的正方形。注意不要有行末空格。
样例输入
10
样例输出
++++++++++
+........+
+.++++++.+
+.+....+.+
+.+.++.+.+
+.+.++.+.+
+.+....+.+
+.++++++.+
+........+
++++++++++
数据范围
对于100%100%的数据,保证2≤n≤100
要求输出正方形,那么我们只需要知道一个正方形的四个顶点便可以确定这个正方形,并输出对应的符号了。
所以对第一个正方形,他的顶点也就是(1,1),(1,n),(n,1),(n,n),然后,我们根据这四个顶点输出对应的符号。之后的每个正方形的顶点就向里缩一格就可以了。
完整代码如下:
#include<iostream>
using namespace std;
char arr[105][105]; //用二维数组存放图案
int main(){
int n,x1,y1,x2,y2,x3,y3,x4,y4,num=0;
char t;
cin>>n;
x1=1,y1=1,x2=1,y2=n,x3=n,y3=1,x4=n,y4=n; //第一个正方形的四个顶点
while(1){
if(num%2){
t='.';
}
else{
t='+'; //判断符号
}
if(num>n/2){ //对于a,最多有a/2圈
break;
}
for(int i=x1;i<=x3;i++){
arr[y1][i]=t;
}
for(int i=y3;i<=y4;i++){
arr[i][x3]=t;
}
for(int i=x2;i<=x4;i++){
arr[y2][i]=t;
}
for(int i=y1;i<=y2;i++){
arr[i][x1]=t;
} //输出符号
x1++,y1++,x2++,y2--,x3--,y3++,x4--,y4--,num++; //记得要更改四个顶点的位置
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cout<<arr[i][j]; //输出最后结果
}
cout<<endl;
}
return 0;
}
2.走楼梯2
楼梯有 n 阶,上楼可以一步上一阶,也可以一步上二阶。
但你不能连续三步都走两阶,计算走到第n阶共有多少种不同的走法。
输入格式
一行,一个数字,表示n。
输出格式
输出走楼梯的方式总数。
样例输入
6
样例输出
12
数据规模
对于100%的数据,保证n≤50。
一道动态规划的题目,但多了一个限制,就是不能连续三步都走两层台阶。
那么我们可以多加一个条件。
我们可以设 f[i] [j] 为到达第 i 层台阶,且最后一步为连续两级台阶的次数为 j 的次数,也就是当 j =0 时就是最后一步走一级台阶,当 j =1时就是走连续两级台阶到第 i 层,j=2 就是最后连续走2次连续两级台阶到的第 i 层,j<3.
那么我们就可以很容易的想出状态转移方程:
$$
f(i,0)=f(i-1,0)+f(i-1,1)+f(i-1,2) \
f(i,1)=f(i-2,0) ,i-2>0 \
f(i,2)=f(i-2,1) ,i-2>0 \
$$
完整代码如下:
#include<bits/stdc++.h>
using namespace std;
long long f[55][3]; //f[i][j]表示走第i层台阶,最后有连续两级台阶的次数为j的走法数量
int main(){
int n;
long long ans=0;
cin>>n;
f[0][0]=1;
for(int i=1;i<=n;i++){
f[i][0]=f[i-1][0]+f[i-1][1]+f[i-1][2];
if(i-2>=0){
f[i][1]=f[i-2][0];
f[i][2]=f[i-2][1]; //进行转移
}
}
for(int j=0;j<=2;j++){
ans+=f[n][j]; //计算结果
}
cout<<ans;
return 0;
}
3.走路
有一条很长的数轴,一开始你在0的位置。接下来你要走n步,第i步你可以往右走a_i或者b_i。
问n步之后,0到m的每个位置,能不能走到?
输入格式
第一行,两个整数n,m。
接下来n行,每行两个整数a_i,b_i。
输出格式
一行,一共m+1个数,每个数都是0
或1
表示能否走到,数字之间不用空格隔开。
输入样例
3 10
1 2
2 6
3 3
输出样例
00000011001
数据规模
对于所有数据,保证1≤n≤100,1≤m≤10^5,1≤a_i,b_i≤1000
同样是一道动态规矩的题目,我们可以假设 f[i] [j] 表示长度为 i 的这一点,走 j 步能不能到,如果 f[i] [j]=1 表示可以到 ,等于0就代表不能到。
接下来就是它的状态转移方程:
我们可以知道,如果 f[i] [j]=1,那么显然,f[i+a_i[i]] [j]=1,f[i+b_i[i]] [j]=1;
也就是:
f
(
i
,
j
)
=
f
(
i
+
a
i
(
i
)
,
j
)
=
f
(
j
+
b
i
(
i
)
,
j
)
f(i,j)=f(i+a_i(i),j)=f(j+b_i(i),j)
f(i,j)=f(i+ai(i),j)=f(j+bi(i),j)
那么一开始的初始条件也就是 f[0] [0]=1了,最后的结果也就是 f[0~i] [n] 了
那么就可以轻松ac了
完整代码如下:
#include<iostream>
using namespace std;
int a[105],b[105],n,m;
bool f[105][100005];
int main()
{
f[0][0]=1;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i]>>b[i]; //记录步数
}
for(int i=0;i<=n;i++){
for(int j=0;j<=m;j++){
if(f[i][j]){
f[i+1][j+a[i+1]]=f[i+1][j+b[i+1]]=f[i][j]; //状态转移
}
}
}
for(int j=0;j<=m;j++){
cout<<f[n][j]; //输出最后结果
}
return 0;
}
4.简单分数统计
N个好朋友在codeforces上参加一场包含 M个题目的比赛, 比赛期间codeforces网站一共有 k次提交。
已知每个题目的分数,
但是由于他们只能查到在比赛期间codeforces总共的提交记录(其他用户提交的其他题目记录也包含在内, 即存在不属于该场比赛的题目),
所以想请你编写一个程序算出他们每个人的分数。
输入格式
第一行三个整数 N, M, K 分别表示好朋友的个数, 题目的个数, 和提交的总次数(其中0<N,M,K<=200)。
接下来 N 行 第 i 行输入为第 i 个人的id,
接下来 M 行 第 j 行输入为第 j 个题目的名称和分数,
接下来 K 行 第 k 行输入为第 k 次提交的提交者id, 题目名称和结果(“WA” 或 “AC”, 如果"AC"代表通过这个题目, 提交者获得对应分数)。
注: 题目名称和id均为仅包含英文字母和数字的字符串, 题目分数为小于等于 1e6 的正整数. 每一行的多个输入之间用空格隔开。
所有输入的字符串长度 lengthℎ 满足 0<length≤500
所有用户id和题目名称不存在重名, 用户AC了某个题之后之后不会再重复提交该题, 好朋友们只会提交属于比赛的题目。
输出格式
输出 N 行, 第 i 行输出第 i 个人的名字和对应分数 (名字和分数用空格隔开)。
样例输入
2 2 4
GabrielPessoa
beza
metebronca 100
geometry 200
beza metebronca AC
ffern numbertheory AC
GabrielPessoa geometry WA
beza geometry AC
样例输出
GabrielPessoa 0
beza 300
样例解释
beza 过了 metebronca和geometry 拿到 300300 分。
GabrielPessos 没有过题, 所以是 00 分。
还有一些其他选手提交的其他题目忽略不计。
一道简单的模拟题目,我们只需要将每位我们需要查找的朋友名字,以及题目及其分数记录下来,然后在输入提交记录时先对题目是否正确进行判断。
如果是"WA",那么就无需再查找,这个记录就是无效的记录。如果是”AC“就继续查找对应的人名,如果不是我们需要找的人,那么也就无需再对题目进行查找。
然后重复这项操作即可。
这道题的数据规模才200,所以时间是完全足够的。
完整代码如下:
#include<iostream>
using namespace std;
struct tip{
string name;
int score; //记录题目名称以及其分数
}tips[205];
struct person{
string p_name;
long long sum; //记录人名以及其得的分数
}persons[205];
int main(){
int n,m,k,t;
string nowperson,nowtip,nowr_w;
cin>>n>>m>>k;
for(int i=1;i<=n;i++){
cin>>persons[i].p_name;
}
for(int i=1;i<=m;i++){
cin>>tips[i].name>>tips[i].score;
}
for(int i=1;i<=k;i++){
t=0;
cin>>nowperson>>nowtip>>nowr_w;
if(nowr_w=="WA"){
continue; //如果是”WA“就不用看了
}
for(int a=1;a<=n;a++){
if(persons[a].p_name==nowperson){ //查找人名
t=a;
break;
}
}
if(t){
for(int i=1;i<=m;i++){
if(tips[i].name==nowtip){
persons[t].sum+=tips[i].score; //查找题目并给这个人加上这代题目的分数
}
}
}
}
for(int i=1;i<=n;i++){
cout<<persons[i].p_name<<" "<<persons[i].sum<<endl; //输出结果
}
return 0;
}
5.Alice的德州扑克
德州扑克是目前世界上最流行的扑克游戏,全世界有众多相关的比赛,例如是 WSOP,WPT,EPT等,也让这款游戏的玩法变得层出不穷,丰富多变。 不要被简单的游戏规则而误导,复杂多变的比赛状况,让这款游戏在高水平的竞技中会变得非常复杂,这也让人们为德州扑克给出了这样一句评价 ”用一刻就能学会,但要用一生才能掌握” 。
现在我们并不在乎游戏规则是什么,因为 Alice 是一个德州扑克高手,他对于德州扑克的规则烂熟于心,不过他每次都记不得牌型的大小关系,他知道你是一个编程高手,所以他想让你帮他写一个程序:输入五张牌的大小和花色,输出这五张牌能组成的最大牌型.你能帮帮他吗?
为了降低你的编程难度,我们规定:
- 输入的牌都是来源于同一副扑克牌
- 输入的牌的点数都是非递减的
- 所有花色没有大小之分
下面给出各牌型,(从大到小)
- 皇家同花顺(ROYAL FLUSH):五张顺连的牌(点数连续单调递增),且最大的一张牌是A(Ace),并且五张牌的花色相同
- 同花顺(STRAIGHT FLUSH):五张顺连的牌(点数连续单调递增),不规定最大的一张牌是A(Ace),并且五张牌的花色相同
- 四条(FOUR OF A KIND):至少四张牌的点数相同
- 葫芦(FULL HOUSE):至少三张牌的点数相同,并且除此之外还有两张牌的点数相同
- 同花(FLUSH):五张牌的花色都相同
- 顺子(STRAIGHT):五张顺连的牌(点数连续单调递增),不要求五张牌的花色相同
- 特别注意:由于 Alice 是个谨慎的人,所以比 三条(THREE OF A KIND) (包括三条) 小的牌型 Alice 不在乎他们的大小关系,你只需要告诉 Alice 弃牌就行
输入格式
输入两行,每行五个数字,第一行的第 i个字符表示第 i 张扑克的点数,
第二行的第 i 个数字表示第 i 张扑克花色。(保证输入的牌的点数是非递减的,且所有输入均合法)。
点数和对应输入的数字:
- 2−102−10 对应 2 - 10
- J(Jack) 对应 11
- Q(Queen) 对应 12
- K(King)对应 13
- A(Ace) 对应 14
花色和对应输入的数字:
- 黑桃 (Spades) 对应 1
- 方片 (Diamonds) 对应 2
- 红桃 (Hearts) 对应 3
- 梅花 (Clubs) 对应 4
输出格式
输出这五张牌能组成的最大牌型。
- 如果最大是皇家同花顺输出 “ROYAL FLUSH”
- 如果最大是同花顺输出 “STRAIGHT FLUSH”
- 如果最大是四条输出 “FOUR OF A KIND”
- 如果最大是葫芦输出 “FULL HOUSE”
- 如果最大是同花输出 “FLUSH”
- 如果最大是顺子输出 “STRAIGHT”
- 如果最大的牌型小于等于三条输出"FOLD",劝 Alice 弃牌
- 输出不包括引号
样例输入1
10 11 12 13 14
1 1 1 1 1
样例输出1
ROYAL FLUSH
样例输入2
10 11 12 13 14
1 2 1 3 4
样例输出2
STRAIGHT
样例输入3
6 6 6 7 7
1 2 3 1 3
样例输出3
FULL HOUSE
样例输入4
3 3 6 6 9
1 2 1 2 1
样例输出4
FOLD
一道简单的模拟题,我们只需要对输入的5张牌做个判断,判断它是什么牌型的牌,然后输出即可。
因为这里的输入的5张牌是非递减的,所以就比较好判断相等以及顺子。
如果输入的牌型没有在上述描述中,那么就是三条及以下,就直接输出FOLD就可以了
完整代码如下:
#include<iostream>
using namespace std;
int num[10],color[10];
void judge(){
if(num[1]==10&&num[2]==11&&num[3]==12&&num[4]==13&&num[5]==14){
if(color[1]==color[2]==color[3]==color[4]==color[5]){
cout<<"ROYAL FLUSH"; //皇家同花顺
return;
}
}
if((num[2]==(num[1]+1))&&(num[3]==(num[2]+1))&&(num[4]==(num[3]+1))&&(num[5]==(num[4]+1))){
if((color[1]==color[2])&&(color[2]==color[3])&&(color[3]==color[4])&&(color[4]==color[5])){
cout<<"STRAIGHT FLUSH"; //同花顺
return;
}
else{
cout<<"STRAIGHT"; //顺子
return;
}
}
if((num[1]==num[2])&&(num[2]==num[3])&&(num[3]==num[4])){
cout<<"FOUR OF A KIND"; //4条
return;
}
else if((num[2]==num[3])&&(num[3]==num[4])&&(num[4]==num[5])){
cout<<"FOUR OF A KIND"; //4条
return;
}
if((num[1]==num[2])&&(num[2]==num[3])&&(num[4]==num[5])){
cout<<"FULL HOUSE"; //葫芦
return;
}
else if((num[1]==num[2])&&(num[3]==num[4])&&(num[4]==num[5])){
cout<<"FULL HOUSE"; //葫芦
return;
}
if((color[1]==color[2])&&(color[2]==color[3])&&(color[3]==color[4])&&(color[4]==color[5])){
cout<<"FLUSH"; //同花
return ;
}
else{
cout<<"FOLD"; //弃牌
return ;
}
}
int main(){
for(int i=1;i<=5;i++){
cin>>num[i];
}
for(int i=1;i<=5;i++){
cin>>color[i];
}
judge();
return 0;
}
6.订单编号
小缘开了一家公司,生意很好,每天都会收到很多订单,自动交易系统会自动给这些订单生成没有重复的订单编号。但是有一天,系统出现了未知的错误,导致当天的订单编号可能有重复的,这可把小缘急坏了。你可以帮助小缘按照规则给这些订单重新编号吗?
按照时间先后顺序给出 N个正整数作为原订单编号,你需要按照规则依次赋予这些订单新的编号,对于任意一个订单,要找到大于等于其原订单编号且未被使用过的(没有被之前的订单作为新的订单编号)的最小整数,作为它的新订单编号。
例如: 原订单编号依次为1 2 3 1,则新订单编号应该为1 2 3 4 (前3个订单的原订单编号都没有使用过,所以用其原订单编号即可,对于第四个订单,原订单编号为1,而1, 2, 3都已经被使用过,所以新订单编号为4)。
输入格式
第一行输入一个整数 N (1≤N≤5×10^5)
第二行输入 N个数 a_i (1≤a_i≤10^9)作为原订单编号。
输出格式
输出一行,包含 N 个整数为新的订单编号。
样例输入1
6
2 3 4 1 1 1
样例输出1
2 3 4 1 5 6
样例输入2
3
1000000000 1000000000 1000000000
样例输出2
1000000000 1000000001 1000000002
样例输入3
6
4 5 1 2 1 1
样例输出3
4 5 1 2 3 6
这道题我最开始的思路就是先判断当前的订单序号是否被用过了,如果没被用过就标记它,并继续使用;如果用过就++,判断++后的数有没有被用过,知道找到没有被用过的数字。
但最终只得了48分,其他的点,无一例外的都TLE了 (qwq)
那么,显然是这里5*10^5的数据规模太大了
那么只能另辟蹊径了。
想了许久也没想出来。结果,我只能去网上搜寻结果了。
我们可以先想象有一个12e9的区间,对应每次的订单编号,我们查找它的最接近它的,最右边的区间。(一开始也就是在12e9的区间里面)如果该数在一个区间里面,那么就直接输出它,(因为代表该数没有被用过)
并把这个区间以这个数为界,分为两个区间。如果找到一个这个数没在的区间,那么就输出这个区间的左端点(因为显然这个左端点就是离他最接近的且没有被用过的点),并把这个区间的左端点抠掉(这个点用过了),
形成新的区间。一直重复这个操作即可。
虽然我知道总的思路了,但奈何以我的蒟蒻的代码能力实在写不出这么高级的代码。总之请看下面的完整代码吧:
#include<bits/stdc++.h>
using namespace std;
int n;
set<pair<int,int> >c;
void inse(int l,int r){
if(l>r) return;
c.insert(make_pair(r,l)); //插入数
}
int main(){
int x;
scanf("%d",&n);
c.insert(make_pair(2e9,1)); //原先的区间
for(int i=1;i<=n;i++){
scanf("%d",&x);
auto it=c.lower_bound(make_pair(x,0)); //二分查找到大于等于该点的区间
if(it->second<=x){
printf("%d ",x); //如果这点在区间里,就输出这一点
inse(it->second,x-1);
inse(x+1,it->first); //一这一点为界,形成新的两个区间
c.erase(it); //记得挖除这一点
}
else{
printf("%d ",it->second); //如果这点不在区间里面,就输出左端点
inse(it->second+1,it->first);
c.erase(it); //记得扣掉该点
}
}
return 0;
}
怎么难度一下子就提高了这么多?(qwq)
7.饿饿 饭饭
有n个同学正在排队打饭,第i个同学排在从前往后第i个位置。但是这天食堂内只有一个食堂阿姨,为了使同学们都能尽快的吃上饭,每一个同学在打完一份饭之后就会排在队伍的末尾先吃着打到的饭,我们知道第i个同学的饭量为a_i,也就是说第i个同学要吃a_i份饭才能吃饱,当一位同学吃饱后,他就会立刻离开食堂,不会排在队伍的末尾。食堂阿姨想知道,在打完k份饭之后,队伍的样子是怎样的,但是食堂阿姨数学不太好,想让你帮忙想想办法。
输入格式
第一行给出两个整数n,k。
第二行给出n个整数a_1,a_2,…a_n
输出格式
如果食堂阿姨打饭数少于k,请输出"-1"。
否则按照队伍顺序输出每一个同学的编号。
样例输入1
3 3
1 2 1
样例输出1
2
样例输入2
4 10
3 3 2 1
样例输出2
-1
样例输入3
7 10
1 3 3 1 2 3 1
样例输出3
6 2 3
数据规模
数据保证1≤n≤10^5, 0≤k≤10^14, 1≤a_i≤10^9
这道题目,我们不可能一步一步的模拟打饭过程,我们要想倒数第二轮打饭后,再模拟最后一轮打饭。
那么这样,我们必须先求出这个打饭的轮数。我一开始是直接用 k/n 来求得,但是后来发现,有些人可能还没到 k/n 轮就以及先走了,那么我们就需要一个新的方法来求。
也就是二分答案的方法。我们可以枚举每个答案,对于每个答案,我们的judge函数也就是求出这个轮数,所有人共打了多少份饭。如果打的饭数小于k, 那么我们就让 l=m ,防止就让 r=m,这样就能求出轮数了。之后我们只 需要对最后一轮进行模拟即可。
完整代码如下:
#include<bits/stdc++.h>
using namespace std;
long long n,k,a[100005],c[100005];
long long cls(long long t){ //求第t轮,共打了多少份饭
long long res=0;
for(int i=1;i<=n;i++){
res+=min(a[i],t); //记得要取最小值,因为每个人在吃饱后就会离开。
}
return res;
}
int main(){
long long sum=0;
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
sum+=a[i]; //记录总饭数
}
if(sum<k){ //如果打的饭数小于k,那么直接输出-1
cout<<"-1";
return 0;
}
long long l=-1,r=1e9+1,mid;
while(l!=r-1){
mid=(l+r)/2;
if(cls(mid)<=k){ //进行二分答案
l=mid;
}
else{
r=mid;
}
}
long long leave=k-cls(l),tot=0; //leave为剩下多少份饭没打
for(int i=1;i<=n;i++){
if(a[i]>l){
c[++tot]=i; //记录当前还有多少人在队列里,用c[i]记录人的序号
}
}
for(int i=leave+1;i<=tot;i++){
cout<<c[i]<<" "; //对于后面的人,先输出他们的序号
}
for(int i=1;i<=leave;i++){
if(a[c[i]]!=l+1){
cout<<c[i]<<" "; //对于前面的人,要先判断最后打一份饭后,他们是否还在队伍里面,然后再输出他们的序号
}
} //因为打完饭后,前面的人会走到后面,所以要先输出后面的人的序号
return 0;
}
8.任务分配
你有n个任务,其中第i个任务,在s_i开始,e_i时刻结束,如果做这个任务,你能获得w_i的收益。
但是你在一个时刻只能做一个任务,问选择哪些任务,能让你的收益尽量大。
注意:你在上一个任务结束后马上开始下一个任务是可以的。
输入格式
第一行一个整数n。
接下来n行,每行三个整数s_i,e_i,w_i。
输出格式
一个数,表示答案。
样例输入
3
1 3 100
2 4 199
3 5 100
样例输出
200
数据规模
对于所有数据,保证1≤n≤103,1≤s_i<e_i≤103,1≤w_i≤10^5
又是一道动态规划的问题,但这道题的每个物品都对应一个区间。
我们可以设 f[i] 为到时间 i 时,能获得的最大价值
那么状态转移方程我们也可以很容易的写出:
f
(
i
+
1
)
=
f
(
i
)
,
没有一项任务的开始时间在
i
f
(
e
i
)
=
m
a
x
(
f
(
e
i
)
,
f
(
i
)
+
w
i
)
,
有任务开始时间在
i
时
f(i+1)=f(i),没有一项任务的开始时间在i\\ f(e_i)=max(f(e_i),f(i)+w_i) ,有任务开始时间在i时\\
f(i+1)=f(i),没有一项任务的开始时间在if(ei)=max(f(ei),f(i)+wi),有任务开始时间在i时
开始状态也就是f[1]=0,那么我们就可以轻松ac了
完整代码如下:
#include<bits/stdc++.h>
using namespace std;
int f[1005],n;
struct thing{
int l;
int r;
int w;
}things[1005]; //记录任务
int main(){
int flag=0,max1=0,max2=0;
cin>>n;
for(int i=1;i<=n;i++){
cin>>things[i].l>>things[i].r>>things[i].w;
max1=max(max1,things[i].r); //找到任务最大的结束时间
}
for(int i=1;i<=max1;i++){
for(int j=1;j<=n;j++){
if(things[j].l==i){
f[things[j].r]=max(f[i]+things[j].w,f[things[j].r]); //状态转移
flag=1;
}
}
f[i+1]=max(f[i],f[i+1]); //状态转移
}
for(int i=1;i<=max1;i++){
max2=max(max2,f[i]); //找到最大值
}
cout<<max2;
return 0;
}
9.路径计数
有一个n×n的网格,有些格子是可以通行的,有些格子是障碍。
一开始你在左上角的位置,你可以每一步往下或者往右走,问有多少种走到右下角的方案。
由于答案很大,输出对10^9+7取模的结果。
输入格式
第一行一个正整数n。
接下来n行,每行n个正整数,1表示可以通行,0表示不能通行。
输出格式
一个整数,表示答案。
样例输入
3
1 1 1
1 0 1
1 1 1
样例输出
2
数据规模
对于100%的数据,保证2≤n≤100,左上角右下角都是可以通行的。
一开始,我以为这是一道搜素回溯题。但是看到了这里最大的数据规模,100,我就知道这道题不能有dfs做了。那么就只能改变思路。
我们可以设 f[i] [j] 表示到达点(i,j)的方法数,那么因为只能向下和向右走,所以状态转移方程也很容易了:
f
(
i
,
j
)
=
{
f
(
i
−
1
,
j
)
+
f
(
i
,
j
−
1
)
i
−
1
>
0
,
j
−
1
>
0
f
(
i
−
1
,
j
)
j
−
1
≤
0
f
(
i
,
j
−
1
)
i
−
1
≤
0
f(i,j)= \begin{cases} f(i-1,j)+f(i,j-1) & i-1>0,j-1>0\\ f(i-1,j) & j-1 \leq 0 \\ f(i,j-1) & i-1 \leq 0 \end{cases}
f(i,j)=⎩
⎨
⎧f(i−1,j)+f(i,j−1)f(i−1,j)f(i,j−1)i−1>0,j−1>0j−1≤0i−1≤0
完整注释代码如下:
#include<bits/stdc++.h>
using namespace std;
#define mod 1000000007 //求余
long long n,numarr[105][105],f[105][105];
int main(){
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>numarr[i][j];
}
}
f[1][1]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(numarr[i][j]){
if(i-1>0&&j-1>0){
f[i][j]=(f[i][j-1]%mod)+(f[i-1][j]%mod); //要求余储存
}
else if(i-1==0&&j-1>0){
f[i][j]=(f[i][j-1]%mod); //求余
}
else if(j-1==0&&i-1>0){
f[i][j]=(f[i-1][j]%mod); //状态转移,求余
}
}
}
}
cout<<f[n][n]%mod; //记得求余
return 0;
}
10.最大和上升子序列
给定一个长度为 n 的数组 a_1,a_2,…,a_n,问其中的和最大的上升子序列。也就是说,我们要找到数组 p1,p2,…,pm1,2,…,,满足 1≤p_1<p_2<⋯<p_m≤n并且 a_p_1<a_p_2<⋯<a_p_m,使得a_p_1+a_p_2+⋯+a_p_m最大。
输入格式
第一行一个数字 n。
接下来一行 n 个整数 a_1,a__2,…,a_n。
输出格式
一个数,表示答案。
样例输入
6
3 7 4 2 6 8
样例输出
21
数据规模
所有数据保证 1≤n≤1000,1≤ai≤10^5
又是一道动态规划的题目(怎么这么多动态规划题目啊?小声bb),我们可以先设 f[i] 为以第 i 个数为结尾的最大和上升子序列的最大和。
那么我们就可以从前向后推出动态转移方程了:
f
(
i
)
=
{
m
a
x
(
f
(
j
)
+
a
i
,
f
(
i
)
)
a
j
<
a
i
a
i
a
j
≥
a
i
f(i)= \begin{cases} max(f(j)+a_i,f(i)) & a_j<a_i\\ a_i & a_j \geq a_i \end{cases}
f(i)={max(f(j)+ai,f(i))aiaj<aiaj≥ai
起始条件也就是 f(1)=a_1 了,这道题1000的数据量,时间复杂度显然是够过的了。
完整代码如下:
#include<bits/stdc++.h>
using namespace std;
long long n,numarr[1005],f[1005];
int main(){
long long ans,max1=0; //要开long long
cin>>n;
for(int i=1;i<=n;i++){
cin>>numarr[i];
}
f[1]=numarr[1]; //初始条件
for(int i=2;i<=n;i++){
for(int a=1;a<i;a++){
if(numarr[a]<numarr[i]){
ans=f[a]+numarr[i];
f[i]=max(f[i],ans); //状态转移
}
}
if(!f[i]){
f[i]=numarr[i]; //状态转移
}
}
for(int i=1;i<=n;i++){
if(f[i]>max1){
max1=f[i]; //找到最大值
}
}
cout<<max1; //输出结果
return 0;
}