题单链接
蓝桥杯题单day2【题目】_奔跑的星黛露的博客-CSDN博客
题解
蓝肽子序列
链接
https://www.lanqiao.cn/problems/1030/learning/?page=1&first_category_id=1&sort=students_count&second_category_id=3&tags=%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92,%E5%9B%BD%E8%B5%9B
思路
先将字符串预处理分割为每个大写字母开头的“蓝肽” 在将其作为判断最长子序列 判断最长子序列 可以采用 动态规划(最简单高效) if S1[i] = S2[j] dp[i][j] = dp[i][j]+1; else dp[i][j] = max(dp[i-1][j] , dp[i][j-1]) 输出最大值即可。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1000;
int dp[N][N];
string a,b;
vector<string>tmp;
vector<string>w;
int main(){
cin >> a >> b;
for(int i = 0, j = 0 ; i < a.size();){
if(a[j] >= 'A' && a[j] <= 'Z'){
j++;
while(a[j] >= 'a' && a[j] <= 'z' && j < a.size())j++;
tmp.push_back(a.substr(i,j - i));
i = j;
}
}
for(int i = 0, j = 0 ; i < b.size();){
if(b[j] >= 'A' && b[j] <= 'Z'){
j++;
while(b[j] >= 'a' && b[j] <= 'z' && j < b.size())j++;
w.push_back(b.substr(i,j - i));
i = j;
}
}
int ans = 0;
int n = tmp.size(),m = w.size();
for(int i = 1; i <= n ; i++){
for(int j = 1 ; j <= m; j++){
if(tmp[i - 1] == w[j - 1])dp[i][j] = dp[i - 1][j - 1] + 1;
else dp[i][j] = max(dp[i - 1][j],dp[i][j - 1]);
}
}
cout << dp[n][m] << endl;
return 0;
}
能量项链
链接
https://www.luogu.com.cn/problem/P1063
思路
考虑区间DP。
定义 fij 表示将区间 [i, j] 合并的最大能量。那么我们可以有简单的转移方程:
f[i, j] = max_{i <= k <= j} {f[i, k] + f[k, j] + w[l] * w[k] * w[r]}
上述转移方程适用于一条链的转移,但是本题我们是一个环,因此我们可以使用一个常用的技巧将环拆解成两倍长度的链,所以 答案就是 max_{1 <= i <= n} f[i, i + n - 1]
代码
#include "bits/stdc++.h"
using namespace std;
const int N=220;
int n,g[N],f[N][N];
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>g[i];
g[i+n]=g[i];
}
for(int len=3;len<=n+1;len++){
for(int l=1;l+len-1<=2*n;l++){
int r=l+len-1;
for(int k=l+1;k<r;k++){
f[l][r]=max(f[l][r],f[l][k]+f[k][r]+g[l]*g[k]*g[r]);
}
}
}
int res=0;
for(int l=1;l<=n;l++) res=max(res,f[l][l+n]);
cout<<res<<endl;
return 0;
}
字串
链接
https://www.luogu.com.cn/problem/P2679
思路
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define CaseT int CaseT; cin >> CaseT; while(CaseT--)
#define endl '\n'
#define all(x) (x).begin(), (x).end()
#define rall(x) (x).rbegin(), (x).rend()
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
void solve() {
int n, m, K; string a, b; cin >> n >> m >> K >> a >> b;
vector<vector<int>> dp(m + 1, vector<int>(K + 1));
dp[0][0] = 1;
vector<vector<int>> pre(m + 1, vector<int>(K + 1));
for (int i = 1; i <= n; i++) {
for (int j = m; j; j--) { // 从后往前匹配a[1 ... i]和b[1 ... j]
for (int k = K; k; k--) {
if (a[i - 1] == b[j - 1])
pre[j][k] = (pre[j - 1][k] + dp[j - 1][k - 1]) % MOD;
else pre[j][k] = 0;
dp[j][k] = (dp[j][k] + pre[j][k]) % MOD;
}
}
}
cout << dp[m][K] << endl;
}
int main() {
cin.tie(0)->sync_with_stdio(false);
// init();
// CaseT
solve();
return 0;
}
对局匹配
链接
https://www.lanqiao.cn/problems/107/learning/?page=1&first_category_id=1&sort=students_count&second_category_id=3&tags=%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92,%E5%9B%BD%E8%B5%9B
思路
进行分组dp,这里是差值只要是k就不能同时选,所以会将差值为k的划分为一组,然后每一组进行动态规划就可以了
dp[i]=max(dp[i−2]+arr[i], dp[i−1])
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 100005;
int cnt[N], arr[N], dp[N];
int n, k;
int main() {
while (scanf("%d%d", &n, &k) == 2) {//输入个数n,差值k
memset(cnt, 0, sizeof(cnt));//这里的cnt是记录每个值出现的次数
int val, ans = 0;
for (int i = 1; i <= n; i++) {
scanf("%d", &val);
cnt[val]++;
}
//特殊处理k=0的情况
//处理思路是把所有的出现的元素都给ans加一
if (k == 0) {
for (int i = 0; i < N; i++) {
if (cnt[i]) ans++;//计算有多少种数字
}
}
else {
for (int i = 0; i < k; i++) {//k个小组
int len = 0;
for (int j = i; j < N; j += k) {
arr[len++] = cnt[j];//遍历每个小组中的所有元素
//arr数组记录了每个元素的个数
}
//状态转移方程
dp[0] = arr[0];
for (int j = 1; j < len; j++) {
if (j == 1)
{
dp[j] = max(dp[0], arr[j]);
}
else
{
dp[j] = max(dp[j - 2] + arr[j], dp[j - 1]);
}
}
ans += dp[len - 1];//把k组的值都相加
}
}
printf("%d\n", ans);
}
return 0;
}
货币系统
链接
https://www.luogu.com.cn/problem/P5020
思路
若两个货币系统等价,有如下性质
1. a1,a2,...,an一定都可以被表示出来
2. 在最优解中,b1,b2,...bm一定都是从a1,a2,...,an中选择的
2. b1,b2,...,bm一定不能被其他bi表示出来
步骤
由于数据中不存在负数,将 a[] 数组从小到大排序
1. 若ai能被a0~a(i-1)表示出来,则一定不选
2. 若ai不能被能被a0~a(i-1)表示出来,则一定选
时间复杂度 O(nm)
代码
#include "bits/stdc++.h"
using namespace std;
const int maxn=25010;
typedef long long ll;
int t,n,f[maxn],a[110];
int main(){
scanf("%d",&t);
while(t--){
scanf("%d",&n);
memset(f,0,sizeof f);
f[0]=1;
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
}
sort(a,a+n);
int v=a[n-1],ans=0;
for(int i=0;i<n;i++){
if(f[a[i]]==0) ans++;
for(int j=a[i];j<=v;j++){
f[j]+=f[j-a[i]];
}
}
printf("%d\n",ans);
}
return 0;
}
最优包含
链接
https://www.lanqiao.cn/problems/239/learning/?page=1&first_category_id=1&sort=students_count&second_category_id=3&tags=%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92,%E5%9B%BD%E8%B5%9B
思路
状态定义:f[i][j]表示第一个字符串前i个字符,第二个字符串前j个字符,第一个字符串前i个字符包含第二个字符串前j个字符的最少修改次数
初始状态:f[i][0] = 0 other f[i][j] = INF
状态转移:s1[i]==s2[j] f[i][j] = f[i - 1][j - 1]
s1[i] != s2[j] max(f[i - 1][j - 1] + 1, f[i - 1][j])
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1024, INF = 0x3f3f3f3f;
int f[N][N];
int main()
{
string s1, s2;
cin >> s1 >> s2;
int n = s1.size();
int m = s2.size();
memset(f, 0x3f, sizeof(f));
for (int i = 0; i <= n; i++) f[i][0] = 0;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= i && j <= m; j++)
{
if (s1[i - 1] == s2[j - 1]) f[i][j] = f[i - 1][j - 1];
else
{
f[i][j] = f[i - 1][j - 1] + 1;
if (i - 1 >= j) f[i][j] = min(f[i][j], f[i - 1][j]);
}
}
}
cout << f[n][m] << endl;
return 0;
}
合根植物
链接https://www.lanqiao.cn/problems/110/learning/?page=1&first_category_id=1&sort=students_count&second_category_id=3&tags=%E5%B9%B6%E6%9F%A5%E9%9B%86
思路
编号为 a 的小格子和编号为 b 的小格子合根表示他们合并在了同一个集合里,把所有合并之后,若发现某一个格子的father等于格子编号,说明这是一个集合,统计一下就可以算出共有多少个合根植物。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 20;
int m, n, k, fa[N];
int find(int x){
if(x == fa[x]) return x;
return fa[x] = find(fa[x]);
}
void uni(int x, int y){
int fx = find(x), fy = find(y);
fa[fx] = fy;
}
int main()
{
cin >> m >> n >> k;
for(int i = 1; i <= m * n; i++) fa[i] = i;
while(k--){
int x, y;
cin >> x >> y;
uni(x, y);
}
int ans = 0;
for(int i = 1; i <= m * n; i++){
if(find(i) == i) ans++;
}
cout << ans;
return 0;
}
Bitwise Exclusive-OR Sequence
链接
https://ac.nowcoder.com/acm/contest/24346/B?&headNav=acm
思路
种类并查集,首先可能是多个图, 对于每个图中的每一位, 取0 或者取1 都是可以确定图上的其他数字取0 或者取1 ,所以我们可以去枚举取0 或者取1 ,来取个min 得到结果。
所以这里我们用并查集, 最大数为 2的30次, 所以我们对于每一位都做一个并查集, 就是30个并查集。 f[a] 表示当前位置取1,f[a+n] 表示当前位取0,这样就可以枚举区间了。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5+10;
ll n, m, fa[N], sz[N];
ll a[N];
struct Node
{
ll a, b;
ll val;
}num[N];
ll find(ll x)
{
if(x == fa[x]) return x;
return fa[x] = find(fa[x]);
}
void init()
{
for(ll i = 1; i <= n; i ++)
{
fa[i] = i, sz[i] = 1;
fa[i+n] = i+n, sz[i+n] = 0;
}
}
void uni(ll a, ll b)
{
sz[a] += sz[b];
fa[b] = a;
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= m; i ++)
{
scanf("%lld%lld%lld", &num[i].a, &num[i].b, &num[i].val);
}
ll res = 0;
for(int i = 0; i < 30; i ++)
{
init(); // 每一次做并查集都要初始化!
for(int j = 1; j <= m; j ++)
{
int a1 = find(num[j].a);
int b1 = find(num[j].b);
int a2 = find(num[j].a + n);
int b2 = find(num[j].b + n);
if((num[j].val >> i) & 1 ) // 不同为1 ,一个取0一个取1
{
if(a1 == b1)
{
cout << -1 << endl;
return 0;
}
if(a1 == b2) continue;
uni(a1, b2);
uni(b1, a2);
}
else // 相同为0, 都取1 或者都取0
{
if(a1 == b2)
{
cout << -1 << endl;
return 0;
}
if(a1 == b1) continue;
uni(b1, a1);
uni(b2, a2);
}
}
for(int j = 1; j <= n; j ++) // 对于每个数字计算结果
{
res += (ll)min(sz[find(j)], sz[find(j+n)]) * (1 << i);
sz[find(j)] = 0;
sz[find(j+n)] = 0;
}
}
cout << res << endl;
return 0;
}