蓝桥杯14届B组题解C++(2)
传送阵
题目描述
小蓝在环球旅行时来到了一座古代遗迹,里面并排放置了 n 个传送阵,进入第 i 个传送阵会被传送到第 ai 个传送阵前,并且可以随时选择退出或者继续进入当前传送阵。小蓝为了探寻传送阵中的宝物,需要选择一个传送阵进入,然后连续进入之后的传送阵。小蓝希望尽可能多地进入传送门以便搜索宝物,同时他可以使用一次魔法,从某个传送阵 j 走到相邻的(第 j − 1 或第 j + 1 个)传送阵,请问小蓝最多能到达多少个不同的传送阵?一个传送阵可多次进入,但在计算答案时只算一个。
输入格式
输入的第一行包含一个正整数 n 。第二行包含 n 个正整数 a1, a2, · · · , an ,相邻整数之间使用一个空格分隔。
输出格式
输出一行包含一个整数表示答案。
样例输入
5
2 1 5 4 3
样例输出
4
提示
【样例说明】
小蓝的路径可以是:1 → 2 → 3 → 5 。其中 2 → 3 使用魔法。
【评测用例规模与约定】
对于 20% 的评测用例,1 ≤ n ≤ 1000 ;对于所有评测用例,1 ≤ n ≤ 106,且 a 是 1 至 n 的一个排列。
解题思维
可以利用并查集的思想,完成该题。
对于样例
2 1 5 4 3
它会有这样的三个不同并查集,且最大的大小当前为2。
遍历一遍数组,将两个相邻且属于不同并查集的合并大小,与当前最大的大小比较即可。
发现将1和5两个所在的并查集,合并后大小最大。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6+10;
int a[N];
int far[N];
int sz[N];
//寻找父节点
int findFa(int x)
{
if(far[x]==x)
return x;
return findFa(far[x]);
}
signed main()
{
int n;
cin>>n;
int m=0;
//初始化,且获取数据
for(int i=1;i<=n;i++)
{
cin>>a[i];
far[i]=i;
sz[i]=1;
}
//实现并查集合。将其分为不同的集合。
for(int i=1;i<=n;i++)
{
int cur =i;
do{
int cnt = a[cur];
int f1 = findFa(cur);
int f2 = findFa(cnt);
//当自己的父节点就是自己时,标志着找不到新的节点合并了。
if(f1 == f2)
break;
//将子节点的并查集合并到父节点的并查集。
far[f1] = f2;
sz[f2]+=sz[f1];
//记录好当前最大的并查集的长度。
m = max(m,sz[cnt]);
cur = cnt;
}while(cur!=far[cur]);
}
int f1 = findFa(1);
int f2 = findFa(n);
if(f1!=f2)
{
m = max(m,sz[1]+sz[n]);
}
for(int i=1;i<n;i++)
{
//依次尝试将两个不同并查集的大小合并。(也就是我们的魔法)
f1 = findFa(i);
f2 = findFa(i+1);
if(f1!=f2)
{
m = max(m,sz[f1]+sz[f2]);
}
}
cout<<m;
}
前缀总分
题目描述
给定 n 个由小写英文字母组成的字符串 s1, s2, · · · , sn ,定义前缀总分为V =∑i<j P(si, sj) ,其中 P(si, sj) 表示 si, sj 的最长公共前缀的长度。
小蓝可以选择其中一个字符串,并修改其中的一个字符。请问修改后前缀总分最大为多少?
输入格式
输入的第一行包含一个正整数 n 。接下来 n 行,每行包含一个字符串 si 。
输出格式
输出一行包含一个整数表示答案。
样例输入
3
aab
bbb
abb
样例输出
5
提示
【样例说明】
将第二个字符串改为 abb ,得分为 P(aab, abb)+P(aab, abb)+P(abb, abb) =1 + 1 + 3 = 5 。
【评测用例规模与约定】
对于 20% 的评测用例,1 ≤ n ≤ 20 ;
对于所有评测用例,1 ≤ n ≤ 200 ,1 ≤ |si| ≤ 200 ,其中 |si| 表示 si 的长度。
解题思路
可以先用例子找找关系
假设有两个字符串分别为,且已经找到了前缀总分sum
s
1
=
a
0
a
1
a
2
a
3
s
2
=
b
0
b
1
b
2
b
3
s_1=a_0a_1a_2a_3\\ s_2=b_0b_1b_2b_3
s1=a0a1a2a3s2=b0b1b2b3
假设我们打算修改a1为其他的字符,
$$
记pre[i][j]表示,i,j字符串的最长公共子序列,而suf[k][i][j]表示以k位置开头的,i,j字符串的最长公共后缀长度。
\
\
只有当a1长度的字符之前和s2都已经匹配,以及a1变成了b1。那么
\此时的长度就会变为sum+suf[2][1][2]【2开头的,1和2字符串】+1
$$
其他情况见代码
#include<bits/stdc++.h>
using namespace std;
const int N = 210;
//两个字符串最长公共前缀 i j
int pre[N][N];
//以K开头的两个公共字符串的最长后缀 K i j
int suf[N][N][N];
//字符串
string s[N];
//获取两个字符串的最长公共前缀
int getMaxPre(int i,int j)
{
int m = min(s[i].size(),s[j].size());
int k=0;
for(int z=0;z<m;z++)
{
if(s[i][z]!=s[j][z])
return k;
k++;
}
return k;
}
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)cin>>s[i];
int pans=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(i==j)continue;
pre[i][j] = getMaxPre(i,j);
if(i<j)
pans+=pre[i][j];
for(int k=min(s[i].size(),s[j].size())-1;k>=0;k--)
{
if(s[i][k]==s[j][k])
suf[k][i][j]=suf[k+1][i][j]+1;
}
}
}
int ans=pans;
for(int i=1;i<=n;i++)
for(int j=0;j<s[i].size();j++)
for(char c='a';c<='z';c++)
if(c!=s[i][j])
{
int t = pans;
for(int k=1;k<=n;k++)
{
t-=pre[i][k];
if(pre[i][k]<j)
t+=pre[i][k];
else if(c==s[k][j]&&pre[i][k]==j)
{
t+=j+suf[j+1][i][k]+1;
}
else
t+=j;
}
ans = max(ans,t) ;
}
cout<<ans<<"\n";
return 0;
}
遗迹
题目描述
小蓝找到了一个外星文明留下来的遗迹,遗迹大门的屏幕上有一个长度为m 的字符串 t 和一个输入框,下面还有一个键盘,键盘为一个长度为 n 的字符串 s ,由一个可以横向移动的指针来敲击键盘,指针可以向左移或向右移,不能移出键盘。
小蓝需要在键盘字符串 s 上先指定指针初始位置然后不断移动指针的位置,过程中通过敲击指针所在的字符来进行输入。然而,指针最多只能移动 L 的距离,小蓝想输入一个尽可能长的一个 t 的前缀,请问他最多能输入多少位。
输入格式
输入的第一行包含三个正整数 n, m, L ,相邻整数之间使用一个空格分隔。
第二行包含一个长度为 n 的字符串 s 。
第三行包含一个长度为 m 的字符串 t 。
输出格式
输出一行包含一个整数表示答案。
样例输入
3 6 5
abc
acbbac
样例输出
5
提示
【样例说明】
初始选择指针位于键盘 abc 上的 a ,输入 acbbac 这 6 个字符分别需要指针移动 0, 2, 1, 0, 1, 2 的距离,而最大移动距离为 5 ,所以最多输入 5 个字符,移动 0 + 2 + 1 + 0 + 1 = 4 的距离。
【评测用例规模与约定】
对于 20% 的评测用例,1 ≤ m ≤ 20;
对于所有评测用例,1 ≤ n ≤ 103 ,1 ≤ m ≤ 105 ,1 ≤ L ≤ 109 且 s, t 中只包含小写字母,且 s 中一定包含所有 t 中出现过的字母,数据保证随机。
思路
因为前缀和的最大长度满足,它之前所有字符都要满足条件【前面的字符匹配了,才能匹配后面的字符】考虑动态规划。希望找到的是实现这个前缀的最短路径长度。也就意味着它可以由到达它之前所在的相同字符的所有位置的最短路径长度和到达它同字符的所有位置的所有情况中,找到最短的那条路径,就是到达它的最短路径。
如果最短路径都无法满足<=最大允许长度,说明已经找到了最短路径。
结合下图理解
第一个字符不用考虑,因为它作为初始位置,默认从第二个字符开始看,也就是看它到它前一个字符的所有路径情况,找到实现它同字符所有位置最短的。
第一个b明显更靠近第一个1,到达它的最短路径也就是1。到达第二个b的最短路径明显是2。
依次类推,知道距离不够用或者找全了前缀为止。
#include<bits/stdc++.h>
using namespace std;
vector<int> a[26];
const int N=1e3+10;
//到达该位置的最短路径长度。由n决定
int st[N];
int main()
{
int n,m,l;
cin>>n>>m>>l;
string s,t;
cin>>s>>t;
//将到达这个字符的所有位置记录
for(int i=0;i<n;i++)
{
a[s[i]-'a'].push_back(i);
}
//遍历目标前缀,从第2个字符开始
for(int i=1;i<m;i++)
{
//找到当前字符,以及它之前的一个字符
int cur = t[i]-'a';
int pre = t[i-1]-'a';
//如果相同代表不用移动
if(cur==pre)continue;
bool findn=false;
int ans = 0x3f3f3f3f;
//遍历每一个当前字符的位置,确认到达它的最短路径
for(auto cur_c :a[cur])
{
for(auto pre_c:a[pre])
{
ans=min(ans,st[pre_c]+abs(pre_c-cur_c));
}
st[cur_c] = ans;
//存在能够到达的路径
if(ans<=l)
findn = true;
}
//如果不能到达了就直接输出当前长度,默认是有一个的,因为可以指定初始位置
if(!findn)
{
cout<<i<<"\n";
return 0;
}
}
//都能到达输出目标前缀长度即可。
cout<<m;
}