AGC005

我自信地看了一眼以为三个紫题可以做的很快,最起码不会被一个银牌卡半天。结果不说了。

个人觉得你光看我 vp 记录应该能比较知道我哪个题是自己写的哪个题是贺的题解。

吗了六道贺两道我是什么几把。

[AGC005A] STring

入门。

int ans,top,n;
char s[200010];
int main(){
    scanf("%s",s+1);
    n=strlen(s+1);
    for(int i=1;i<=n;i++){
        if(s[i]=='S')top++;
        else{
            if(top)top--;
            else ans++;
        }
    }
    printf("%d\n",ans+top);
    return 0;
}

[AGC005B] Minimum Sum

【模板】单调栈。不明白为什么是蓝的。

int n,a[200010],stk[200010],top,l[200010],r[200010];
long long ans;
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=n;i++){
        while(top&&a[stk[top]]>=a[i]){
            r[stk[top]]=i-1;top--;
        }
        l[i]=stk[top]+1;stk[++top]=i;
    }
    while(top){
        r[stk[top]]=n;top--;
    }
    for(int i=1;i<=n;i++)ans=(ans+1ll*a[i]*(r[i]-i+1)*(i-l[i]+1));
    printf("%lld\n",ans);
    return 0;
}

[AGC005C] Tree Restoring

我暂且不明白这个 \(O(n)\)\(100\) 数据范围是什么心态。

首先找到最大的 \(a_i\)\(mx\),然后构造一条长 \(mx\) 的链,并且对于每个距离计算出有多少点在树上和距离最远的点的距离是它。然后就很好构造了:

  1. 对于 \(a_i\le \lceil \dfrac {mx}2\rceil\) 的是不能通过往链上加点加出来的。
  2. 对于 \(a_i> \lceil \dfrac {mx}2\rceil\) 的是可以随便加点加出来的。

然后扫一遍就行了,最后判掉没有全部用完的不合法。

int n,a[110],cnt[110];
long long ans;
bool check(){
    int i;
    for(i=1;2*i<=a[n]+1;i++)if(cnt[i]<0)return false;
    for(;i<=a[n];i++)if(cnt[i]>0)return false;
    return true;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    sort(a+1,a+n+1);
    for(int i=a[n];(i<<1)>=a[n];i--){
        cnt[i]++;
        if(2*i!=a[n])cnt[i]++;
    }
    for(int i=1;i<=n;i++)cnt[a[i]]--;
    if(check())puts("Possible");
    else puts("Impossible");
    return 0;
}

[AGC005D] ~K Perm Counting

感觉这一场的 D 和 E 都比 F 好不少。反正我这俩都不会。

首先子集反演,设 \(f_i\) 为钦定 \(i\) 个不满足条件,其余放任自流的答案,那么剩下 \(n-i\) 个可以随便排列,方案数是全排列。所以答案是:

\[ans=\sum_{i=0}^n(-1)^{i}f_i(n-i)! \]

然后考虑怎么求这个 \(f\)。我们要找到所有有一定数量个 \(p_i\equiv i\pmod k\) 的方案数,根据这个取模,我们可以对每个 \(\bmod k\) 的剩余系计算答案,最后跑一个背包合并。

于是我们现在对于一个大小为 \(n\) 的剩余系重标号成一个排列 \(1,2,\dots,n\)。此时如果我们钦定一个元素不满足条件,那么 \(p_i=i\pm 1\)

\(dp_{i,j}\) 为扫到第 \(i\) 个元素,有 \(j\) 个元素不满足 \(p_i=i\pm 1\) 的答案。那么有以下情况:

  1. 不选 \(i\):贡献显然是 \(dp_{i-1,j-1}\)
  2. \(i\),且 \(p_i=i-1,p_{i-1}=i\):此时同时选了 \(i,i-1\),从 \(dp_{i-2,j}\) 转移。
  3. \(i\) ,且 \(p_i=i-1,p_{i-1}\not=i\):则此时有一条 \(i\rightarrow i-1\rightarrow i-2\rightarrow \dots \rightarrow x\) 的链。枚举链长 \(k\) ,贡献是 \(2\sum_{k=1}^{i-1}dp_{i-k-1,j-1}\) 。前缀和优化一下,推一下柿子可以发现这一部分的贡献是 \(dp_{i-2,j-1}-dp_{i-3,j}\)

背包合并就不说了。

