路径计数
有一个𝑛×𝑛的网格,有些格子是可以通行的,有些格子是障碍。
一开始你在左上角的位置,你可以每一步往下或者往右走,问有多少种走到右下角的方案。
由于答案很大,输出对109+7取模的结果。
输入格式
第一行一个正整数𝑛。
接下来𝑛行,每行𝑛个正整数,1表示可以通行,0表示不能通行。
输出格式
一个整数,表示答案。
样例输入
3 1 1 1 1 0 1 1 1 1
样例输出
2
数据规模
对于100%的数据,保证2≤𝑛≤100,左上角右下角都是可以通行的。
解题思路:
dp
对于每一个通路格1能走到的方法总数,
等于它上面一个通路格能走到的方法总数+左边一个通路格能走到的方法总数
dp [i] [j] =dp [i-1] [j] +dp [i] [j-1]
完整代码如下:
#include <bits/stdc++.h> using namespace std; const int maxn=1e2+5,mod=1e9+7; int n; int tu[maxn][maxn],dp[maxn][maxn]; int main() { cin>>n; for(int i=1; i<=n; i++){ for(int j=1; j<=n; j++){ cin>>tu[i][j]; } } dp[1][1]=1;//初始化为1 for(int i=1; i<=n; i++){ for(int j=1; j<=n; j++){ if(i==1 && j==1) continue; //跳过起点 if(tu[i][j]==1) dp[i][j]=(dp[i-1][j]+dp[i][j-1]) % mod; } } cout<<dp[n][n]; return 0; }
最大和上升子序列
给定一个长度为 𝑛 的数组 𝑎1,𝑎2,…,𝑎**𝑛,问其中的和最大的上升子序列。也就是说,我们要找到数组 𝑝1,𝑝2,…,𝑝**𝑚,满足 1≤𝑝1<𝑝2<⋯<𝑝**𝑚≤𝑛 并且 𝑎**𝑝1<𝑎**𝑝2<⋯<𝑎𝑝𝑚,使得𝑎**𝑝1+𝑎**𝑝2+⋯+𝑎𝑝𝑚最大。
输入格式
第一行一个数字 𝑛。
接下来一行 𝑛 个整数 𝑎1,𝑎2,…,𝑎**𝑛。
输出格式
一个数,表示答案。
样例输入
6 3 7 4 2 6 8
样例输出
21
数据规模
所有数据保证 1≤𝑛≤1000,1≤𝑎**𝑖≤10^5。
解题思路:
dp
对于每一个数可以选择它之前满足序列的最大序列与其相加
eg. 3 7 4 2 6 8
3 10 7 2 13 21
最后找出其中的最大序列
完整代码如下:
#include <bits/stdc++.h> using namespace std; const int maxn=1e5+5; int a[maxn],dp[maxn]; int n; int main() { cin>>n; for(int i=1; i<=n; i++) cin>>a[i]; for(int i=1; i<=n; i++) dp[i]=a[i]; for(int i=1; i<=n; i++){ for(int j=1; j<=i; j++){ if(a[i]>a[j]) dp[i]=max(dp[i],a[i]+dp[j]); } } int max=0; for(int i=1; i<=n; i++){ if(dp[i]>max) max=dp[i]; } cout<<max; return 0; }
加一
给定一个整数 𝑛。你需要对它做 𝑚 次操作。在一次操作中,你要将这个数的每一位 𝑑 替换成 𝑑+1。比如,1912在进行一次操作后将变成 21023。
请求出整数 𝑛 进行了 𝑚 次操作后的长度。答案可能很大,输出对 10^9+7 取模后的结果。
输入格式
第一行一个整数 𝑡,表示测试单元的个数。
接下来 𝑡 行,每行有两个整数 𝑛 和 𝑚,表示最初的数字和进行多少次操作。
输出格式
对于每个测试单元输出最终数字的长度,答案对 10^9+7 取模。
样例输入
5 1912 1 5 6 999 1 88 2 12 100
样例输出
5 2 6 4 2115
数据规模
所有数据保证 1≤𝑡≤2⋅10^5,1≤𝑛≤10^9,1≤𝑚≤2⋅10^5。
解题思路:
其中的每一个数字都是独立的,所以只要对每一个数字进行m次操作后形成的数字长度进行累加就行
因为数字是独立的,所以可以对所有数字进行预处理,最后累加时直接调用即可,减少时间复杂度
完整代码如下:
#include <bits/stdc++.h> using namespace std; const int maxn=2e5+5,M=2e5+5,mod=1e9+7; int t,n,m; long long a[10],b[10],dp[10][maxn]; int main() { //先模拟进行预处理 for(int i=0; i<=9; i++){ a[i]=1;//将每个数都从数量1开始 for(int j=1; j<=M; j++){ b[0]=a[9]; b[1]=(a[0]+a[9])%mod; for(int k=2; k<=9; k++){ b[k]=a[k-1]; }//一次循环所有数字的数量变化 for(int k=0; k<=9; k++){ dp[i][j]=(dp[i][j]+b[k])%mod; }//用dp存什么数操作几次后有几个 for(int k=0; k<=9; k++){ a[k]=b[k]; }//将b数组中的数拷贝到a数组中 } memset(a,0,sizeof(a));//每一次模拟结束要对a数组清空 } scanf("%d",&t); while(t--){ scanf("%d%d",&n,&m); long long sum=0; while(n>0){ sum=(dp[n%10][m]+sum)%mod; n/=10; }//对每一个数字进行处理 printf("%d\n",sum); } return 0; }
跳跳
平面上给定了一些整点(横纵坐标均为整数的点),被称为 “魔法阵”。魔法少女派派想要在各魔法阵之间传送,每一次传送,她将使用下面的方式:
-
刚开始,派派已经位于某传送阵之上;
-
如果派派掌握一种魔法 (𝐴,𝐵),其中 𝐴,𝐵均为整数。使用一次这个魔法可以让派派从任意整点 (𝑋,𝑌) 瞬间移动至 (𝑋+𝐴,𝑌+𝐵);
-
选择一种魔法并开始传送,在一次传送过程中可以使用多次该魔法,但在抵达下一个传送阵之前仅能使用这一种魔法。
问派派至少需要掌握多少种魔法,才能在从任意魔法阵直接传送到任意魔法阵?
输入格式
第一行一个整数 𝑁。
接下来一行 𝑁 行,每行包含两个整数 𝑋**𝑖,𝑌**𝑖, 表示每个魔法阵的坐标。
输出格式
一个数,表示答案。
样例1输入
3 1 1 4 5 1 4
样例1输出
6
解释: 任务是从 (1,1) 传送至 (4,5) 以及 (1,4) 、从 (4,5) 传送至 (1,1) 以及 (1,4) 、从 (1,4) 传送至 (1,1) 以及 (4,5) 。
注意你不能使用 (0,3)+(3,1) 的魔法从 (1,1) 到达 (4,5)。因为每次移动,你只能使用一种魔法。
当然,你可以学习 (0,1),那样的话,从 (1,1)到达 (1,4) 则需要使用 3次 (0,1) 魔法了。
样例2输入
3 1 1 2 2 1000000000 1000000000
样例2输出
2
数据规模
-
𝑁∈[10,500]
-
𝑋**𝑖,𝑌**𝑖∈[0,109], 但保证坐标之间两两不同。
解题思路:
假设从坐标1传送到坐标2,再从坐标2传送到坐标1,魔法的绝对值数值是相同的,所以只需要考虑正的,最后将次数乘以2即可
从坐标1开始,依次对后面的坐标选取魔法,
每次选取一种魔法,要取魔法的最大公约数,然后把魔法最简化,最后用最简化的魔法去寻找能够达到的坐标
直到坐标全能到达为止
最后将选取的魔法数乘以2就是总魔法数
完整代码如下:
#include <bits/stdc++.h> using namespace std; const int maxn=505; int n,q,w,cnt=0,s; struct ch{ int x; int y; }p[maxn]; int chuan[maxn][maxn]={0};//记录此坐标是否有魔法已经可以传送到 int gcd(int x,int y); int main() { cin>>n; for(int i=1; i<=n; i++) cin>>p[i].x>>p[i].y; for(int i=1; i<=n; i++){ for(int j=i+1; j<=n; j++){ if(chuan[i][j]==0){ cnt+=2; chuan[i][j]=1; q=p[j].x-p[i].x; w=p[j].y-p[i].y; s=gcd(q,w);//寻找最大公约数 q/=s; w/=s; //最简化魔法 for(int k=j+1; k<=n; k++){ if((p[k].x-p[i].x)%q==0 && (p[k].y-p[i].y)%w==0) chuan[i][k]=1; if((p[k].x-p[j].x)%q==0 && (p[k].y-p[j].y)%w==0) chuan[j][k]=1; }//寻找能够达到的魔法阵 } } } cout<<cnt; return 0; } int gcd(int x,int y) { if(x<y) swap(x,y); int m; while(y){ m=x%y; x=y; y=m; } return x; }
异或和或
对于一个长度为 𝑛 的01序列 𝑎1,𝑎2,…,𝑎**𝑛。
你可以执行以下操作任意多次:
-
选择两个下标 1≤𝑖,𝑗≤𝑛(𝑖≠𝑗)。
-
记𝑥=𝑎**𝑖 xor 𝑎**𝑗 , 𝑦=𝑎**𝑖 or 𝑎**𝑗 , 其中 xor 表示按位异或 , or 表示按位或。
-
然后令 𝑎**𝑖=𝑥,𝑎**𝑗=𝑦或 𝑎**𝑖=𝑦,𝑎**𝑗=𝑥。
给定两个01序列 𝑠,𝑡 , 请你判断是否可以通过有限次(可以为0次)操作将序列 𝑠 变为 𝑡。
输入格式
第一行一个整数 𝑡 , 表示数据的组数(1≤𝑡≤10^3)。接下来 𝑡组数据:
每组第一行一个01字符串 𝑠(1≤|𝑠|≤10^3),每组第二行一个01字符串 𝑡(1≤|𝑡|≤10^3)。
注意:|𝑠| 可能不等于 |𝑡|。
输出格式
如果可以通过有限次(可以为0次)操作将序列 𝑠 变为 𝑡, 输出 YES
, 否则输出 NO
。
样例输入
2 001 011 11 101
样例输出
YES NO
样例解释
第一组数据选择 𝑖=2,𝑗=3 , 那么 𝑥=1,𝑦=1 , 接着令 𝑎**𝑖=𝑥,𝑎**𝑗=𝑦 即可得到 𝑡 序列。
第二组数据 |𝑠|=2,|𝑡|=3 显然无法满足要求。
解题思路:
1当两个序列长度相同时:
一共可分三种情况: xor or
0 0 0 0
1 0/1 0 1 1
1 1 0
由此发现,只要序列中有1存在,都能转化为全是1的序列m,也就是s->m,t->m,s->t
所以只要序列中有1,或者s,t都全为0,就输出YES
反之都输出NO
2当两个序列长度不同时,直接输出NO
完整代码如下:
#include <bits/stdc++.h> using namespace std; int main() { int t; cin>>t; string a,b; while(t--){ cin>>a>>b; int sa,sb; sa=a.length(); sb=b.length(); if(sa!=sb){ cout<<"NO"<<endl; continue; //不能写return 0,有多组数据 } int cnt1=0,cnt2=0; for(int i=0; i<sa; i++){ if(a[i]=='1') cnt1++; } for(int i=0; i<sb; i++){ if(b[i]=='1') cnt2++; } if(cnt1>0 && cnt2>0) cout<<"YES"<<endl; else if(cnt1==0 && cnt2==0) cout<<"YES"<<endl; else cout<<"NO"<<endl; } return 0; }
01序列
我们称一个字符串为好字符串,指这个字符串中只包含0
和1
。
现在有一个好字符串,求这个字符串中1
恰好出现𝑘次的子串有多少个。
输入格式
第一行给出一个数字𝑘,表示子串中1
的个数。
第二行给出好字符串。
输出格式
输出一个整数,表示好字符串中有多少个符合条件的子串
数据范围
0≤𝑘≤10^6, |𝑠|≤10^6
样例输入1
1 1010
样例输出1
6
样例输入2
2 01010
样例输出2
4
解题思路:
题目要求我们找的是子串中1的个数为k的情况有多少,我们当然可以枚举字串的长度,然后遍历字串的组成情况,如果满足要求则计数器++,但这样的时间复杂度为O(n^2),在这题1e6的数据下显然是会超时的,所以我们想办法把题目转化一下。
既然是子串,那就说明是连续的,且字符串除了0就是1,那么我们可以用前缀和的思路来写,这样问题就变成了,区间和为k的情况有多少。
我们计算子串的前缀和数组,并把相同前缀和的数量用哈希表记录下来。每一个前缀和为sum的位置,都能和所有前缀和为sum-k的位置形成好子串,所以我们计数器记录的是(前缀和为sum的数量 * 前缀和为sum-k的数量)。最后只要把计数器输出即可。
但有一种情况是特殊的,即k=0的情况,如果k=0,那么我们记录的子串数量就是(前缀和为sum的情况 * 前缀和为sum的情况),这显然是不对的,会出现重复的好子串。所以遇到k=0时我们应该特殊处理,它的子串数为:连续ans个0组成的子串,它能形成的不同好子串为:(ans+1) * ans/2。
完整代码如下:
#include <bits/stdc++.h> using namespace std; const int maxn=1e6+5; int f[maxn],cnt[maxn]; int main() { long long k,t=0,sum=0,m; cin>>k; string s; cin>>s; m=s.length(); if(k==0){ long long ans=0; for(int i=0; i<m; i++){ if(s[i]=='0') ans++; else{ sum+=(ans+1)*ans/2; ans=0; } } if(sum==0) sum+=(ans+1)*ans/2;//防止序列全是0 }else{ for(int i=0; i<m; i++){ if(s[i]=='1') f[i]=f[i-1]+1; else f[i]=f[i-1]; } cnt[0]=1;//初始化为1,防止漏算从开头开始的子串 for(int i=0; i<m; i++){ cnt[f[i]]++; } for(int i=0; i<=m-k; i++){ sum+=cnt[i]*cnt[i+k]; } } cout<<sum; return 0; }
出栈序列判断
现在有一个栈,有 𝑛 个元素,分别为 1,2,…,𝑛。我们可以通过 push
和 pop
操作,将这 𝑛 个元素依次放入栈中,然后从栈中弹出,依次把出栈的元素写下来得到的序列就是出栈序列。
比如 𝑛=3,如果执行 push 1, push 2, pop, push 3, pop, pop
,那么我们 pop
操作得到的元素依次是 2,3,1。也就是说出栈序列就是 2,3,1。
现在给定一个合法的出栈序列,请输出一个合法的由 push
和 pop
操作构成的操作序列。这里要求 push
操作一定是按 1,2,…,𝑛 的顺序。
输入格式
第一行一个整数 𝑛。接下来一行 𝑛 个整数,表示出栈序列。
输出格式
输出 2𝑛 行,每行一个 push
或 pop
操作,可以证明一个出栈序列对应的操作序列是唯一的。
样例输入1
3 2 3 1
样例输出1
push 1 push 2 pop push 3 pop pop
样例输入2
5 1 3 5 4 2
样例输出2
push 1 pop push 2 push 3 pop push 4 push 5 pop pop pop
数据规模
对于 100% 的数据,保证 1≤𝑛≤100000,输入一定是个合法的出栈序列。
解题思路:
建立一个栈,然后分情况判断:1如果栈为空,则加入一个数
2如果栈顶数与出栈序列的待出栈数相同,则出栈
3如果栈顶数与出栈序列的待出栈数不同,则加入一个数
注意:要用scanf和printf,防止TEL(qwq
完整代码如下:
#include <bits/stdc++.h> using namespace std; const int maxn=1e5+5; stack <int> s; int a[maxn]; int main() { int n; scanf("%d",&n); for(int i=1; i<=n; i++) scanf("%d",&a[i]); int t=1,m=1; for(int i=1; i<=2*n; i++){ if(s.empty()){ s.push(m); printf("push %d\n",m); m++; continue; } if(s.top()==a[t]){ printf("pop\n"); t++; s.pop(); }else{ s.push(m); printf("push %d\n",m); m++; } } return 0; }
序列维护
你有一个序列,现在你要支持几种操作:
-
insert x y
,在从前往后的第𝑥个元素后面插入𝑦这个数。如果𝑥=0,那么就在开头插入。 -
delete x
,删除从前往后的第𝑥个元素。 -
query k
,询问从前往后数第𝑘个元素是多少。
输入格式
第一行一个整数𝑚,表示操作个数。
接下来𝑚行,每行一个上面所述的操作。
输出格式
输出若干行,对于每个查询操作,输出答案。
样例输入
10 insert 0 1 insert 1 2 query 1 query 2 insert 0 3 query 1 delete 1 query 1 insert 1 4 query 2
样例输出
1 2 3 1 4
数据规模
对于100%的数据,保证𝑚≤10^3。
对于insert操作,保证1≤𝑦≤10^9。
对于所有操作,保证位置不会超出当前序列的长度。
解题思路:
用数组存数字即可,插入时,插入位置后的数字都向后移一位,再将数字插入这个位置
删除时,将删除位置后的数字都向前移一位,覆盖掉删除位置的数字
完整代码如下:
#include <bits/stdc++.h> using namespace std; const int maxn=1e5+5; int p[maxn],q[maxn]; int main() { int m; cin>>m; string a; int b,c,s=0; while(m--){ cin>>a; if(a[0]=='i'){ cin>>b>>c; if(b==0){ for(int i=s; i>=1; i--) p[i+1]=p[i]; p[1]=c; s++; }else{ for(int i=s; i>=b+1; i--) p[i+1]=p[i]; p[b+1]=c; s++; } }else if(a[0]=='q'){ cin>>b; cout<<p[b]<<endl; }else if(a[0]=='d'){ cin>>b; for(int i=b; i<=s; i++) p[i]=p[i+1]; s--; } } return 0; }
网格判断
您将获得一个 𝑛×𝑛的网格,网格中每个正方形的颜色为黑色或白色。如果满足以下所有条件,则网格是正确的:
-
每行的黑色方块数与白色方块数相同。
-
每列的黑色正方形数与白色方块数相同。
-
没有行或列具有 3个及以上相同颜色的连续正方形。
给定网格,确定它是否正确。
输入格式
第一行一个数字 𝑛(2≤𝑛≤24), 并且数字 𝑛 是偶数。
接下来 𝑛 行,每行包含一个长度为𝑛的由字符B
和W
组成的字符串,代表网格正方形的颜色。
输出格式
如果网格正确,请打印数字 1 在一行上。否则,请打印数字 0 在一行上。
样例输入
4 WBBW WBWB BWWB BWBW
样例输出
1
解题思路:
直接按照题意写即可,判断条件时仔细点
完整代码如下:
#include <bits/stdc++.h> using namespace std; char net[25][25]; int main() { int n; cin>>n; for(int i=1; i<=n; i++){ for(int j=1; j<=n; j++){ cin>>net[i][j]; } } int cnt1=0,ans1=0,cnt2=0,ans2=0;//cnt1,cnt2分别记录行,列中白色格子的数目,ans1,ans2分别记录行,列中连续格子的数目 bool flag1=true,flag2=true; for(int i=1; i<=n; i++){ for(int j=1; j<=n; j++){ if(net[i][j]=='W'){ if(ans1<0) ans1=0; cnt1++; ans1++; }else{ if(ans1>0) ans1=0; ans1--; } if(ans1==3 || ans1==-3){ flag1=false; break; } } if(!flag1) break; if(cnt1!=(n-cnt1)){ flag1=false; break; } ans1=0; cnt1=0; }//判断行 for(int i=1; i<=n; i++){ for(int j=1; j<=n; j++){ if(net[j][i]=='W'){ if(ans2<0) ans2=0; cnt2++; ans2++; }else{ if(ans2>0) ans2=0; ans2--; } if(ans2==3 || ans2==-3){ flag2=false; break; } } if(!flag2) break; if(cnt2!=(n-cnt2)){ flag2=false; break; } ans2=0; cnt2=0; }//判断列 if(flag1 && flag2) cout<<1; else cout<<0; return 0; }
整齐的数组
Polycarp 有一个长度为 𝑛 的数组 𝑎1,𝑎2,...,𝑎**𝑛(𝑛 是偶数)。Polycarp 还得到了一个正整数 𝑘,他开始对数组 𝑎 做如下操作:选择一个下标 𝑖 (1≤𝑖≤𝑛) 使 𝑎**𝑖 减去 𝑘。
在 Polycarp 进行若干次操作后(可能 0 次),数组 𝑎 中的所有数都变成相同的了。请你找到最大的符合要求的 𝑘,如果 𝑘 可以为任意大,请输出 −1。
输入格式
第一行一个整数 𝑡,表示测试单元的个数。
接下来每个测试单元有两行。第一行包含一个偶数 𝑛。第二行包含 n 个整数 𝑎1,𝑎2,...,𝑎**𝑛。
输出格式
对于每个测试单元输出单独一行一个整数 𝑘 (𝑘≥1)—— Polycarp 能用来对数组进行操作的最大的数,或者 −1 —— 如果 𝑘 能任意大的话。
样例输入
3 6 1 5 3 1 1 5 8 -1 0 1 -1 0 1 -1 0 4 100 -1000 -1000 -1000
样例输出
2 1 1100
数据规模
所有数据保证 1≤𝑡≤10,4≤𝑛≤40(𝑛 是偶数),−10^6≤𝑎**𝑖≤10^6,并且 n 的总和不超过100。
解题思路:
1如果只有一种数字,那么k可以无限大,直接输出-1
2不止一种数字,那所有数字要变成相同的数字,只能变成最小数
所以可以先将所有数字从小到大排序,然后算出所有数对于最小是的差值,其中的k一定是范围1~最小数之间所
有差值的最大公约数
完整代码如下:
#include <bits/stdc++.h> using namespace std; int t,n,ans; int a[45],b[45]; int check(); int main() { cin>>t; while(t--){ cin>>n; for(int i=1; i<=n; i++) cin>>a[i]; sort(a+1,a+1+n); //只有一种数字 bool flag=true; if(a[1]==a[n]) flag=false; if(!flag){ cout<<-1<<endl; continue; } //有两种及以上不同的数字 ans=0; for(int i=2; i<=n; i++){ if((a[i]-a[1])!=b[ans]) b[++ans]=a[i]-a[1]; }//存入使所有数能变成最小的那个数的所有可能k int s; s=check();//寻找所有数的最大公约数 cout<<s<<endl; memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); } return 0; } int check() { bool flag=true; for(int i=b[1]; i>=1; i--){ for(int j=1; j<=ans; j++){ if(b[j]%i!=0){ flag=false; break; } } if(flag) return i; else flag=true; } }