Codeforces Round 972 (Div. 2) 補題記錄(A~C,E1)

yhbqwq發表於2024-09-16

dp round again

A

發現構造若干個 \(a\) 然後接若干個 \(e\) 接若干個 \(i\) 接若干個 \(o\) 再接若干個 \(u\) 且讓這些字母的出現次數儘量相等最優。直接構造時間複雜度為 \(O(n)\)

void solve(unsigned __testid=1){
    int n;
    cin>>n;
    F(i,0,4){
        int cnt=n/5;
        if(n%5>i)++cnt;
        F(j,1,cnt){
            if(i==1)putchar('e');
            if(i==2)putchar('i');
            if(i==3)putchar('o');
            if(i==4)putchar('u');
            if(!i)putchar('a');
        }
    }
    cout<<'\n';
}

B1

分類討論 \(c\)\(b_1\) 左、\(b_1\)\(b_2\) 之間,\(b_2\) 右三種情況討論。

void solve(unsigned __testid=1){
    int n,m,q;cin>>n>>m>>q;
    int b1,b2;cin>>b1>>b2;int c;cin>>c;
    if(b1>b2)swap(b1,b2);
    if(b1<=c&&c<=b2)cout<<(b2-b1)/2<<'\n';
    else if(c<b1)cout<<b1-1<<'\n';
    else cout<<n-b2<<'\n';
}

B2

在 B1 的基礎上加一個二分求出 \(c\)\(b\) 的哪一段區間即可。時間複雜度為 \(O(n+q\log n)\)

int a[N],b[N];
void solve(unsigned __testid=1){
    int n,m,q;cin>>n>>m>>q;
    F(i,1,m)cin>>b[i];
    sort(b+1,b+m+1);
    F(i,1,q){
        int x;cin>>x;
        int l=1,r=m,best=-1;
        if(x<b[1])cout<<b[1]-1<<'\n';
        else if(x>b[m])cout<<n-b[m]<<'\n';
        else{
            while(l<=r){
                int mid=l+r>>1;
                if(b[mid]>=x)best=mid,r=mid-1;
                else l=mid+1;
            }
            assert(~best);
            // best-1 ~ best
            cout<<(b[best]-b[best-1])/2<<'\n';
        }
    }
}

C

咚咚咚

一個十分複雜的做法。

\(f_{i,0/1/2/3/4}\) 表示第 \(i\) 個字串以 \(n/a/r/e/k\) 開頭最多可以獲得多少得分。

首先可以發現字串 \(s_i\) 中不是 \(n/a/r/e/k\) 的字元對答案毫無貢獻,因此先刪去。

然後進入 dp。首先設 \(f_{0,4}=0\) 為初始狀態。

然後考慮做轉移方程。

對於每一個字串 \(s_i\) 和其開始字元 \(j\),都暴力掃描字串 \(s_i\) 得到最後一個出現的位置 \(p\)。然後用 \(f_{i,p}\) 更新 \(f_{k,\text{Pre}(j)}\)(其中 \(\text{Pre}(j)\) 表示 \(j\) 迴圈的上一個字元是什麼)。令 \(cnt\) 表示當前可以被匹配上的字元數,\(all\) 表示為 \(n/a/r/e/k\) 但是當前不能被匹配上的字元數。更新的時候:

  • 考慮 \(\text{Pre}(j)=4\),此時可以直接從這個字串打頭,此時這個字串對答案的貢獻為 \(\lfloor\frac{cnt}{5}\rfloor\times 5-cnt\bmod 5-all\)
  • 列舉上一個被選擇的字串 \(s_k\),此時對答案的貢獻為:
    • \(s_i\)\(s_k\) 的基礎上開闢了一個新的迴圈,那麼:
      • \(\text{Pre}(j)=4\),則此時 \(s_k\) 最後一個迴圈已經被匹配,因此對答案的貢獻為 \(0\)
      • \(\text{Pre}(j)\neq 4\),則此時 \(s_k\) 最後一個迴圈沒有被匹配,計算該迴圈剩下的未匹配字元數量 \(4-\text{Pre}(j)\),其就是對答案的貢獻。
    • 否則,因為還在上一個迴圈裡所以對答案沒有貢獻。
    • 計算 \(s_k\) 所屬完整迴圈節數量 \(\lfloor\frac{cnt-4+\text{Pre}(j)}{5}\rfloor\times 5\) 也是對答案的貢獻。
    • 剩下的 \((cnt-4+\text{Pre}(j))\bmod 5\) 即為下一個迴圈但是沒有被 \(narek\) 字串所完全覆蓋的長度,對答案的貢獻為 \(-((cnt-4+\text{Pre}(j))\bmod 5)\)

