2024SMU蓝桥训练2补题
C-密文搜索
思路:不难。
void solve(){ //C--密文搜索 可以不是字符串哈希--因为只需要知道相同长度字符串对字母出现情况,可以对字符串进行!!!排序!!!
string str; cin>>str;
int n,ans=0; cin>>n;
unordered_map<string,int> mp;
for(int i=1;i<=n;i++){
string x; cin>>x;
sort(x.begin(),x.end()); //对字符串进行排序!!!
mp[x]++;
}
for(int i=0;i<str.size()-7;i++){
string x=str.substr(i,8);
sort(x.begin(),x.end());
ans+=mp[x];
}
cout<<ans;
}
D-交换次数
思路:十分有技巧!首先最终结果有6种,要对六种可能枚举,这个是容易想到的。
但是怎么检查某种排列的代价?
已知A,B,T各自出现的次数,那么枚举那种情况,就知道哪种情况最终具体是什么样的。
例如:输入 TTA BBT BAATT
情况BAT: BBB AAA TTTTT
定义f1t23,f1t2--f2t1,f2t3,含义为,第一部分要换到2,3部分的个数,第一部分要换到第2部分的个数--第二部分要换到第一部分的个数,第二部分要换到第三部分的个数。
第3部分不用管,因为前面两部分换好了,第3部分必然也换好了。
考虑3种情况:
①f1t2==f2t1:这种情况最好考虑:cost=f1t23+f2t3
②f1t2>f2t1:这种情况也比较好考虑,先把f2t1换了,再换多余的f1t2,,和换f1t3和f2t3:cost=f1t23+f2t3
③f1t2<f2t1:这种情况因为没有记录第三部分的情况,所以相对比较绕:cost=f1t23-f1t2+f2t1+f2t3--这里的f1t23-f1t2=f1t3,即cost=f1t3+f2t1+f2t3
第三种情况先f1t3是为了把在第三部分的,第二部分的字母,换到第一部分,然后进行f2t1的时候就可以换上。
string str;
int n,ans=INT_MAX,cnt[3];
unordered_map<char,int> mp;
string pos[6]={"ABT","ATB","BAT","BTA","TAB","TBA"};
void check(char a,char b,char c){
int f1t23=0,f1t2=0,f2t1=0,f2t3=0;
for(int i=0;i<cnt[mp[a]]+cnt[mp[b]];i++){
if(i<cnt[mp[a]]){
if(str[i]!=a) f1t23++;
if(str[i]==b) f1t2++;
}
else{
if(str[i]==a) f2t1++;
if(str[i]==c) f2t3++;
}
}
if(f1t2==f2t1) ans=min(ans,f1t23+f2t3);
else if(f1t2>f2t1) ans=min(ans,f1t23+f2t3);
else ans=min(ans,f1t23-f1t2+f2t1+f2t3);
}
void solve(){ //补D--交换次数--nb
//枚举6种情况容易想到,问题是怎么check某种情况下的代价。
cin>>str; n=str.size();
mp['A']=0,mp['B']=1,mp['T']=2;
for(int i=0;i<n;i++){
if(str[i]=='A') cnt[mp[str[i]]]++;
else if(str[i]=='B') cnt[mp[str[i]]]++;
else cnt[mp[str[i]]]++;
}
for(int i=0;i<6;i++) check(pos[i][0],pos[i][1],pos[i][2]);
cout<<ans;
}
E-k倍区间
思路:错了又错的老题。。见注释。
void solve(){ //补E--K倍区间--在牛客还是cf,做过一模一样的题目,当时也是补的题。现在再遇到,还是没想到,还是补题。。
//前缀和pre;
//如果某个区间[L,R]的和是K的倍数;
// 那么(pre[R]-pre[L-1])%k=0;
//pre[R]%k-pre[L-1]%k=0;
//pre[R]%k = pre[L-1]%k;
//答案显而易见,只需记录前缀和中出现相同余数的数字,两两配对的对数即是ans;
int n,k,ans=0; cin>>n>>k;
int sum=0;
unordered_map<int,int> mp;
//mp[0]=1; //init
for(int i=1;i<=n;i++){
int x; cin>>x;
sum=(sum+x)%k;
ans+=mp[sum]; //到目前,前面有几个现在这个余数,就可以构成几个合法区间.
if(sum==0) ans++; //sum刚刚好为0的时候,代表这个区间不用减去前面某段,从1到cur刚刚好是k的倍数。当然也可以减去前面某段,就是减去前面也是0的区间。
mp[sum]++; //这个余数出现次数加一
}
cout<<ans;
}
void solve(){ //补E--K倍区间--在牛客还是cf,做过一模一样的题目,当时也是补的题。现在再遇到,还是没想到,还是补题。。
int n,k,ans=0; cin>>n>>k;
int sum=0;
unordered_map<int,int> mp;
for(int i=1;i<=n;i++){
int x; cin>>x;
sum=(sum+x)%k;
mp[sum]++; //这个余数出现次数加一
}
for(int i=0;i<k;i++) ans+=(mp[i]*(mp[i]-1)/2); //组合--Cm2:m个中选两个配对;Cm2=m*(m-1)/2;
ans+=mp[0]; //刚刚好为0的时候,代表这个区间不用减去前面某段,从1到cur刚刚好是k的倍数。当然也可以减去前面某段,就是减去前面也是0的区间(上面循环已经算过了).
cout<<ans;
}
G-包子凑数
思路:正解是类似背包的dp,我的写法不是正解,但是类似。
void solve(){ //G--包子凑数 siwei?
//似乎不是正解...要设好参数范围
//一开始用vector来维护能组合出来的数字。
//但是判断一个数x的能否被组合出来时,时间复杂度过高--用两层循环枚举vct所有数字组合---o(n^2)。
//改成了set之后,既避免了重复数字,还能o(nlogn)复杂度下判断x能否被组合出来
//参数范围ok的话,可以AC
int n; cin>>n;
set<int> st;
int minn=200;
bool checkone=false;
for(int i=1;i<=n;i++){
int x; cin>>x;
st.insert(x);
minn=min(minn,x);
if(x==1) checkone=true;
}
if(checkone){
cout<<"0";
return;
}
int ans=0;
set<int> test;
st.insert(0); //便于查找本身存在于st的x
for(int x=1;x<=10000;x++){ //参数范围--1000太小,会wa两个点--5,6,7,8,9000太小,wa一个点--10000够了
bool check=false;
for(auto s:st){
if(st.find(x-s)!=st.end()){
st.insert(x);
check=true;
break;
}
}
if(!check) {
test.insert(x);
ans++;
}
}
bool check=false;
int cur=1;
for(auto p=st.begin();p!=st.end();p++){
if(p==st.begin()) continue;
if(*p-*prev(p)==1) cur++;
else cur=1;
if(cur==minn){ //判断能组合出来的数字,是否有minn个连续的数字,有的话后面所有数字都可以推出来。
//--否则可以判定为INF,虽然不是完全严谨,但是在搜索1e4个数字后还没有连续minn个数字,就认为是INF了
check=true;
break;
}
}
if(check) cout<<ans;
else cout<<"INF";
}
H-疑惑和之和
思路:拆位+贡献+前缀和。比较有价值的一题
int n,ans=0;
int arr[100005];
void solve(){ //补H-异或和之和 (二进制)拆位+贡献法 o(20*n) 学习新思想
cin>>n;
for(int i=1;i<=n;i++) cin>>arr[i];
for(int j=0;j<=20;j++){ //二进制位下的位置
int sum=0,cntodd=0,cnteven=0;
for(int i=1;i<=n;i++){
if(arr[i]>>j&1) sum++;
if(sum&1) { ////如果现在这一位为1的前缀和是奇数,那么这个数字就有前面(cnteven+1)个合法区间在这一位上有贡献
cntodd++;
ans+=(1<<j)*(cnteven+1); ////每个总和为奇数的区间,都会在这一位上有贡献
}
else { ////如果现在这一位为1的前缀和是偶数,那么这个数字就有前面cntodd个合法区间在这一位上有贡献
cnteven++;
ans+=(1<<j)*cntodd; ////每个总和为奇数的区间,都会在这一位上有贡献
}
}
}
cout<<ans;
}