const int mod=924844033;
int n,k,mx,dp[2010][2010],a[2010],tmp[2010];
void solve(int s[]){
    for(int i=0;i<=n;i++)tmp[i]=0;
    for(int i=0;i<=mx;i++){
        for(int j=0;j<=n-i;j++)tmp[i+j]=(tmp[i+j]+1ll*s[i]*a[j]%mod)%mod;
    }
    for(int i=0;i<=n;i++)a[i]=tmp[i];
}
int main(){
    scanf("%d%d",&n,&k);
    mx=(n+k-1)/k;
    dp[0][0]=dp[1][1]=dp[2][0]=dp[2][2]=1;dp[2][1]=2;
    for(int i=3;i<=n;i++){
        for(int j=0;j<=i;j++){
            dp[i][j]=dp[i-1][j];
            if(j)dp[i][j]=(1ll*dp[i][j]+dp[i-1][j-1]+dp[i-2][j-1])%mod;
            dp[i][j]=(1ll*dp[i][j]+dp[i-2][j]-dp[i-3][j]+mod)%mod;
        }
    }
    int ret=(n-1)%k+1;a[0]=1;
    for(int i=0;i<ret;i++)solve(dp[mx]);
    for(int i=ret;i<k;i++)solve(dp[mx-1]);
    int jc=1,ans=0;
    for(int i=0;i<=n;i++){
        ans=(ans+1ll*((n-i)&1?mod-1:1)*a[i]%mod*jc%mod)%mod;
        jc=1ll*jc*(i+1)%mod;
    }
    printf("%d\n",ans);
    return 0;
}

[AGC005E] Sugigma: The Showdown

仙术。

以下钦定树 \(a\) 的根是 \(x\) ,树 \(b\) 的根是 \(y\)

首先考虑什么时候会走无限步。如果树 \(a\) 上有一条边满足两个端点在树 \(b\) 上的距离 \(\ge 3\) ,那么只要 \(A\) 可以走到这两个端点中的一个点,就可以无限风筝 \(B\)

然后考虑 \(A\) 可以走到哪些点。所有在树 \(a\) 上深度小于树 \(b\) 上深度的点都可以走,其他点由于 \(B\) 可以先一步来拦截,都不可以走。

那么就很可以预料一种解法:找到所有 \(A\) 能走到的点,如果这些点中有满足无限步的,那么就可以无限走。反之,答案就是这些点在树 \(b\) 上深度的最大值 \(\times 2\) ,最优的走法是走到目标点后原地不动。

证明不会。

struct node{
    int v,next;
}edge[800010];
int n,x,y,t,ans,head[200010][2],dis[200010],fa[200010],u[200010],v[200010];
bool jud,vis[200010];
void add(int u,int v,int id){
    edge[++t].v=v;edge[t].next=head[u][id];head[u][id]=t;
}
void dfs1(int x,int f){
    dis[x]=dis[f]+1;fa[x]=f;
    for(int i=head[x][1];i;i=edge[i].next){
        if(edge[i].v!=f)dfs1(edge[i].v,x);
    }
}
bool check(int x,int y){
    if(dis[x]<dis[y])swap(x,y);
    if(dis[x]==dis[y])return fa[x]!=fa[y];
    else if(dis[x]==dis[y]+1)return fa[x]!=y;
    else if(dis[x]==dis[y]+2)return fa[fa[x]]!=y;
    return true;
}
void dfs2(int x,int f,int d){
    if(vis[x]){
        puts("-1");exit(0);
    }
    ans=max(ans,dis[x]);
    for(int i=head[x][0];i;i=edge[i].next){
        if(edge[i].v!=f){
            if(d+1<dis[edge[i].v])dfs2(edge[i].v,x,d+1);
        }
    }
}
int main(){
    scanf("%d%d%d",&n,&x,&y);
    for(int i=1;i<n;i++){
        scanf("%d%d",&u[i],&v[i]);
        add(u[i],v[i],0);add(v[i],u[i],0);
    }
    for(int i=1;i<n;i++){
        int u,v;scanf("%d%d",&u,&v);
        add(u,v,1);add(v,u,1);
    }
    dfs1(y,0);
    for(int i=1;i<n;i++){
        if(check(u[i],v[i]))vis[u[i]]=vis[v[i]]=true;
    }
    dfs2(x,0,1);
    ans--;
    printf("%d\n",ans<<1);
    return 0;
}

[AGC005F] Many Easy Problems

义眼顶针,鉴定为:纯纯的套路。

讲个笑话,我一开始在 D 题看见这个模数的时候感觉有一点异样,但是我觉得 D 既然不是 poly 那就没什么事。然后joke3579先生为我分解了一下这个模数:

\[924844033=2^{21}\times 441+1 \]

(陷入沉思)

