2024SMU藍橋訓練2補題

osir發表於2024-04-10

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的時候就可以換上。

2024SMU藍橋訓練2補題

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-疑惑和之和

思路:拆位+貢獻+字首和。比較有價值的一題

2024SMU藍橋訓練2補題2024SMU藍橋訓練2補題

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

相關文章