Codeforces Round 972 (Div. 2) 补题记录(A~C,E1)

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

咕咕咕。

posted @ 2024-09-16 22:34  yhbqwq  阅读(27)  评论(0编辑  收藏  举报