$Educational Codeforces Round 138 (Rated for Div. 2)$

发布时间 2023-09-07 22:01:50作者: EdGrass

\(A. Cowardly Rooks\)

我模拟了一遍,因为我没看到题目中给出的矩阵已经合法\(a\) 题第一次写这么多。
实验室同事跟我说只要判断 \(n\)\(m\) 的大小关系就行了。

int x[N],y[N],m,n;
bool check(){
    for(int i=1;i<=m;i++){
        for(int j=1;j<=m;j++){
            if(i==j)continue;
            if(x[i]==x[j]||y[i]==y[j])return false;
        }
    }
    return true;
}
void solve(){
    n=read(),m=read();
    int ans=0;
    for(int i=1;i<=m;i++){
        x[i]=read();
        y[i]=read();
    }
    for(int i=1;i<=m;i++){
        int tx=x[i],ty=y[i];
        for(int j=1;j<=n;j++){
            if(j==ty)continue;
            x[i]=tx;
            y[i]=j;
            if(check())ans=1;
        }
        for(int j=1;j<=n;j++){
            if(j==tx)continue;
            x[i]=j;
            y[i]=ty;
            if(check())ans=1;
        }
        x[i]=tx;
        y[i]=ty;
    }
    puts(ans==1?"YES":"NO");
    //puts(ans>0?"Yes":"No");
}

