Codeforces Round #854 by cybercats (Div. 1 + Div. 2) 题解

Codeforces Round #854 by cybercats (Div. 1 + Div. 2) 题解

孩子不懂事,写着玩的。

CF1799A - Recent Actions

对于加入的编号范围 \([n+1,n+m]\) 的帖子,这些帖子第一次加入的时候,会挤掉一个原有的编号在 \([1,n]\) 的帖子,否则只是顺序变动。同时因为编号在 \([1,n]\) 的帖子没有更新过,所以也一直在最近发布栏目的最底端,不会有其他帖子先挤出去的情况。于是只需要维护哪些帖子已经被加入了就可以了。

#include<bits/stdc++.h>
using namespace std;

void mian(){
    int n,m;
    cin>>n>>m;
    set<int> s;
    stack<int> t;
    for(int i=1;i<=m;i++){
        int x;
        cin>>x;
        if(!s.count(x)){
            t.emplace(i);
            s.emplace(x);
        }
    }
    for(int i=n;i>=1;i--){
        while(t.size()>i)t.pop();
        cout<<(t.size()==i?t.top():-1)<<' ';
    }
    cout<<'\n';
}

int main(){

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int T;
    cin>>T;
    while(T--)mian();

    return 0;
}

CF1799B - Equalize by Divide

首先序列里有 \(1\) 然后不全是 \(1\) 就无解,唯一的一种无解。

每次找到最小的一个数,用它去除一个次小的和它不相等的数(防止出现 \(1\))。结果分三种情况:

  1. 结果还是比最小数大,这个情况比较平凡。
  2. 结果和最小数相同,这个情况就不需要你再操作它了,除非最小数变了。
  3. 结果比最小数小,这样就用这个新的最小数去除其他数。

因为一次操作至少会让被除数减半,所以操作数不太多。

最后一定会是全部相同,因为存在不同的数,就可以继续操作,同时操作不会生成 \(1\)

#include<bits/stdc++.h>
using namespace std;

int n,a[105];
vector<pair<int,int>> ans;

void mian(){
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    if(*min_element(a+1,a+1+n)==1){
        cout<<(*max_element(a+1,a+1+n)==1?0:-1)<<'\n';
        return;
    }
    ans.clear();
    while(*min_element(a+1,a+1+n)!=*max_element(a+1,a+1+n)){
        int p=min_element(a+1,a+1+n)-a,q=-1;
        for(int i=1;i<=n;i++)if(a[i]>a[p]){
            if(q==-1||a[q]>a[i])q=i;
        }
        ans.emplace_back(q,p);
        a[q]=(a[q]+a[p]-1)/a[p];
    }
    cout<<ans.size()<<'\n';
    for(auto &[i,j]:ans)cout<<i<<' '<<j<<'\n';
}

int main(){

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int T;
    cin>>T;
    while(T--)mian();

    return 0;
}

CF1799C - Double Lexicographically Minimum

看上去比较像直接构造 \(t_{\max}\) 而不是 \(t\)

首先从小到大考虑填进去的字母。先左右成对从字符串两侧向中间塞入,直到有某一种字母剩下来一个。

如果只剩一个这种字母,和另一种字母,可以把这个字母塞在字符串最中间。

否则一定是放在一侧,剩下的空间升序填放剩下的字母。

#include<bits/stdc++.h>
using namespace std;

int n,a[26];
char s[100005];

void mian(){
    cin>>s;
    n=strlen(s);
    for(int i=0;i<n;i++)a[s[i]-'a']++;
    int b=0;
    for(int i=0;i<26;i++){
        for(int j=b;j<b+a[i]/2;j++)s[j]=i+'a';
        for(int j=n-1-b;j>n-1-b-a[i]/2;j--)s[j]=i+'a';
        b+=a[i]/2;
        a[i]=a[i]&1;
        if(a[i]){
            a[i]=0;
            int lft=0;
            for(int j=i+1;j<26;j++)lft+=!!a[j];
            if(lft!=1){
                s[n-1-b]=i+'a';
                for(int j=b,k=i+1;j<n-1-b;j++){
                    while(!a[k])k++;
                    s[j]=k+'a';
                    a[k]--;
                }
            }else{
                s[n/2]=i+'a';
                for(int j=b,k=i+1;j<=n-1-b;j++)if(j!=n/2){
                    while(!a[k])k++;
                    s[j]=k+'a';
                    a[k]--;
                }
            }
            break;
        }
    }
    cout<<s<<'\n';
}

