题意本身并不复杂,就是试密码,一共试m次第i次在a[i]位置错误,很容易想到字母桶排序,但仔细一看,n的范围在2*10的五次方,这不是一个n方能完成的问题,所以我们要使用一些技巧处理问题,不难发现,a[i]越大它前面所有的敲击次数都要+1,那么我们先用一个数组读取整个m区域,存下每个位置结尾的次数,在计算是倒着向前加a【i】=a【i+1】+a【i】,可以存储过程中所有加过的位置整体算法复杂度只有o(n)代码如下
#include<bits/stdc++.h>
using namespace std;
int arr[200100],res[30];
int main(){
int t; cin>>t;
while(t--){
int n; cin>>n;
int m; cin>>m;
string s= ""; cin>>s;
for(int i=0; i<n; i++)
res[s[i]-'a']++;
for(int i=0; i<m; i++){
int x; cin>>x;
arr[x]++;
}
for(int i=n; i>=1; i--){
arr[i]+=arr[i+1];
res[s[i-1]-'a']+=arr[i];
}
for(int i=0; i<26; i++)cout<<res[i]<<" ";cout<<endl;
}
return 0;}
下面介绍的二分是一种nlogn的做法,他就是在原算法的基础上把遍历改成了二分,通过lowbound函数找到第一个大于i的位置,m-该位置得到的就是这个位置因为打错而出现的次数(在这次打错之后,每次该位置都会打错)这需要logn的时间就可以找到当前位置所需要加的数量。
需要注意的是low_bound函数返回的是一个地址,如果要得到位置需要把它减去数组开头,这样就可以找到在数组第某项到第某项过程中的第一个大于某个数的数
代码如下
#include<bits/stdc++.h>
using namespace std;
int main(){
int t;
cin>>t;
while(t--){
int n,m;
cin>>n>>m;
string a;
cin>>a;
int p[m+1],mp[600]={0};
for(int i=1;i<=m;i++) cin>>p[i];
sort(p+1,p+m+1);
for(int i=0;i<n;i++)
mp[a[i]]+=m-(lower_bound(p+1,p+m+1,i+1)-p)+1;
for(int i=0;i<n;i++) mp[a[i]]++;
for(char i='a';i<='z';i++){
if(i!='a') cout<<" ";
cout<<mp[i];
}
cout<<endl;
}
return 0;
}