2024牛客暑期多校訓練營2 HI

Beater_1117發表於2024-07-20

2024牛客暑期多校訓練營2

H.Instructions Substring

題意:

有一個字串序列,有WSAD四種操作,可以上下左右移動。可以選取一段連續的子序列,從(0,0)出發,經過連續子序列操作後可以經過點(x,y),問這樣的子序列有多少個

思路:

若一個子序列能夠實現到達點(x,y),那麼在這個子序列後面加任意字元都符合要求,因此只需要找到一個最短的合法子序列即可。一個完整的字串序列可以形成一個路徑,若截去前面一段子序列,形成的新路徑只需要將原有路徑加個字首和的偏移量即可。我們可以記錄下整個序列路徑中經過每個點時的下標;然後列舉子序列的起始下標,將(x,y)加上移動路徑字首和的偏移量,看原路徑是否經過這個點,經過這個點時的下標是否大於此時的起始下標,若滿足則可以二分得到最短合法子序列,然後加上這個貢獻n-右邊界+1

程式碼:

#include <bits/stdc++.h>
using namespace std ;
const int MAXN=1e5+7;
const int INF = 0x3f3f3f3f;
typedef long long ll;
#define endl "\n"

void solve(){
    int n,x,y;
    cin>>n>>x>>y;
    string s;
    cin>>s;
    if(x==0&&y==0){
        ll ans=n*(n+1)/2;
        cout<<ans<<endl;
        return ;
    }
    map<pair<int ,int >,vector<int > > mp;
    int prex=0,prey=0;
    for(int i=0;i<n;i++){
        if(s[i]=='W'){
            prey++;
        }else if(s[i]=='S'){
            prey--;
        }else if(s[i]=='A'){
            prex--;
        }else{
            prex++;
        }
        mp[{prex,prey}].push_back(i);
    }
    prex=0,prey=0;
    ll ans=0;
    for(int i=0;i<n;i++){
        if(!mp[{x+prex,y+prey}].empty()){
            int left=0,right=mp[{x+prex,y+prey}].size();
            while(left<right){
                int mid=(left+right)/2;
                if(mp[{x+prex,y+prey}][mid]<i){
                    left=mid+1;
                }else{
                    right=mid;
                }
            }
            if(left<mp[{x+prex,y+prey}].size()&&mp[{x+prex,y+prey}][left]<n){
                ans+=n-mp[{x+prex,y+prey}][left];
            }
        }
        if(s[i]=='W'){
            prey++;
        }else if(s[i]=='S'){
            prey--;
        }else if(s[i]=='A'){
            prex--;
        }else{
            prex++;
        }
    }
    cout<<ans<<endl;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);

    int t=1;
    
    // cin>>t;
    while(t--){
        solve();
    }
    
    return 0;
}

I.Red Playing Cards

題意:

有2n張卡牌,1到n的所有數字都恰好出現2次。
每次操作,可以選擇相同數字的兩張牌,刪去這兩張牌之間的區間(包含這兩張牌),其貢獻為這個數字大小
區間的牌數,問怎樣操作可以使貢獻最大

思路:

對於兩個區間,若兩個區間有重疊部分,則兩個區間只能二選一;若兩個區間是包含關係,則可以選擇先取裡面的小區間,再取外面的大區間;若兩個區間沒有任何重疊,則可以都選取。
區間的貢獻為“邊界數字大小*區間的牌數”,那麼可以看作這個區間每個數的貢獻為“邊界的數字大小”.對於兩個區間是包含關係的情況,可以知道若“小區間的數字大小”大於“大區間的數字大小”,那麼先取小區間再取大區間可以最大化貢獻,反之沒有意義。
因此我們可以先預處理出每個數字的左右邊界,從大到小遍歷這些數字,得出每個數字的區間的最大貢獻,最後考慮沒有重疊的區間的貢獻相加(增加0的區間來實現)。
\(f_i\)為數字i的區間的最大貢獻,\(dp_j\)為數字i區間內部的每張牌的貢獻的字首和。
狀態轉移方程為:
\(dp_j\)=\(dp_{j-1}\)+i
\(dp_{r[a[j]]}\)=\(dp_{j-1}\)+\(f_{a[j]}\) (如果\(j=l[a[j]]\)並且\(r[a[j]]<r[i]\)

程式碼:

#include <bits/stdc++.h>
using namespace std ;
const int MAXN=1e5+7;
const int INF = 0x3f3f3f3f;
typedef long long ll;
#define endl "\n"

void solve(){
    int n;
    cin>>n;
    int a[2*n+1];
    for(int i=1;i<=2*n;i++){
        cin>>a[i];
    }
    vector<int > l(n+1,-1),r(n+1);
    for(int i=1;i<=2*n;i++){
        if(l[a[i]]==-1){
            l[a[i]]=i;
        }else{
            r[a[i]]=i;
        }
    }
    l[0]=0;
    r[0]=2*n+1;
    vector<int > f(n+1,0);
    for(int i=n;i>=0;i--){
        vector<int > dp(2*n+1,0);
        for(int j=l[i]+1;j<r[i];j++){
            dp[j]=max(dp[j],dp[j-1]+i);
            if(j==l[a[j]]&&r[a[j]]<r[i]){
                dp[r[a[j]]]=max(dp[r[a[j]]],dp[j-1]+f[a[j]]);
            }
        }
        f[i]=2*i+dp[r[i]-1];
    }
    cout<<f[0]<<endl;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);

    int t=1;
    
    // cin>>t;
    while(t--){
        solve();
    }
    
    return 0;
}

相關文章