int main(){

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int T;
    cin>>T;
    while(T--)mian();

    return 0;
}

CF1799D - Hot Start Up

两次相同的任务连续在同一块处理器上处理时,可以看作在这两个任务中间,这块处理器保留了预处理的数据。

双路处理器的限制,意味着同一时间只能有一份数据被保留。

这样相同的任务中,邻近的两个就可以抽象出一条带权线段。要求的就是最大权不相交线段集合。

要特判连续的相同任务,它们不要求抽象出的退化成点的线段不和其他线段相交。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

int n,k,a[300005],b[300005],c[300005],h[300005];
pair<int,int> seg[300005];
ll dp[300005];

void mian(){
    cin>>n>>k;
    ll ans=0;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    for(int i=1;i<=k;i++){
        cin>>c[i];
    }
    for(int i=1;i<=k;i++){
        cin>>h[i];
    }
    fill(b+1,b+1+k,0);
    for(int i=1;i<=n;i++){
        ans+=(a[i]==a[i-1]?h:c)[a[i]];
        seg[i]={-1,-1};
        if(a[i]!=a[i-1]&&b[a[i]]){
            seg[i-1]={b[a[i]],c[a[i]]-h[a[i]]};
        }
        b[a[i]]=i;
    }
    for(int i=1;i<=n;i++){
        dp[i]=dp[i-1];
        if(seg[i].first!=-1)dp[i]=max(dp[i],dp[seg[i].first]+seg[i].second);
    }
    cout<<ans-dp[n]<<'\n';
}

int main(){

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int T;
    cin>>T;
    while(T--)mian();

    return 0;
}

Cf1799E - City Union

首先考虑满足两城市之间只经过城市的最短路径长等于曼哈顿距离的要求,和任意同行同列的城市之间的路径必须都是城市是等价的。

先用上面的道理预处理整张图,可以通过把行列看成结点,原图格点看作连边来类似 BFS 的流程扩展城市。

之后如果已经是一个连通块就退出,否则一定是两个连通块,同时能在某个点画一个直角坐标系,一个在第一象限一个在第三象限,或者一个在第二象限一个在第四象限。

因为一定存在最优方案,其中这个虚拟原点建立了城市,所以我们就当它上面有城市,把两个城市群无论如何都会因为它而存在的城市先扩展一下。类似这样子:

...#..    ...#..
...###    ...###
.....# => ...###
.##...    .##...
.#....    .#....

然后对这两个城市群,一个群作为起点,找到另一个的最短路,涂成城市,就做完了。

#include<bits/stdc++.h>
using namespace std;

int n,m;
char g[55][55];

void flood(){
    queue<pair<int,int>> q;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(g[i][j]=='#'){
                q.emplace(0,i);
                break;
            }
        }
    }
    for(int j=1;j<=m;j++){
        for(int i=1;i<=n;i++){
            if(g[i][j]=='#'){
                q.emplace(1,j);
                break;
            }
        }
    }
    while(!q.empty()){
        int mo,id;
        tie(mo,id)=q.front();
        q.pop();
        if(mo){
            int l=-1,r;
            for(int i=1;i<=n;i++){
                if(g[i][id]=='#'){
                    r=i;
                    if(l==-1)l=i;
                }
            }
            for(int i=l;i<=r;i++)if(g[i][id]=='.'){
                g[i][id]='#';
                q.emplace(0,i);
            }
        }else{
            int l=-1,r;
            for(int j=1;j<=m;j++){
                if(g[id][j]=='#'){
                    r=j;
                    if(l==-1)l=j;
                }
            }
            for(int j=l;j<=r;j++)if(g[id][j]=='.'){
                g[id][j]='#';
                q.emplace(1,j);
            }
        }
    }
}

bool in(int x,int y){
    return x>=1&&y>=1&&x<=n&&y<=m;
}

int dx[]={-1,0,1,0},dy[]={0,1,0,-1},vis[55][55];

void bfs(int sx,int sy,int col){
    queue<pair<int,int>> q;
    q.emplace(sx,sy);
    vis[sx][sy]=col;
    while(!q.empty()){
        int x,y;
        tie(x,y)=q.front();
        q.pop();
        for(int d=0;d<4;d++){
            int nx=dx[d]+x,ny=dy[d]+y;
            if(in(nx,ny)&&g[nx][ny]=='#'&&!vis[nx][ny]){
                vis[nx][ny]=col;
                q.emplace(nx,ny);
            }
        }
    }
}

