Codeforces Edu 143 补题笔记

[A. Two Towers](Problem - A - Codeforces)

Def

给出长为n,m的两个01栈,可以将栈顶的元素移往另一个栈顶,问是否可以使得两个栈中元素均为01交替

Sol

因为是栈顶,可以知道操作等价于由一边单向向另一边移动,顺着两个方向跑两次判断可行性即可

Code

#include<bits/stdc++.h>
using namespace std;
bool cmp(char *s1,int n,char *s2,int m){
    int fl=1,k=-1;
    for(int i=2;i<=n;i++)
    if(s1[i]==s1[i-1]){
        k=i;
        break ;
    }
    for(int i=2;i<=m;i++)
    if(s2[i]==s2[i-1]) return 0;
    if(k==-1) return 1;
    char c=s2[m];
    for(int i=n;i>=k;i--){
        if(s1[i]==c) return 0;
        c=s1[i];
    }
    return 1;
}
int main(){
    int t,n,m;
    cin>>t;
    char s1[21],s2[21];
    while(t--){
        cin>>n>>m;
        cin>>s1+1>>s2+1;
        if(cmp(s1,n,s2,m)||cmp(s2,m,s1,n)) cout<<"YES\n";
        else cout<<"NO\n";
    }
    return 0;
}

[B. Ideal Point](Problem - B - Codeforces)

Def

给定n组线段和点k,判断能否通过移除线段使得k是占有最多线段的点(\(l\)

Sol

需要考虑的是和k有关的线段,统计每个点的相关量即可

Code

#include<bits/stdc++.h>
using namespace std;
int main(){
    int t,n,k;
    int x,y;
    int f[51];
    cin>>t;
    while(t--){
        memset(f,0,sizeof(f));
        cin>>n>>k;
        for(int i=1;i<=n;i++){
            cin>>x>>y;
            if(x<=k&&y>=k)
            for(int j=x;j<=y;j++) f[j]++;
        }
        int fl=1;
        for(int i=1;i<=50;i++)
        if(i!=k&&f[i]>=f[k]){
            fl=0;
            break ;
        } 
        if(fl) cout<<"YES\n";
        else cout<<"NO\n";
    }
    return 0;
}

[C. Tea Tasting](Problem - C - Codeforces (Unofficial mirror by Menci))

Sol

一杯茶造成的贡献分两种,整段b和一次a-\(\sum b\) ,前缀和后二分查找中间的节点即可

Code

#include<bits/stdc++.h>
using namespace std;
const int nn=2e5+1;
int t,n;
long long sum;
long long a[nn],b[nn],c[nn],d[nn],e[nn];
int mids(int l,int r,long long x,long long *s){
    while(l<r){
        int mid=l+r>>1;
        if(s[mid]>x) r=mid;
        else l=mid+1;
    }
    return r;
}
int main(){
    ios::sync_with_stdio(0);
    cin>>t;
    while(t--){
        cin>>n;
        for(int i=1;i<=n;i++) cin>>a[i],c[i]=0,d[i]=0;
        for(int i=1;i<=n;i++) cin>>b[i],e[i]=b[i],b[i]+=b[i-1];
        b[n+1]=0x3f3f3f3f3f3f3f3f;
        for(int i=1;i<=n;i++){
            int k=mids(1,n+1,a[i]+b[i-1],b);
            c[i]+=1;
            c[k]-=1;
            d[k]+=a[i]+b[i-1]-b[k-1];
        }
        for(int i=1;i<=n;i++){
            c[i]+=c[i-1];
            cout<<c[i]*e[i]+d[i]<<" ";
        } 
        cout<<endl;
    }   
    return 0;
}

[D. Triangle Coloring](Problem - D - Codeforces (Unofficial mirror by Menci))

计数

Sol

每个三角形的染色形式是固定的,称其中连有两条有效边的点为关键点,其中n/6个三角形的关键点是红色,故具体染色的系数为\((^{n/3}_{n/6})\)

Code

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int nn=3e5+5;
const int mod=998244353;
int N;
int W[nn];
ll Jc[nn],InvJc[nn];
int main(){
    scanf("%d",&N);
    Jc[1]=InvJc[1]=InvJc[0]=1ll;
    for(int i=2;i<=N;i++){
        Jc[i]=i*Jc[i-1]%mod;
        InvJc[i]=InvJc[mod%i]*(mod-mod/i)%mod;
    }
    for(int i=2;i<=N;i++) InvJc[i]=InvJc[i]*InvJc[i-1]%mod;
    for(int i=1;i<=N;i++) scanf("%d",&W[i]);
    ll ans=1ll;
    for(int i=1;i<N;i+=3){
        int k=1;
        if((W[i]==W[i+1]&&W[i+2]>W[i])
        ||(W[i]==W[i+2]&&W[i+1]>W[i])
        ||(W[i+1]==W[i+2]&&W[i]>W[i+1])) k=2;
        if(W[i]==W[i+1]&&W[i+1]==W[i+2]) k=3;
        ans=ans*k%mod;
    }
    ll k1=N/6,k2=N/3;
    ll k3=Jc[k2]*InvJc[k1]%mod*InvJc[k2-k1]%mod;
    printf("%lld\n",ans*k3%mod);
    return 0;
}

[E. Explosions?](Problem - E - Codeforces (Unofficial mirror by Menci))

dp #数学

Sol

终结技能打的伤害是一个单峰,考虑左半边,由两段组成
一部分是邻接的由更高伤害降低而来的等差部分,可以用求和公式计算
一部分是本来就小于衰减后伤害的部分,等价于该节点的伤害贡献

Code

#include<bits/stdc++.h>
#define ll long long
//#define debug
using namespace std;
#ifdef debug
const int nn=10;
#endif
#ifndef debug
const int nn=3e5+5;
#endif
int T,N;
int H[nn],B[nn],C[nn],S[nn];
ll F[nn],D[nn];
int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d",&N);
        ll sum=0;
        for(int i=1;i<=N;i++){
            scanf("%d",&H[i]);
            B[i]=H[i]-i;
            C[i]=H[i]+i;
            F[i]=D[i]=0;
            sum+=H[i];
        }
        F[0]=D[N+1]=0;
        int top=0; S[0]=0;
        for(int i=1;i<=N;i++){
            while(top&&B[S[top]]>=B[i]) top--;
            int k=S[top]+1; 
            S[++top]=i;
            if(top==1){ 
                k=max(i-H[i]+1,1);
                F[i]=1ll*(i-k+1)*H[i]-1ll*(i-k)*(i-k+1)/2;
                continue ;
            }
            
            F[i]=max(F[i],F[k-1]
                +1ll*(i-k+1)*H[i]-1ll*(i-k)*(i-k+1)/2);
        }
        top=0; S[0]=N+1; C[N+1]=0;
        for(int i=N;i;i--){
            while(top&&C[S[top]]>=C[i]) top--;
            int k=S[top]-1; 
            S[++top]=i;
            if(top==1){ 
                k=min(i+H[i]-1,N);
                D[i]=1ll*(k-i+1)*H[i]-1ll*(k-i)*(k-i+1)/2;
                continue ;
            }
            
            D[i]=max(D[i],D[k+1]
                +1ll*(k-i+1)*H[i]-1ll*(k-i)*(k-i+1)/2);
        }
        ll ans=0;
        for(int i=1;i<=N;i++){
            ans=max(ans,F[i]+D[i]-H[i]*2);
        }
        ans=sum-ans;
        printf("%lld\n",ans);
    }
    return 0;
}