最後的答案即為 \(\max f_{i,j}\)

考慮依據上述轉移方程暴力轉移,時間複雜度為 \(O(n^2)\)

void solve(unsigned __testid=1){
    // freopen(".out","w",stdout);
    int n,k;cin>>n>>k;
    F(i,1,n)cin>>s[i];
    F(i,1,n){
        string t;
        for(auto &j:s[i])
            if(j=='n'||j=='a'||j=='r'||j=='e'||j=='k')
                t+=j;
        s[i]=t;
    }
    F(i,1,n){
        len[i]=s[i].size();
        s[i]=' '+s[i];
    }
    F(i,1,n)
        F(j,0,4)
            f[i][j]=-1e18;
    int mx=0;
    f[0][4]=0;
    F(i,1,n){
        char sp[]={'n','a','r','e','k'};
        F(j,0,4){
            int cnt=0,all=0;
            int now=j;
            F(k,1,len[i]){
                if(s[i][k]==sp[now]){
                    now=(now+1)%5;
                    ++cnt;
                }else ++all;
            }
            if(!cnt)continue;
            // cout<<"i=" <<i<<" then j can be "<<j<<", "<<cnt<<' '<<all<<'\n';
            int pre=now-1;
            if(pre<0)pre+=5;
            now=pre;
            pre=j-1;
            if(pre<0)pre+=5;
            if(pre==4)f[i][now]=max({cnt/5*5-all-cnt%5,f[i][now]});
            F(k,1,i-1){
                int cost=f[k][pre];
                int cntt=cnt;
                if(cnt<4-pre)cost-=cnt+all;
                else{
                    if(pre!=4){
                        cnt-=(4-pre);
                        cost+=5-all;
                        cost+=pre+1;
                    }else cost-=all;
                    cost+=cnt/5*5;
                    cost-=cnt%5;
                }
                cnt=cntt;
                // cout<<"WORI "<<k<<' '<<cnt<<' '<<cost<<' '<<now<<' '<<pre<<' '<<f[k][pre]<<" COMPARE "<<(cnt<4-pre)<<'\n';
                f[i][now]=max(f[i][now],cost);
            }
        }
        F(j,0,4)mx=max(mx,f[i][j]);
        // cout<<"qwq "<<i<<' ';
        // F(j,0,4)cout<<f[i][j]<<' ';
        // cout<<'\n';
    }
    cout<<mx<<'\n';
}

D

咕咕咕。

E1

一個很 trival 的思路是設 \(f_{i,j,k}\) 表示當前匹配到 \(a\) 中第 \(i\) 個字元,選擇到 \((j,k)\) 位置是否有必勝策略。

那麼考慮列舉 \((p,q)\) 位置表示列舉第 \(i+1\) 個位置選擇 \((p,q)\) 是否有必勝策略。那麼 \(f_{i,j,k}\)\(0\) 當且僅當所有合法的 \((p,q)\) 均滿足 \(f_{i+1,p,q}=1\),否則 \(f_{i,j,k}\)\(1\)

時間複雜度為 \(O(n^5)\),但是發現每一次 \(O(n^2)\) 在連續矩陣中找答案所以考慮用 2D-字首和最佳化,時間複雜度最佳化為 \(O(n^3)\) 可以透過。

比 C 簡單不知道多少。

int f[310][310][310],b[310][310],a[310310];
void solve(unsigned __testid=1){
    int l,n,m;cin>>l>>n>>m;
    F(i,1,l)cin>>a[i];
    F(i,1,n)F(j,1,m)cin>>b[i][j];
    F(i,0,l+1)F(j,0,n+1)F(k,0,m+1)f[i][j][k]=0;
    G(i,l,1)G(j,n,1)G(k,m,1){
        if(a[i]==b[j][k])f[i][j][k]|=!f[i+1][j+1][k+1];
        f[i][j][k]=f[i][j][k]+f[i][j+1][k]+f[i][j][k+1]-f[i][j+1][k+1];
    }
    if(f[1][1][1])cout<<"T\n";
    else cout<<"N\n";
}

E2

咕咕咕。

相關文章