目录:
最长上升子序列(LIS)
1.模板(数据较小)
2.模板(数据较大)
牛客刷题
1. 牛客练习赛107A:如见青山
2.牛客小白月赛65A牛牛去购物
3.牛客小白月赛65B牛牛去购物
4.牛客小白月赛65C牛牛排队伍
最长上升子序列(LIS)
1.模板(数据较小)
洛谷B3637 最长上升子序列
题目描述
这是一个简单的动规板子题。给出一个由n(n≤5000) 个不超过10^6的正整数组成的序列。
请输出这个序列的最长上升子序列的长度。
最长上升子序列是指,从原序列中按顺序取出一些数字排在一起,这些数字是逐渐增大的。
输入格式
第一行,一个整数n,表示序列长度。
第二行有n 个整数,表示这个序列。
输出格式
一个整数表示答案。
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 5e3+10;
int ans=0;
int a[maxn],dp[maxn];
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) { //正序枚举i的位置
dp[i] = 1; //每次初始化
for (int j = 1; j < i; j++) { //在i前面找j的位置
if (a[i] > a[j]) { //如果满足条件,则第i个数可以放在第j个数后面
dp[i] = max(dp[i], dp[j] + 1);//比较不取i位置:dp[i]
} //和取i位置后第i个数放在第j个数后面:dp[j]+1哪个数目大
ans = max(ans, dp[i]);//对可能的最大值进行比较
}
}
cout << ans << '\n';
/*cout << *max_element(dp + 1, dp + 1 + n) << '\n';*/
//*max_element(begin, end)函数返回最大元素,不加*返回地址,头文件algorithm
//*min_element(begin, end)相同用法
}
2.(数据较大)
洛谷-AT2827最长上升子序列
给定一个长为 n 的序列ai,求这个序列的最长单调上升子序列长度。
(1≤ai≤n≤10^5)
#include <iostream>
#include <algorithm>
const int maxn = 1e5 + 10;
#define inf 0x3f3f3f3f
using namespace std;
int n;
int a[maxn], b[maxn];
int low[maxn];
int mylower_bound (int r, int x) {
int l = 1;
while (l <= r) {
int mid = (l + r) >> 1;
if (low[mid] <= x)
l = mid + 1;
else
r = mid - 1;
}
return l;
}
int main(void) {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
low[i] = inf;
}
int len = 1;
low[1] = a[1];
for (int i = 2; i <= n; ++i) {//枚举a
if (a[i] >= low[len])//如果a[i]> 目前最长上升子序列的最优结尾,长度+1,结尾替换
low[++len] = a[i];
else //二分查找将该结尾放到最优的位置,替换掉前面比他大的最优结尾,成为最优结尾
low[mylower_bound(len, a[i])] = a[i];
/*dp[lower_bound(dp + 1, dp + len + 1, a[i]) - dp] = a[i];*/
}
cout << len << '\n';
return 0;
}
1. 牛客练习赛107A:如见青山
给定n, m, 求(n!)!模m的值。其中!代表阶乘,模代表取余运算。
思路:
当n! >= m时,(n!)!内存在因子m, 即答案为0
#include<iostream>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
ll t,m,n, flag, ans=1;
ll dp[maxn];
int main()
{
ios::sync_with_stdio(false);
cin >> t >> m;
dp[0] = 1;
for (int i = 1; i <= maxn; i++)
{
dp[i] = (dp[i - 1] * i) % m;
ans *= i;
if (ans >= m && !flag){
flag = i;
}
}
while (t--){
cin >> n;
if (n >= flag)
cout << 0 <<'\n';
else
cout << dp[dp[n]] % m << '\n';
}
return 0;
}
2.牛客小白月赛65A牛牛去购物
牛牛带着n 元钱去超市买东西,超市一共只有两款商品,价格为a 元的篮球和价格为b 元的足球,
牛牛想把手里的钱尽可能花光,请问牛牛最少能剩多少钱?
(1 <= n, a, b <= 1000)
思路:
数据较小,暴力枚举
#include<iostream>
using namespace std;
int n, a, b;
int main()
{
ios::sync_with_stdio(false);
cin >> n >> a >> b;
int Min = 0x3f3f3f3f;
for (int i = 0; i <= 1000; i++) {
for (int j = 0; j <= 1000&& (a * i + b * j)<=n; j++) {
Min = min(Min, n - a * i - b * j);
}
}
cout << Min << '\n';
return 0;
}
3.牛客小白月赛65B牛牛去购物
牛牛给牛妹写了一封情书(仅包含小写字母a到z),但是被牛可乐截获了,由于牛可乐也喜欢牛妹
所以往这份情书里增加了许多数字和特殊字符(也可以不加),
最后收到情书的牛妹想知道原本的情书是否包含某个字符串k。如果包含则输出 YES,否则输出 NO。
包含指k 是原本情书的子串,子串即任意连续的字符构成的子字符串,
例如对于字符串abcdefg,"abd","acg","afe"不是该字符串的子串,"
"abc","cde"是该字符串的子串。
牛可乐添加的数字和特殊字符仅包含以下这些:
0123456789 + -*| , .~!@#$%^& ()[] {}'";:?<>\/
输入描述 :
第一行输入两个正整数n, m(1≤n, m≤5×10^3) ,n 表示字符串s 的长度,m 表示字符串k 的长度。
第二行输入一个字符串s ,代表牛妹最后收到的情书。
第三行输入一个字符串k ,代表牛妹想知道原本的情书是否包含的单词。
保证s 和k 中都不包含空格。
输出描述 :
输出一行一个YES或者NO代表答案。
思路:
数据不是很大,同样暴力匹配即可
#include<iostream>
using namespace std;
int n, m;
string s, k;
string p = "0123456789+-*|,.~!@#$%^&()[]{}'\";:?<>\\/";
int main()
{
ios::sync_with_stdio(false);
cin >> n >> m;
cin >> s >> k;
for (int i = 0; i <= s.size(); i++) { //枚举s
if (s[i] == k[0]) {
int l = i + 1, j = 1;
while (l < s.size() && j < k.size())
{
bool flag = 0;
for (int m = 0; m <= p.size(); m++) {
if (s[l] == p[m])
flag = 1;
}
if (s[l] == k[j])
l++, j++;
else if (flag)
l++;
else
break;
}
if (j == k.size()) {
cout << "YES" << '\n';
return 0;
}
}
}
cout << "NO" << '\n';
return 0;
}
4. 牛客小白月赛65C牛牛排队伍
题目描述
有n 个人排队,1 号排在2 号前面,2 号排在3 号前面,...,以此类推
n−1 号排在n 号前面。在这个过程中,偶尔老师会把某个人叫走,叫走之后这个人就离开了队伍,假设本来
a 排在b 前面,b 排在c 前面,b 被叫走后a 就排在了c 前面。老师偶尔也会感到疑问,她想知道此时排在
a 号前面的同学是几号呢?
已知老师一共会有k 次操作,每次会执行下面的一种:
1. 把x 叫走。
2. 求排在x 前面的是谁。
输入描述 :
第一行输入两个正整数
n, k(1≤n, k≤10^6)n 表示排队人数,k 表示老师的操作次数。
接下来k 行,每行包含2个整数,表示一个操作,具体如下:
1 x :把x 叫走,保证此时x 一定在队伍里
2 x :输出排在x 前面的人的编号,保证此时x 一定在队伍里,如果没有人排在x 前面,输出0 。
(1≤x≤n)
输出描述:
输出包含若干行整数,即为所有操作 2 的结果。
1.(这道题卡vector,vector只能过95% 的数据!!!)
2.后移一步不能通过
#include<iostream>
using namespace std;
const int maxn = 1e6 + 10;
int n, k;
int p[maxn];//p记录每一个前面人编号
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> k;
for (int i = 1; i <= n; i++) {
p[i] = i - 1;
}
for (int i = 1; i <= k; i++) {
int n, x;
cin >> n >> x;
if (n == 2)
cout << p[x] << '\n';
else {
for (int j = k; j>= x; j--) { //向后移步
p[j] = p[j-1];
}
}
}
return 0;
}
优化
#include<iostream>
using namespace std;
const int maxn = 1e6 + 10;
int n, k;
int p[maxn];//p记录每一个前面人编号
int Next[maxn];//记录后一个人编号
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> k;
for (int i = 1; i <= n; i++) {
p[i] = i - 1;
Next[i] = i + 1;
}
for (int i = 1; i <= k; i++) {
int n, x;
cin >> n >> x;
if (n == 2)
cout << p[x] << '\n';
else {
int a = p[x];
int b = Next[x];
p[b] = p[x];
Next[a] = Next[x];
}
}
return 0;
}