20231024 比赛总结

比赛链接

反思

A

没有想到维护 l i m lim lim 个单调栈的做法,感觉比较难想,之前没见过很难想出来,感觉还是见多识广

B

没想到 d p dp dp 的做法,感觉很妙

C

考试的时候死磕了 3 h 3h 3h T 3 T3 T3,最后别人跟我讲假了,感觉不能一直弄一道题, T 2 T2 T2 T 3 T3 T3 感觉还是简单一些的

D

考试的时候没看完题感觉有点失败,感觉 T 4 T4 T4 还是比较板的一道题

题解

A

好题!!!
我们考虑 l i m lim lim 的范围很小,所以考虑维护 l i m lim lim 个单调栈,第 x x x 个单调栈里维护当前在 i i i 之后的 j j j 需要找到 a n s j , x ans_{j,x} ansj,x j j j,即后面需要找前面第 x x x 个比它大的位置集合
然后直接单调栈维护,注意一下加入顺序,需要保证加完之后栈仍是单调递减的

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=3000100,NN=10000100;
int n,lim,type,a[NN];
int stk2[NN],tp,ans[NN];
int stk[5][N],top[5],tans[N][5],tmp[6][N],TP[6];
inline int read(){
    int FF=0,RR=1;
    char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
    for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
    return FF*RR;
}
void solve(int x,int numb){
    while(top[numb]&&a[x]>=a[stk[numb][top[numb]]]){
        tans[stk[numb][top[numb]]][numb]=x;
        tmp[numb+1][++TP[numb+1]]=stk[numb][top[numb]];
        top[numb]--;
    }
}
void insert(int x){
    for(int i=TP[x];i;i--) stk[x][++top[x]]=tmp[x][i];
}
int main(){
    freopen("kth.in","r",stdin);
    freopen("kth.out","w",stdout);
    n=read(),lim=read(),type=read();
    for(int i=1;i<=n;i++) a[i]=read();
    if(lim==1){
        for(int i=1;i<=n;i++){
            while(tp&&a[i]>a[stk2[tp]]) tp--;
            ans[i]=stk2[tp],stk2[++tp]=i;
        }
        if(!type)
            for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
        else{
            LL ANS=0;
            for(int i=1;i<=n;i++) ANS^=1ll*ans[i]*i;
            printf("%lld\n",ANS);
        }
        exit(0);
    }
    for(int i=n;i;i--){
        for(int j=0;j<=lim;j++) TP[j]=0;
        for(int j=0;j<lim;j++) solve(i,j);
        stk[0][++top[0]]=i;
        for(int j=1;j<lim;j++) insert(j);
    }
    if(!type)
        for(int i=1;i<=n;i++){
            for(int j=0;j<lim;j++) printf("%d ",tans[i][j]);
            puts("");
        }
    else{
        LL ANS=0;
        for(int i=1;i<=n;i++) for(int j=0;j<lim;j++) ANS^=1ll*tans[i][j]*i*(j+1);
        printf("%lld\n",ANS);
    }
    fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
    return 0;
}

B

我不知道如何才能想到这个 d p dp dp
把过程倒过来,变成从叶子往根流
f u , i f_{u,i} fu,i 为在 u u u 的子树中 i i i 时刻最多有多少水可以从叶子流到 u u u,转移很 s i m p l e simple simple,发现对于 t > n t>n t>n 时流量即为 f 1 , n f_{1,n} f1,n,求答案很 s i m p l e simple simple
时间复杂度单次 O ( n 2 ) O(n^2) O(n2)
(有 O ( n l o g n ) O(nlogn) O(nlogn) 的做法)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1100,inf=1e9+5;
int n,k,c[N],dp[N][N];
int e[N],ne[N],h[N],idx;
inline int read(){
    int FF=0,RR=1;
    char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
    for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
    return FF*RR;
}
void dfs(int u){
    for(int i=0;i<=n;i++) dp[u][i]=0;
    bool leaf=1;
    for(int i=h[u];~i;i=ne[i]){
        int v=e[i];dfs(v);leaf=0;
        for(int j=1;j<=n;j++){
            dp[u][j]+=min(c[v],dp[v][j-1]);
            if(dp[u][j]>inf) dp[u][j]=inf;
        }
    }
    if(leaf) for(int i=0;i<=n;i++) dp[u][i]=inf;
}
void add(int x,int y){ e[idx]=y,ne[idx]=h[x],h[x]=idx++;}
void work(){
    n=read(),k=read();
    idx=0;
    for(int i=1;i<=n;i++) h[i]=-1;
    for(int i=2;i<=n;i++){
        int fa=read();add(fa,i);
    }
    for(int i=2;i<=n;i++) c[i]=read();
    dfs(1);
    int res=0,ans=-1;
    for(int i=1;i<=n;i++){
        res+=dp[1][i];
        if(res>=k){ ans=i;break;}
    }
    if(ans==-1){
        int t=(k-res-1)/dp[1][n]+1;
        ans=n+t;
    }
    printf("%d\n",ans);
}
int main(){
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    int T=read();
    while(T--) work();    
    fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
    return 0;
}