\(B. Death's Blessing\)

看了下题,感觉每次删掉前面和后面更小的一个就行了。
要从中间删会被计算两次,而头尾只会被计算一次。因为最后只会剩下一个数,所以可以让最大的数留下来。

void solve(){
    int n=read(),sum=0;
    for(int i=1;i<=n;i++){
        sum+=read();
    }
    deque<int>a;
    for(int i=1;i<=n;i++){
        a.push_back(read());
    }
    for(int i=1;i<n;i++){
        if(a.front()<=a.back()){
            sum+=a.front();
            a.pop_front();
        }else{
            sum+=a.back();
            a.pop_back();
        }
    }
    cout<<sum<<'\n';
    //puts(ans>0?"YES":"NO");
    //puts(ans>0?"Yes":"No");
}

\(C. Number Game\)

本题可以用二分答案,在 \(check\) 里面用 \(multiset\) 模拟一遍。但是数据太小可以直接枚举。

void solve(){
    int n=read(),ans=0;
    multiset<int>st;
    for(int i=1;i<=n;i++){
        st.insert(read());
    }
    for(int k=1;k<=n;k++){
        multiset<int>tp(st);
        int ok=1;
        for(int i=1;i<=k;i++){
            int x=k-i+1;
            while(tp.upper_bound(x)!=tp.end()) 
                tp.erase(tp.upper_bound(x));
            if(tp.size()==0){
                ok=0;
                break;
            }
            auto it=tp.upper_bound(x);
            it--;
            tp.erase(it);
            if(tp.size()){
                tp.erase(tp.begin());
            }
            x--;
        }
        if(ok)ans=k;
    }
    cout<<ans<<'\n';
    //puts(ans>0?"YES":"NO");
    //puts(ans>0?"Yes":"No");
}

\(D. Counting Arrays\)

思考什么样的数列会是 \(ambiguous\) 的,显然是后面的数字与 \(i\) 的连积的 \(gcd\)\(1\)。那么明显的非 \(ambiguous\) 数字更好求,每次算当前位有多少种方案是连积的倍数,然后容斥减掉。

int primes[N], cnt;
bool st[N];    
void get_primes(int n){
    for (int i = 2; i <= n; i ++ ){
        if (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; primes[j] <= n / i; j ++ ){
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break;
        }
    }
}
int fac[N];
void init(){
    get_primes(N-1);
    fac[1]=1;
    for(int i=2;i<N;i++){
        fac[i]=fac[i-1];
        if(!st[i])fac[i]*=i;
        // fac[i]=mod;
    }
}
void solve(){
    int n=read(),m=read();
    mint tol=0,ans=1,sum=1;
    for(int i=1;i<=n;i++){
        sum*=m;
        ans*=(m/fac[i]);
        tol+=sum-ans;
    }
    cout<<tol<<'\n';
    //puts(ans>0?"YES":"NO");
    //puts(ans>0?"Yes":"No");
}

\(E. Cactus Wall\)

那么转换题意,就是要求一条从左边到右边可以横穿的仙人掌路,而且路还得斜着走。那么对于已经有仙人掌的点点权记为 \(0\) ,对于没有的点点权记为 \(1\) ,这样就是求一个从左端到右端的最短路,可以在 \(01\) 图上跑双端 \(bfs\)

struct node{
    int x,y;
};
const int dx[]={0,0,1,-1,1,1,-1,-1};
const int dy[]={1,-1,0,0,1,-1,1,-1};
deque<node>q;
vector<int>pre[N],d[N];
string mp[N];
int n,m;
bool check(int x,int y){
    if(x<0||x>=n||y<0||y>=m)return false;
    for(int i=0;i<4;i++){
        int xx=x+dx[i],yy=y+dy[i];
        if(xx>=0&&xx<n&&yy>=0&&yy<m&&mp[xx][yy]=='#')return false;
    }
    return true;
}
void bfs(){
    while(!q.empty()){
        int x=q.front().x,y=q.front().y;
        q.pop_front();
        for(int i=4;i<8;i++){
            int xx=x+dx[i],yy=y+dy[i]; 
            if(!check(xx,yy))continue;
            int s=(mp[xx][yy]=='.');
            if(d[xx][yy]>d[x][y]+s){
                d[xx][yy]=d[x][y]+s;
                pre[xx][yy]=i;
                if(s)q.push_back((node){xx,yy});
                else q.push_front((node){xx,yy});
            }
        }
    }
}
void solve(){
    n=read(),m=read();
    for(int i=0;i<n;i++){
        cin>>mp[i];
    }
    for(int i=0;i<n;i++){
        pre[i].clear();
        d[i].clear();
        for(int j=0;j<m;j++){
            d[i].push_back(inf);
            pre[i].push_back(0);
        }
        if(mp[i][0]=='#')q.push_front((node){i,0}),d[i][0]=0;
        else if(check(i,0))q.push_back((node){i,0}),d[i][0]=1;
    }
    bfs();
    int minn=inf,mini=-1;
    for(int i=0;i<n;i++){
        if(d[i][m-1]<minn){
            minn=d[i][m-1];
            mini=i;
        }        
    }
    if(mini==-1){
        cout<<"NO\n";
        return ;
    }
    cout<<"YES\n";
    int x=mini,y=m-1;
    while(1){
        mp[x][y]='#';
        if(!pre[x][y])break;
        int lst=pre[x][y];
        x-=dx[lst];y-=dy[lst];
    }
    for(int i=0;i<n;i++){
        cout<<mp[i]<<'\n';
    }
    //puts(ans>0?"YES":"NO");
    //puts(ans>0?"Yes":"No");
}

\(F. Distance to the Path\)

对于修改操作可以先对路径标记,用树状数组 \(+dfs\) 序标记。具体的就是对 \(dfs\) 序上的 \(fenwick[x]\)\(fenwick[y]+d\) ,对 \(fenwick[lca(x,y)]-2*d\) ,然后询问的时候对 \(x\) 入队和出队的地方进行差分。以上只是子树内的处理,而对子树外的点可以归结为 \(lca\) 的贡献,对单点进行暴力修改,记录 \(dp[i][j]\) 表示 \([i]\) 的位置向外 \([j]\) 个距离加上修改值,并且向上转移,每次转移会重复计算下面的点,容斥处理。
子树内修改为 \(O(qlogn)\) ,查询为 \(O(qdlogn)\) 。子树外修改和查询都为为 \(O(qd)\) ,最后的复杂度类似于 \(O(nlog^2n)\)

vector<int>adj[N+5];
int f[N+5][21],n,m;
int son[N+5],siz[N+5],father[N+5],top[N+5],in[N+5],out[N+5],dep[N+5],timer;
struct Fenwick {
    int c[N+5];
    inline int lowbit(int x) {return x&(-x);}
    inline void add(int x,int d) {
        for(;x<=n;x+=lowbit(x))c[x]+=d;
    }
    inline int query(int x) {
        int res=0;
        for(;x>=1;x-=lowbit(x))res+=c[x];
        return res;
    }
    inline int rangequery(int l,int r) {
        return query(r)-query(l-1);
    }
}fen[21];
void dfs1(int u,int fa) {
    siz[u]=1,father[u]=fa,dep[u]=dep[fa]+1;    
    int mx=0;
    for(int v:adj[u]) 
        if(v!=fa) {
            dfs1(v,u);
            if(siz[v]>mx)son[u]=v,mx=siz[v];
            siz[u]+=siz[v];
        }
}
void dfs2(int u,int fa,int now) {
    top[u]=now,in[u]=++timer;
    if(son[u])dfs2(son[u],u,now);
    for(int v:adj[u])
        if(v!=son[u]&&v!=fa)dfs2(v,u,v);
    out[u]=timer;
}
inline int lca(int x,int y) {
    while(top[x]!=top[y]) {
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        x=father[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    return x;
}
inline void modify(int x,int y,int k,int d) {
    int z=lca(x,y);
    fen[k].add(in[x],d);
    fen[k].add(in[y],d);
    fen[k].add(in[z],-2*d);
    for(int i=0;i<=k&&z;i++) {
        f[z][k-i]+=d;
        if(k-i-2>=0&&z!=1)f[z][k-i-2]-=d;
        z=father[z];
    }
}
inline int query(int x) {
    int res=0,dis=0;
    for(int i=0;i<=20&&x;i++) {
        res+=fen[i].rangequery(in[x],out[x]);
        for(int j=dis;j<=20;j++)res+=f[x][j];
        ++dis,x=father[x];
    }
    return res;
}
void solve() {
    n=read();
    for(int i=1;i<n;i++) {  //build edge
        int x=read(),y=read();
        adj[x].push_back(y);
        adj[y].push_back(x);
    }
    dfs1(1,0),dfs2(1,0,1);  //build lca
    m=read();
    while(m--) {   
        int op=read();
        if(op==1) {
            int x=read();
            cout<<query(x)<<'\n';
        }
        else {
            int x=read(),y=read(),d=read(),k=read();    //distance is k   add d
            modify(x,y,k,d);
        }
    }
}