int dis[55][55];

void get(){
    queue<pair<int,int>> q;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(vis[i][j]==1){
                q.emplace(i,j);
                dis[i][j]=0;
            }
        }
    }
    while(!q.empty()){
        int x,y;
        tie(x,y)=q.front();
        q.pop();
        for(int d=0;d<4;d++){
            int nx=dx[d]+x,ny=dy[d]+y;
            if(in(nx,ny)&&dis[nx][ny]==-1){
                dis[nx][ny]=dis[x][y]+1;
                q.emplace(nx,ny);
            }
        }
    }
}

void mian(){
    memset(vis,0,sizeof(vis));
    memset(dis,-1,sizeof(dis));
    memset(g,0,sizeof(g));
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>g[i];
        memmove(g[i]+1,g[i],m);
    }
    flood();
    vector<pair<int,int>> cc;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(g[i][j]=='#'&&!vis[i][j]){
                bfs(i,j,cc.size()+1);
                cc.emplace_back(i,j);
            }
        }
    }
    if(cc.size()==2){
        int dn=1,up=n,v1,v2;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(vis[i][j]==1){
                    dn=max(dn,i);
                    v1=j;
                }
                if(vis[i][j]==2){
                    up=min(up,i);
                    v2=j;
                }
            }
        }
        if(v1<v2){
            for(int i=1;i<dn;i++){
                for(int j=1;j<=m;j++){
                    if(vis[i][j]==1&&vis[i+1][j-1]==1){
                        g[i+1][j]='#';
                        vis[i+1][j]=1;
                    }
                }
            }
            for(int i=n;i>up;i--){
                for(int j=m;j>=1;j--){
                    if(vis[i][j]==2&&vis[i-1][j+1]==2){
                        g[i-1][j]='#';
                        vis[i-1][j]=2;
                    }
                }
            }
        }else{
            for(int i=1;i<dn;i++){
                for(int j=m;j>=1;j--){
                    if(vis[i][j]==1&&vis[i+1][j+1]==1){
                        g[i+1][j]='#';
                        vis[i+1][j]=1;
                    }
                }
            }
            for(int i=n;i>up;i--){
                for(int j=1;j<=m;j++){
                    if(vis[i][j]==2&&vis[i-1][j-1]==2){
                        g[i-1][j]='#';
                        vis[i-1][j]=2;
                    }
                }
            }
        }
        get();
        int x=-1,y=-1;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++)if(vis[i][j]==2){
                if(x==-1||dis[x][y]>dis[i][j]){
                    x=i;
                    y=j;
                }
            }
        }
        while(dis[x][y]){
            for(int d=0;d<4;d++){
                int nx=dx[d]+x,ny=dy[d]+y;
                if(in(nx,ny)&&dis[nx][ny]+1==dis[x][y]){
                    x=nx;
                    y=ny;
                    break;
                }
            }
            g[x][y]='#';
        }
    }
    for(int i=1;i<=n;i++)cout<<g[i]+1<<'\n';
}

int main(){

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int T;
    cin>>T;
    while(T--)mian();

    return 0;
}

CF1799F - Halve or Subtract

首先我们把数字升序排序。同时对一个数字我们先操作除法再操作减法。

容易发现如果对第 \(i\) 个数字操作了而没有操作第 \(j\) 个数字 \((i<j)\),那么把给第 \(i\) 个数字的操作给到第 \(j\) 个一定更好。那么操作的数字一定是一段后缀。

同理,如果对第 \(i\) 个数字使用了两种操作,而第 \(j\) 个数字使用了一种 \((i<j)\),也是把给第 \(i\) 个数字的操作给到第 \(j\) 个一定更好。那么操作两次的数字一定是一段后缀。

我们枚举操作两次的数字,同时对于前面的一段剩下的数字,先考虑都是做除法操作,记录下改为减法操作的代价差值,选择最优的一些改为减法操作就可以构造一种方案。取最优的即可。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

int n,b,k1,k2,a[5005];