C

CF原题的加强版(不过加强了很多)

沿用 C F CF CF 原题的套路,考虑对于每一层记录下一层的逆序对和顺序对数
考虑在分治树上建出线段树
那么修改就是把一个区间 [ L , R ] [L,R] [L,R] 中所有深度 > q >q >q 的点全部交换左右儿子,且交换顺序对合逆序对数
这个操作是可以用线段树做的
具体来说,我们对线段树的每个结点维护这个节点的子树中每层的和,然后修改时暴力修改,同时打 t a g tag tag 也是简单的
时间复杂度 O ( n l o g n + m l o g 2 n ) O(nlogn+mlog^2n) O(nlogn+mlog2n),因为一次 p u s h d o w n pushdown pushdown 需要 O ( l o g n ) O(logn) O(logn)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=(1<<20)+100;
struct Node{
    int lc,rc,tag;
    LL sum[22][2];
}seg[N<<2];
int n,m,a[N],b[N],depth[N<<2];
inline int read(){
    int FF=0,RR=1;
    char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
    for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
    return FF*RR;
}
void cdq(int l,int r,int x){
    depth[x]=depth[x>>1]+1;
    if(l==r) return;
    int mid=(l+r)>>1;
    seg[x].lc=x<<1,seg[x].rc=x<<1^1;
    cdq(l,mid,x<<1),cdq(mid+1,r,x<<1^1);
    for(int i=1;i<=20;i++) for(int t:{0,1}) seg[x].sum[i][t]=seg[x<<1].sum[i][t]+seg[x<<1^1].sum[i][t];
    for(int i=mid+1,j=l;i<=r;i++){
        while(j<=mid&&a[j]<=a[i]) j++;
        seg[x].sum[depth[x]][0]+=mid-j+1;
    }
    for(int i=l,j=mid+1;i<=mid;i++){
        while(j<=r&&a[j]<=a[i]) j++;
        seg[x].sum[depth[x]][1]+=r-j+1;
    }
    int j=mid+1,cnt=l;
    for(int i=l;i<=mid;i++){
        while(j<=r&&a[j]<=a[i]) b[cnt++]=a[j],j++;
        b[cnt++]=a[i];
    }
    while(j<=r) b[cnt++]=a[j],j++;
    for(int i=l;i<=r;i++) a[i]=b[i];
}
void make_tag(int x,int dep){
    for(int i=dep;i<=n;i++){
        swap(seg[x].sum[i][0],seg[x].sum[i][1]);
        seg[x].tag^=1<<i;
    }
}
void pushdown(int x){
    if(seg[x].tag>>depth[x]&1) swap(seg[x].lc,seg[x].rc);
    for(int i=1;i<=20;i++)
        if(seg[x].tag>>i&1){
            swap(seg[seg[x].lc].sum[i][0],seg[seg[x].lc].sum[i][1]);
            swap(seg[seg[x].rc].sum[i][0],seg[seg[x].rc].sum[i][1]);
        }
    seg[seg[x].lc].tag^=seg[x].tag,seg[seg[x].rc].tag^=seg[x].tag;
    seg[x].tag=0;
}
void modify(int l,int r,int x,int L,int R,int dep){
    if(L<=l&&r<=R){ make_tag(x,dep);return;}
    pushdown(x);
    int mid=(l+r)>>1;
    if(mid>=L) modify(l,mid,seg[x].lc,L,R,dep);
    if(mid<R) modify(mid+1,r,seg[x].rc,L,R,dep);
    for(int i=depth[x]+1;i<=n;i++) for(auto t:{0,1}) seg[x].sum[i][t]=seg[seg[x].lc].sum[i][t]+seg[seg[x].rc].sum[i][t];
}
int main(){
    freopen("pair.in","r",stdin);
    freopen("pair.out","w",stdout);
    n=read(),m=read();
    for(int i=1;i<=1<<n;i++) a[i]=read();
    cdq(1,1<<n,1);
    while(m--){
        int q=read(),l=read(),r=read();
        l=(l-1)*(1<<(n-q))+1,r=r*(1<<(n-q));
        modify(1,1<<n,1,l,r,q+1);
        LL ans=0;
        for(int i=1;i<=n;i++) ans+=seg[1].sum[i][0];
        printf("%lld\n",ans);
    }
    fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
    return 0;
}