[F. Blocking Chips](Problem - F - Codeforces (Unofficial mirror by Menci))

贪心 #树

Def

一颗n节点的树,有k个黑色碎片,没有碎片的节点是白色,按顺序移动棋子,可以向相邻白色节点移动,然后会将其染黑,直到没法移动,问移动次数最多是多少

Sol

  • 如何线性处理一颗树上多个节点的顺序移动 ?
  • 将顺序拆开,从终局考虑 -> 二分每个棋子的移动次数
  • 如何判断可行性?
  • 性质:节点不能跨越,所以优先考虑底层节点,发现往下走是不影响上层节点的,而往上走是唯一的,即其状态转移是确定的,对每个碎片记录所需路径长度,子树够就走子树,否则把自己的需求转移给父亲,判可行性即可

Code

#include<bits/stdc++.h>
//#define debug
using namespace std;
#ifdef debug
const int nn=10;
#endif
#ifndef debug
const int nn=2e5+5;
#endif
int T,N,K;
int A[nn];
vector<int>E[nn];
int Nd[nn],Ca[nn];
bool Dfs(int u,int p){
    for(int v:E[u]){
        if(v==p) continue ;
        if(!Dfs(v,u)) return 0; //如果有节点不可行则return
        if(!Nd[v]) Ca[u]=max(Ca[u],Ca[v]+1); //判断最长可行路径,只有子节点不在路当上当
    }
    if(!Nd[u]||Ca[u]>=Nd[u]) return 1; //如果该节点没有需求,或者存在向下路径满足需求
    if(Nd[p]||!p) return 0; //如果祖先已经有别的需求或者没有祖先
    Nd[p]=Nd[u]-1;
    return 1;
}
int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d",&N);
        for(int i=1;i<=N;i++) E[i].clear();
        for(int x,y,i=1;i<N;i++){
            scanf("%d%d",&x,&y);
            E[x].push_back(y);
            E[y].push_back(x);
        }
        scanf("%d",&K);
        for(int i=1;i<=K;i++)
         scanf("%d",&A[i]);
        int l=0,r=N;
        while(l<r){
            int mid=l+r+1>>1;
            //printf("%d %d\n",l,r);
            for(int i=1;i<=N;i++)
             Nd[i]=0,Ca[i]=1; 
            for(int i=1;i<=K;i++)
             Nd[A[i]]=mid/K+(mid%K>=i)+1; //计算碎片的需求路径长度,碎片初始为1因为自身是黑色
            if(Dfs(1,0)) l=mid;
            else r=mid-1;       
        }
        printf("%d\n",l);
    }
    return 0;
}