void mian(){
    cin>>n>>b>>k1>>k2;
    ll tot=0;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        tot+=a[i];
    }
    sort(a+1,a+1+n);
    ll ans=0;
    for(int bo=0;bo<=k1&&bo<=k2;bo++)if(k1-bo+k2-bo+bo<=n){
        ll sum=0;
        for(int i=n;i>n-bo;i--){
            sum+=min(a[i],a[i]/2+b);
        }
        static vector<int> v;
        v.clear();
        int len=k1-bo+k2-bo;
        for(int i=n-bo;i>n-bo-len;i--){
            sum+=a[i]/2;
            v.emplace_back(min(a[i],b)-a[i]/2);
        }
        sort(v.begin(),v.end());
        reverse(v.begin(),v.end());
        for(int i=0;i<k2-bo;i++)sum+=v[i];
        ans=max(ans,sum);
    }
    cout<<tot-ans<<'\n';
}

int main(){

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int T;
    cin>>T;
    while(T--)mian();

    return 0;
}

CF1799G - Count Voting

首先转化一下,预处理 \(a_i\) 表示队伍 \(i\) 需要几票,\(b_i\) 表示队伍 \(i\) 有几人。

定义 \(f_{i,j}\) 表示前 \(i\) 个队伍已经分配了应得的票,这些队伍中投出了 \(j\) 票给这前 \(i\) 个队伍。

这样我们转移时枚举当前队伍投给前面的队伍中的票数 \(k\),和从前面的队伍中拿走的票数 \(l\)

\[f_{i,j+k+l} := f_{i,j+k+l} + f_{i-1,j} \binom {b_i} k \binom {\sum_{p<i} a_p - j} k k! \binom {a_i} l \binom {\sum_{p<i} b_p - j} l l! \]

后面两个较为对称的组合数,第一个代表在当前的票中选 \(k\) 个位置向前投,选择要投的 \(k\) 个位置,和对应关系的排列,第二个是类似的。

这样我们计算的方案数中每个人的得票的位置是可以相互区分的,所以还要除以 \(\prod_i c_i!\)

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int mod=998244353;
const int precLen=200;

ll qpow(ll a,ll n){
    ll res=1;
    while(n){
        if(n&1)res=res*a%mod;
        a=a*a%mod;
        n>>=1;
    }
    return res;
}

int fact[precLen+5],finv[precLen+5];

ll binom(int n,int r){
    return (ll)fact[n]*finv[n-r]%mod*finv[r]%mod;
}

void initialization(){
    fact[0]=1;
    for(int i=1;i<=precLen;i++){
        fact[i]=(ll)fact[i-1]*i%mod;
    }
    finv[precLen]=qpow(fact[precLen],mod-2);
    for(int i=precLen-1;i>=0;i--){
        finv[i]=(ll)finv[i+1]*(i+1)%mod;
    }
}

int n,c[205],t[205],a[205],b[205],f[205][205];

int main(){

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    initialization();
    cin>>n;
    f[0][0]=1;
    for(int i=1;i<=n;i++){
        cin>>c[i];
        f[0][0]=(ll)f[0][0]*finv[c[i]]%mod;
    }
    for(int i=1;i<=n;i++){
        cin>>t[i];
        a[t[i]]+=c[i];
        b[t[i]]++;
    }
    for(int i=1,pa=0,pb=0;i<=n;i++){
        for(int j=0;j<=pb;j++){
            for(int k=0;k<=b[i]&&k+j<=pa;k++){
                for(int l=0;l<=a[i]&&l+j<=pb;l++){
                    f[i][j+k+l]=(f[i][j+k+l]+f[i-1][j]*binom(b[i],k)%mod*binom(pa-j,k)%mod*fact[k]%mod*binom(pb-j,l)%mod*binom(a[i],l)%mod*fact[l])%mod;
                }
            }
        }
        pa+=a[i];
        pb+=b[i];
    }
    cout<<f[n][n]<<'\n';

    return 0;
}

CF1799H - Tree Cutting

考虑以每个点为根,它最终没有被剪掉的方案有多少个。由于最终子树大小恒定,只需要除以最终子树大小,就得到了答案。

同时我们差分题目给出的每次剪枝后剩下的子树大小的序列,就变成要求每次剪掉一个给定大小的子树。

这样我们考虑一个树上 DP,\(f_{i,s}\) 表示强制结点 \(i\) 被从它的父亲剪掉后,这个子树内已经做了子集 \(s\) 内的剪枝操作的方案数。这是由于各个子树间,做的剪枝操作互不影响才能实现的。类似的定义 \(h_{i,s}\) 表示强制不剪掉的方案数,\(dp_{i,s}\) 表示不强制剪不剪掉的方案数。

辅助理解的图片

因为我表述不太清楚,以上是一张辅助理解的图片。

