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;
}

 

posted @ 2024-04-10 22:17  osir  阅读(3)  评论(0编辑  收藏  举报