5280. 替换字符://暴力枚举/*已知结果的模式*/
给定一个长度为 n的字符串 s 和一个长度为 m 的字符串 t。
字符串 s 和 t 均由小写字母组成,字符串 s 的长度不超过字符串 t 的长度。
我们希望字符串 s 成为字符串 t 的子串。
为此,你可以进行任意次(也可以不进行)字符替换操作。
每次操作可以任选字符串 s 中的一个字符并将其替换为
?
。
?
可以视为等于任何其它字符。请你计算,为了使得字符串 s 成为字符串 t 的子串,所需进行的最少操作次数,并提供一种具体操作方案。
例如,如果 s 为
abd
,t 为abcd
,那么我们可以进行一次操作,将 s 中的第 33 个字符替换为?
,替换后 s 变为ab?
,可以与字符串 t 的子串abc
匹配。输入格式
第一行包含两个整数 n,m。
第二行包含一个长度为 n 的由小写字母构成的字符串 s。
第三行包含一个长度为 m 的由小写字母构成的字符串 t。
输出格式
首先,输出一行,一个整数 k,表示所需的最少操作次数。
如果 k=0,则输出到此为止,不要输出多余空行。
如果 k≠0,则还需再输出一行 k 个整数,其中第 i 个整数表示第 i 个操作所替换字符的位置编号。
s 中字符的位置从左到右依次编号为 1∼n。
如果方案不唯一,输出任意合理方案均可。
数据范围
前 6 个测试点满足 1≤n≤m≤10。
所有测试点满足 1≤n≤m≤1000。输入样例1:
3 5 abc daebf
输出样例1:
2 2 3
输入样例2:
4 10 abcd fbcfabaecd
输出样例2:
1 2
#include<bits/stdc++.h>
using namespace std;
int n,m,mi=1000,mi_i;
string s,t;
queue<int> q;
int main() {
cin >> n >> m;
cin >> s >> t;
for(int i=0;i<=m-n;i++){
int cnt=0;
for(int j=0;j<n;j++){
if(s[j]!=t[i+j]) cnt++;
}
if(mi>cnt) mi=cnt,mi_i=i;
}
cout << mi << endl;
for(int i=0;i<n;i++){
if(s[i]!=t[i+mi_i]) cout << i+1 << ' ';
}
return 0;
}
-
#include<iostream> #include<cstring> #include<algorithm> #include<vector> using namespace std; int n,m; string s,t; vector<int> calc(int k){ vector<int> res; for(int i=0;i<n;i++){ if(s[i]!=t[i+k]) res.push_back(i); } return res; } int main(){ cin>>n>>s; cin>>m>>t; vector<int> res(n+1); for(int i=0;i<m-n+1;i++){ auto p=calc(i); if(p.size()<res.size()) res=p; } cout<<res.size()<<endl; for(auto x:res) cout<<x+1<<' '; return 0; }
-
#include<bits/stdc++.h> using namespace std; int main(){ int n,m,ans=2<<20,ansi; string s,t; cin>>n>>m; cin>>s>>t; for(int i=0;i<=m-n;i++){ int sum=0; for(int j=0;j<n;j++){ if(s[j]!=t[j+i])sum++; } if(ans>sum)ansi=i,ans=sum; } cout<<ans<<endl; for(int i=0;i<n;i++){ if(s[i]!=t[ansi+i]) cout<<i+1<<' '; } return 0; }
-
算法竞赛分析思路:
-
数据规模和运行时间限制:
-
算法抉择:
-
eg:P5745 【深基附B例】区间最大和
时间复杂度(运行时间与数据规模的关系)分析:
T (n) 为基本操作执行次数的函数。若存在函数 f(n),使得当n趋近于无穷大时,T(n)/ f(n)的极限值为不等于零的常数,则称 f(n)是T(n)的同数量级函数。
记作 T(n)= O(f(n)),称O(f(n)) 为算法的渐进时间复杂度,简称时间复杂度。
直白点说,就是看基本操作数的数量级。
与数据规模的关系:
在大多数算法竞赛测评平台上,每秒操作次数约为1e^7,(有的计算机是1e+8,)在这个限制下,时间复杂度一定的算法存在能处理的数据规模上限。
具体如下(最为保险的数据规模,视具体问题,上限可略大些):复杂度 数量级 logN >>10^20 N^1/2 10^12 N 10^6 NlogN 10^5 N^2 1000 N^3 100 N^4 50 2^N 20 3^N 14 N! 9 子任务:
算法竞赛里数据规模不同的子任务暗示可能的复杂度,便于选手选择合适的算法,eg此例题中:
子任务1: n<=200 子任务2: n<=300 子任务3: n<-10^5 子任务4: n<=4*10^6 -
时间复杂度:
-
思路1:O(n^3)算法
-
#include<bits/stdc++.h> using namespace std; int a[10000150]; int t=0; int main(){ int n,M; int ansmax=0,ansi,ansj; cin>>n>>M; for(int i=1;i<=n;i++)cin>>a[i]; for(int i=1;i<=n;i++) for(int j=i;j<=n;j++){ int sum=0; for(int k=i;k<=j;k++)t++,sum+=a[k]; if(sum<=M&&sum>ansmax){ ansmax=sum,ansi=i,ansj=j; } } cout<<ansi<<' '<<ansj<<' '<<ansmax<<endl; cout<<t<<endl; return 0; }
程序运行速度和输入规模n有关:
法一:使用全局计算器变量t初始化为0并在实际实现代码功能处进行递增;
法二:数学推导(数学归纳法/*尤其在类似于嵌套循环因为是重复性操作所以满足归纳的要求*/)
实现要求:枚举所有子区间;
T(n)=(1+2+3+4)+(1+2+3)+(1+2)+1
=1*(1+1)/2+2*(1+2)/2+3*(1+3)/2+4*(1+4)/2
=1/2*((1^2+2^2+3^2+4^2)+(1+2+3+4))
=1/2*(1/6n(n+1)*(2n+1)+1/2n(n+1))
=1/6(n^3+3n^2+2n)
主导作用是n^3,所以可以说此算法的时间复杂度是O(n^3)
n=3000时,运算量(与数据规模不一样),n^3=2.7*10e+10;//Time Limit Exceeded.
-
-
思路2:O (n^2) 算法
-
枚举每次区间,计算区间和用前缀和替代//注意如要使用前缀和,为处理边界问题自动初始化s[0]为0;#include<bits/stdc++.h> using namespace std; int a[1000010],s[1000010]; int t=0; int main(){ int n,M; int ansmax=0,ansi,ansj; cin>>n>>M; for(int i=1;i<=n;i++)cin>>a[i]; s[0]=0; for(int i=1;i<=n;i++)s[i]=a[i]+s[i-1]; for(int i=1;i<=n;i++){ for(int j=i;j<=n;j++){ t++; int sum=s[j]-s[i-1]; if(sum<=M&&sum>ansmax){ ansmax=sum,ansi=i,ansj=j; } } } cout<<ansi<<' '<<ansj<<' '<<ansmax<<endl; cout<<t; }
- 对子任务2,n^2=9e+6.对于1e+8仍留有余地
-
-
思路3:O (nlogn)算法:
-
#include<bits/stdc++,h> using namespace std; int a[8000010]; long long s[8000010]; long long n,M; int find(long long x){ int l=1,r=n+1; while(l<r){ int mid=l+r-1>>1; if(s[mid]>=x)r=mid; else l=mid+1; } if(s[l]==x)return l; else return l-1; } int main(){ long long ansmax=0,ansi,ansj; cin>>n>>M; for(int i=1;i<=n;i++)cin>>a[i]; s[0]=0; for(int i=1;i<=n;i++){ long long x=s[i-1]+M; int j=find(x); long long sum=s[j]-s[i-1]; if(sum<=M&&sum>ansmax) ansmax=sum,ansi=i,ansj=j; } cout<<ansi<<' '<<ansj<<' '<<ansmax<<endl; return 0; }
任务目标是得到最贴近M的s[]数组;
//且在贴近数相同的情况下输出最小左端点的情况
//因为从i小的情况开始向大遍历,
//且ansmax的测试条件是>而非>=
//所以就存储到了最优解;
数学语言:枚举所有的i,对于给定的i,要求是s[j]-s[i-1]<=M——s[j]<=s[i-1]+M,找到最大的j;
s[]是前缀和,则s[]是单调递增的,那么就可以用二分查询到符合情况的j;如果未找到x就返回lower_bound;
//时间复杂度是根据算法中n趋近于无穷时的最大数据规模来测定的;
//在读入前缀和与计算前缀和的时间复杂度都是O(N);对最大情况不影响;//根据高数第一章求级数时很好理解
对于子任务3ok,但子任务4时nlogn约等于4e+6*22约等于1e+8//极限满荷,仍需改进算法;
-
-
思路4:O(n)算法:
-
#include<bits/stdc++.h> using namespace std; long long a[8000010],s[8000010]; long long n,m; int main(){ long long ansmax=0,sum=0; int i=1,j=1,ansi,ansj;//i,j分别是队首和队尾指针,j指向区间的下一个数 cin>>n>>m; for(int i=1;i<=n;i++)cin>>a[i]; while(i<=n){ while(j<=n&&sum+a[j]<=M) sum+=a[j],j++;//入队 if(sum<=M&&sum>ansmax) ansmax=sum,ansi=i,ansj=j-1;//注意减1; sum-=a[i],i++;//出队 } cout<<ansi<<' '<<ansj<<' '<<ansmax<<endl; return 0; }
by队列实现窗口滑动并不断更新区间和;
//只需维护两个指针;
//虽然这里有双重循环但所有元素入队与出队的次数都是1
//(均在一个循环遍历中,入队就要出队,所以实际的数据规模是非羁绊计数的)
-
//比O(n^3)还大的算法复杂度eg:O(2^n)、O(n!),对数据量增长很敏感
//所以很多搜索回溯算法连稍大数据规模都处理不了
//在代码结构层面O(n)已经是最优解
//常数优化:处理大数据规模时在语法层面cin的速度远慢于scanf;
P1923 【深基9.例4】求第 k 小的数:
#include<bits/stdc++.h>
using namespace std;
long long n,k,a[5000010];
int main()
{
cin>>n>>k;
for(int i=0;i<n;i++)
scanf("%d",&a[i]);//使用时是300多毫秒,用cin就1.3s多超时
nth_element(a,a+k,a+n);//使第k小整数就位
printf("%d",a[k]);//调用第k小整数
}//尤其在循环遍历时不用cin会优化算法耗时;
提高输入输出流效率的方法:
ios::sync_with_stdio(false)
cin.tie(nullptr)
是C++中的两个语句,用于在输入输出流操作中提高性能。
ios::sync_with_stdio(false)
是一个iostream
类的静态成员函数,将C++标准输入输出流(cin
、cout
等)与C库的输入输出流(stdin
、stdout
等)分离,以避免它们在读写时产生的冲突,从而加快输入输出速度。但需要注意的是,一旦调用了ios::sync_with_stdio(false)
,就不能再使用C库的输入输出函数(如scanf
、printf
)了。cin.tie(nullptr)
是用来解除cin
与cout
之间的默认绑定关系。默认情况下,C++会在每次输出后刷新输出缓冲区,即在使用cout
输出后会自动刷新cin
的缓冲区。这种绑定关系可能会导致在频繁的输入输出操作中产生额外的开销。通过将cin.tie(nullptr)
设置为nullptr
,可以取消cin
和cout
之间的绑定关系,从而提高程序的性能。一般来说,在大多数情况下,这两个语句的使用可以提高输入输出的效率。但需要注意的是,在使用这两个语句后,应避免混合使用C库的输入输出函数和C++的输入输出流,以免产生不可预料的错误
//在数据范围不是非常大时eg:n=1000时,1/100*n^2<100nlogn
//运行程序所需的初始化工作(分配内存等工作)消耗几毫秒//非优化内容,与数据规模无关
//O(1)常数级算法的运算次数与规模无关//不实用循环语句;
-
依据上图实际运行实验可大体推断关系级数
-
-
-
-
-
-
5281. 扩展字符串: //递归
某字符串序列 s0,s1,s2,…0,1,2,… 的生成规律如下:
s0=
DKER EPH VOS GOLNJ ER RKH HNG OI RKH UOPMGB CPH VOS FSQVB DLMM VOS QETH SQB
sn=
DKER EPH VOS GOLNJ UKLMH QHNGLNJ A
+sn−1+AB CPH VOS FSQVB DLMM VOS QHNG A
+sn−1++AB
,其中 n≥1。你需要回答 q 个询问,其中第 i 个询问给定两个整数 n,k 并请你输出字符串 sn 中的第 k 个字符(字符串中的字符索引编号从 11 开始),如果 sn 的长度小于 k,则输出 . 。
输入格式
第一行包含整数 q。
接下来 q 行,每行包含两个整数 n,k表示一个询问。
输出格式
共一行,一个长度为 q 的字符串,其中第 i 个字符表示第 i 个询问的答案。
保证答案的首尾字符不是空格。
数据范围
前 33 个测试点满足 0≤n≤5。
所有测试点满足 1≤q≤10,0≤n≤10e+5,1≤k≤10e+18。输入样例1:
3 1 1 1 2 1 1000000000000000000
输出样例1:
DK.
输入样例2:
5 0 69 1 194 1 139 0 47 1 66
输出样例2:
EFGHI
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=100010;
const LL INF=2e+18;
LL len[N];
string s="DKER EPH VOS GOLNJ ER RKH HNG OI RKH UOPMGB CPH VOS FSQVB DLMM VOS QETH SQB";
string a="DKER EPH VOS GOLNJ UKLMH QHNGLNJ A";
string b="AB CPH VOS FSQVB DLMM VOS QHNG A";
string c="AB";
char f(int n,LL k){
if(k>len[n])return '.';
if(n==0)return s[k-1];
if(k<=a.size())return a[k-1];
k-=a.size();
if(k<=len[n-1])return f(n-1,k);
k-=len[n-1];
if(k<=b.size())return b[k-1];
k-=b.size();
if(k<=len[n-1])return f(n-1,k);
k-=len[n-1];
return c[k-1];
}
int main(){
len[0]=s.size();
for(int i=1;i<N;i++){
len[i]=len[i-1]*2+a.size()+b.size()+c.size();
len[i]=min(len[i],INF);
}
int Q;
cin>>Q;
while(Q--){
int n;
LL k;
cin>>n>>k;
cout<<f(n,k);
}
}
//抽离具象化核心数学思想or核心目标:
根据递推公式:
S(n) = a + S(n-1) + b + S(n-1) + c
可以找出目标字符;|Sn|>=2|Sn-1|,一定大于等于2^n而n是10e5所以大于等于2^100W存不下来(单位就到3000多位了)
//只问第k个字符,所以不用把字符都存下来,可以说是分治或者说是递归中的选择归并类;
f(n,k)
1)k>Sn,return '.';
2)n==0,return Sn[k-1];
3)当再次出现在S[n-1],就进入了下一个子问题即f(n-1,k/*更新过的*/);//最多递归到1e+10次