这样 \(h_{i,s}\) 就是枚举它每个儿子所处理的剪枝操作集合是什么,并求它们的积。特别地,叶子结点只有 \(h_{i,\empty}=1\)

\(f_{i,s}\) 就是对于 \(h_{i,s}\) 中的每个集合,再枚举一个出现在集合内部操作之后的剪枝操作,查看是否合法然后添加进去。

\(dp\) 就是对以上两个简单求和。

需要应用换根 DP 求出每个点为根的答案。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int mod=998244353;

ll qpow(ll a,ll n){
    ll res=1;
    while(n){
        if(n&1)res=res*a%mod;
        a=a*a%mod;
        n>>=1;
    }
    return res;
}

int n,m,cut[10],ctot[1<<6],sz[5005],f[5005][1<<6],h[5005][1<<6],dp[5005][1<<6],ans;
vector<int> g[5005];

void getsum(int *x,int *y,int *z){
    memset(z,0,4<<m);
    for(int s=0;s<1<<m;s++){
        z[s]=(x[s]+y[s])%mod;
    }
}

void combine(int *x,int *y,int *z){
    memset(z,0,4<<m);
    for(int s=0;s<1<<m;s++){
        for(int t=(1<<m)-1^s;t;t=t-1&((1<<m)-1^s)){
            z[s|t]=(z[s|t]+(ll)x[s]*y[t])%mod;
        }
        z[s]=(z[s]+(ll)x[s]*y[0])%mod;
    }
}

void cutit(int *x,int *y,int S){
    memset(y,0,4<<m);
    for(int s=0;s<1<<m;s++){
        for(int i=0;i<m;i++)if(1<<i>s&&S==ctot[s]+cut[i]){
            y[s|1<<i]=(y[s|1<<i]+x[s])%mod;
        }
    }
}

void dfs(int x,int p){
    sz[x]=1;
    h[x][0]=1;
    for(int y:g[x])if(y!=p){
        dfs(y,x);
        sz[x]+=sz[y];
        static int tmp[1<<6];
        combine(h[x],dp[y],tmp);
        memcpy(h[x],tmp,4<<m);
    }
    cutit(h[x],f[x],sz[x]);
    getsum(f[x],h[x],dp[x]);
}

void get(int x,int p,int *fa){
    for(int i=0;i<g[x].size();i++){
        if(g[x][i]==p){
            g[x].erase(g[x].begin()+i);
            break;
        }
    }
    vector<int[1<<6]> pre(g[x].size()),suf(g[x].size());
    for(int i=0;i<g[x].size();i++){
        if(!i){
            memset(pre[i],0,4<<m);
            pre[i][0]=1;
        }else{
            combine(pre[i-1],dp[g[x][i-1]],pre[i]);
        }
    }
    for(int i=g[x].size()-1;i>=0;i--){
        if(i+1==g[x].size()){
            memset(suf[i],0,4<<m);
            suf[i][0]=1;
        }else{
            combine(suf[i+1],dp[g[x][i+1]],suf[i]);
        }
    }
    for(int i=0;i<g[x].size();i++){
        int t1[1<<6],t2[1<<6],t3[1<<6];
        combine(pre[i],suf[i],t1);
        combine(t1,fa,t2);
        cutit(t2,t1,n-sz[g[x][i]]);
        getsum(t2,t1,t3);
        get(g[x][i],x,t3);
    }
    static int tmp[1<<6];
    combine(fa,h[x],tmp);
    ans=(ans+tmp[(1<<m)-1])%mod;
}

int main(){

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    cin>>n;
    for(int i=1;i<n;i++){
        int u,v;
        cin>>u>>v;
        g[u].emplace_back(v);
        g[v].emplace_back(u);
    }
    cin>>m;
    for(int i=0;i<m;i++)cin>>cut[i];
    cut[m]=cut[m-1];
    for(int i=m-1;i>=1;i--)cut[i]=cut[i-1]-cut[i];
    cut[0]=n-cut[0];
    for(int s=1;s<1<<m;s++){
        int i=0;while(s>>i&1^1)i++;
        ctot[s]=ctot[s^1<<i]+cut[i];
    }
    dfs(1,-1);
    static int tmp[1<<6];
    tmp[0]=1;
    get(1,-1,tmp);
    cout<<ans*qpow(cut[m],mod-2)%mod<<'\n';

    return 0;
}
posted @ 2023-02-28 19:53  筵阑  阅读(441)  评论(0)    收藏  举报