首先这个枚举子集很难受,考虑把贡献拆了。分别考虑每个点的贡献,看每个点在选 \(i\) 个点的集合里被选了多少次。容斥变成总数减去不包含的数量。不包含当且仅当所有点都在这个点的一棵子树内(这个节点看做根)。于是点 \(u\) 的贡献就是:

\[\binom ni-\sum_{v\in son_u}\binom{size_v}{i} \]

于是对于 \(i\) 的答案 \(f_i\) 就有了:

\[\begin{aligned} f_i&=\sum_{u=1}^n\binom ni-\sum_{v\in son_u}\binom{size_v}{i}\\ &=n\binom ni-\sum_{u=1}^n\sum_{v\in son_u}\binom{size_v}i\\ \end{aligned} \]

这后面一堆带着 \(size\) 不太好搞,考虑对于每个 \(size\) 计算贡献。设 \(cnt_i=\sum_{x=1}^n[size_x=i]\),那么原式就可以变成:

\[n\binom ni-\sum_{u=1}^n\sum_{j=1}^n\dfrac{j!\times cnt_j}{i!(j-i)!} \]

只看后面显然是个差卷积的形式。于是就做完了。注意这题原根是 \(5\)

(再讲个笑话,我原先写了这么个东西:)

    ntt(a,wl,1);ntt(b,wl,-1);
    for(int i=0;i<wl;i++)a[i]=1ll*a[i]*b[i]%mod;
    ntt(a,wl,-1);

(咳咳)

const int mod=924844033,g=5;
int n,wl,a[600010],b[600010],r[600010],jc[200010],inv[200010],siz[200010],cnt[200010];
void get(int n){
    wl=1;
    while(wl<=n)wl<<=1;
    for(int i=0;i<=wl;i++)r[i]=(r[i>>1]>>1)|((i&1)<<(__lg(wl)-1));
}
int qpow(int a,int b){
    int ans=1;
    while(b){
        if(b&1)ans=1ll*a*ans%mod;
        a=1ll*a*a%mod;
        b>>=1;
    }
    return ans;
}
const int invg=qpow(g,mod-2);
void ntt(int a[],int n,int tp){
    for(int i=1;i<n;i++)if(i<r[i])swap(a[i],a[r[i]]);
    for(int mid=1;mid<n;mid<<=1){
        register int wn=qpow(tp==1?g:invg,(mod-1)/(mid<<1));
        for(int j=0;j<n;j+=mid<<1){
            register int w=1;
            for(int k=0;k<mid;k++,w=1ll*w*wn%mod){
                int x=a[j+k],y=1ll*w*a[j+mid+k]%mod;
                a[j+k]=(x+y)%mod;a[j+mid+k]=(x-y+mod)%mod;
            }
        }
    }
    if(tp^1){
        int inv=qpow(n,mod-2);
        for(int i=0;i<n;i++)a[i]=1ll*a[i]*inv%mod;
    }
}
struct node{
    int v,next;
}edge[400010];
int t,head[200010];
void add(int u,int v){
    edge[++t].v=v;edge[t].next=head[u];head[u]=t;
}
void dfs(int x,int f){
    siz[x]=1;
    for(int i=head[x];i;i=edge[i].next){
        if(edge[i].v!=f){
            dfs(edge[i].v,x);
            siz[x]+=siz[edge[i].v];
            cnt[siz[edge[i].v]]++;
        }
    }
    cnt[n-siz[x]]++;
}
int C(int n,int m){
    return 1ll*jc[n]*inv[m]%mod*inv[n-m]%mod;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        int u,v;scanf("%d%d",&u,&v);
        add(u,v);add(v,u);
    }
    dfs(1,0);jc[0]=inv[0]=1;
    for(int i=1;i<=n;i++)jc[i]=1ll*jc[i-1]*i%mod;
    inv[n]=qpow(jc[n],mod-2);
    for(int i=n-1;i>=1;i--)inv[i]=1ll*inv[i+1]*(i+1)%mod;
    for(int i=1;i<=n;i++)a[i]=1ll*jc[i]*cnt[i]%mod,b[i]=inv[i];
    b[0]=1;
    reverse(a,a+n+1);
    get((n+1)<<1);
    ntt(a,wl,1);ntt(b,wl,1);
    for(int i=0;i<wl;i++)a[i]=1ll*a[i]*b[i]%mod;
    ntt(a,wl,-1);
    reverse(a,a+n+1);
    for(int i=1;i<=n;i++)a[i]=(1ll*n*C(n,i)%mod-1ll*inv[i]*a[i]%mod+mod)%mod;
    for(int i=1;i<=n;i++)printf("%d\n",a[i]);
    return 0;
}

完了。

posted @ 2022-11-14 18:43  gtm1514  阅读(30)  评论(0编辑  收藏  举报