[G. Removal Sequences](Problem - G - Codeforces (Unofficial mirror by Menci))

图论 #转化 #64状压

Def

n点m边无向图,每个点有权\(a_i\)
每次操作可以将度为\(a_i\)的点i移除,输入保证存在移除所有点的方案
问有多少点对\((x,y)\),存在两个移除方案使得一次x先于y被移除,一次x后于y被移除

Sol

  • 当一个点度数等于点权时,它必须先于其邻点先移除
  • 因为保证了存在解,于是同时能够移除的点必不相邻
  • 且对于每个点的相邻点,它与该点的移除顺序是固定的,例如点对\((u,v)\),若\(u\)先移除,那么它是通过移除非v的节点达到的可移除态,而若v也可以先移除,那么它也可以通过移除非u节点达到可移除态,即这两个节点可以同时达到可移除态,与上一条性质矛盾。
  • 而相邻点间顺序的固定可构成一条依赖关系,而所有点的依赖关系将构成一张DAG图(因为有解),而图中任意可到达的点队的顺序都是固定的
  • 于是题目变为求解 有向图中不可到达的点对数目=\(\frac{n*(n-1)}{2}\)-可到达点对数目,DAG上状压跑即可
  • 但一般的bitset处理容易超时,出题人提供了一种想法:每次跑64个点,计算其影响,时间复杂度为\(O(\frac{n^2}{64})\)

Code

#include<bits/stdc++.h>
//#define debug 
#define ull unsigned long long
using namespace std;
#ifndef debug
const int nn=1e5+5;
#endif
#ifdef debug
const int nn=20;
#endif
int T,N,M;
int A[nn],D[nn];
vector<int>G[nn];
int Sta[nn];

int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&N,&M);
        for(int i=1;i<=N;i++){
            D[i]=0;
            G[i].clear();
            scanf("%d",&A[i]);
        }
        for(int x,y,i=1;i<=M;i++){
            scanf("%d%d",&x,&y);
            G[x].push_back(y);
            G[y].push_back(x);
            D[x]++; D[y]++;
        }
        int hd=0;
        for(int i=1;i<=N;i++)
        if(D[i]==A[i]){
            Sta[++hd]=i;
        }
        vector<ull> s(N+1);
		vector<pair<int,int> >g2;
        while(hd){
            int u=Sta[hd--];
            for(int v:G[u]){
            	if(D[v]<=A[v]) continue ;
                D[v]--;
                g2.push_back({u,v});
                if(D[v]==A[v]){
                    Sta[++hd]=v;
                }
            }
        }
        reverse(g2.begin(),g2.end()); //逆拓扑序
        long long ans=0;
        for(int l=1;l<=N;l+=64){ //每次更新64个
            int r=min(N,l+64-1);
            for(int i=l;i<=r;i++) s[i]=1ull << (i-l);
            for(auto it:g2) s[it.first]|=s[it.second]; //逆拓扑序更新
            for(int i=1;i<=N;i++){
              	ans+=__builtin_popcountll(s[i]);
				s[i]=0;           	
			}
        }
        ans=1ll*N*(N-1)/2+N-ans;
        printf("%lld\n",ans);
    }
    return 0;
}
posted @ 2023-02-25 16:28  Wannabtl  阅读(20)  评论(0编辑  收藏  举报