ACWing第126周周赛

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/210^12
          N10^6
          NlogN10^5
          N^2    1000
          N^3100
          N^450
          2^N20
          3^N14
          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)   算法
            • #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;
              }
                  枚举每次区间,计算区间和用前缀和替代//注意如要使用前缀和,为处理边界问题自动初始化s[0]为0;
              • 对子任务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++标准输入输出流(cincout等)与C库的输入输出流(stdinstdout等)分离,以避免它们在读写时产生的冲突,从而加快输入输出速度。但需要注意的是,一旦调用了ios::sync_with_stdio(false),就不能再使用C库的输入输出函数(如scanfprintf)了。

              cin.tie(nullptr)是用来解除cincout之间的默认绑定关系。默认情况下,C++会在每次输出后刷新输出缓冲区,即在使用cout输出后会自动刷新cin的缓冲区。这种绑定关系可能会导致在频繁的输入输出操作中产生额外的开销。通过将cin.tie(nullptr)设置为nullptr,可以取消cincout之间的绑定关系,从而提高程序的性能。

              一般来说,在大多数情况下,这两个语句的使用可以提高输入输出的效率。但需要注意的是,在使用这两个语句后,应避免混合使用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次

  • 13
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值