D

有些套路的一道题
首先看到 k k k 次方,想到转下降幂
根据 n k = ∑ t = 0 k t ! { k t } g l , t n^k=\sum\limits_{t=0}^kt!{k\brace t}g_{l,t} nk=t=0kt!{tk}gl,t
其中 g ( l , t ) g(l,t) g(l,t) 为从 x x x 1 1 1 走过 l l l 条边,从中选出 t t t 条边的期望
于是我们可以令 f u , i f_{u,i} fu,i 表示从 u u u 1 1 1 走过了边中选出 i i i 条边的期望,这样我们就 不用考虑走过多少条边了
然后发现 f u , i f_{u,i} fu,i 是由 f ? , i − 1 f_{?,i-1} f?,i1 f ? , i f_{?,i} f?,i 共同转移过来的,不难想到树上高斯消元,直接推一推式子即可
时间复杂度 O ( n k + k 2 ) O(nk+k^2) O(nk+k2)

#include <bits/stdc++.h>
using namespace std;
const int N=100100,K=1100,P=998244353;
int n,k,p[N],ans[N],s2[K][K];
int e[N<<1],ne[N<<1],h[N],idx;
int f[N],g[N];
inline int read(){
    int FF=0,RR=1;
    char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
    for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
    return FF*RR;
}
int qmi(int a,int b){
    int res=1;
    for(;b;b>>=1){
        if(b&1) res=1ll*res*a%P;
        a=1ll*a*a%P;
    }
    return res;
}
int A[N],B[N];
inline void inc(int &x,int y){ x+=y;if(x>=P) x-=P;}
void dfs(int u,int fa){
    int deg=0,coef1=0,coef2=0;
    for(int i=h[u];~i;i=ne[i]){
        int v=e[i];
        deg++,inc(coef2,g[v]);
        if(v==fa) continue;
        dfs(v,u);
        inc(coef1,A[v]),inc(coef2,B[v]);
    }
    int ivdeg=1ll*qmi(deg,P-2)*(1-p[u]+P)%P;
    coef1=(1ll*coef1*ivdeg+p[u])%P;
    coef1=(P+1-coef1)%P,coef1=qmi(coef1,P-2);
    A[u]=1ll*coef1*ivdeg%P;
    B[u]=(1ll*coef2*ivdeg+1ll*p[u]*g[u])%P*coef1%P;
}
void dfs2(int u,int fa){
    for(int i=h[u];~i;i=ne[i]){
        int v=e[i];if(v==fa) continue;
        f[v]=(1ll*f[u]*A[v]+B[v])%P;
        dfs2(v,u);
    }
}
void add(int x,int y){ e[idx]=y,ne[idx]=h[x],h[x]=idx++;}
int main(){
    freopen("raiden.in","r",stdin);
    freopen("raiden.out","w",stdout);
    n=read(),k=read();
    s2[0][0]=1;
    for(int i=1;i<=k;i++) for(int j=1;j<=i;j++) s2[i][j]=(s2[i-1][j-1]+1ll*s2[i-1][j]*j)%P;
    memset(h,-1,sizeof(h));
    for(int i=1;i<n;i++){
        int x=read(),y=read();
        add(x,y),add(y,x);
    }
    int iv=qmi((int)1e6,P-2);
    for(int i=2;i<=n;i++) p[i]=read(),p[i]=1ll*p[i]*iv%P;
    for(int i=0,fac=1;i<=k;i++,fac=1ll*fac*i%P){
        for(int j=1;j<=n;j++) g[j]=f[j];
        f[1]=(!i);
        dfs(1,-1),dfs2(1,-1);
        for(int j=1;j<=n;j++) inc(ans[j],1ll*s2[k][i]*fac%P*f[j]%P);
    }
    for(int i=2;i<=n;i++) printf("%d\n",ans[i]);
    fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值