AtCoder Regular Contest 093~097

AtCoder Regular Contest 093

E. Bichrome Spanning Tree

考虑先建出最小生成树。不妨设为 Y

如果 X<Y 显然无解。

然后求出每一条边强制选中时的最小生成树,不妨设为 Yi

如果 X>Y,那么最小生成树一定是同色的,对于其他 Yi=X 的边钦定某一条异色,Yi<X 的边钦定同色,那么其他边任意。

如果 X=Y,如果最小生成树异色,那么其他边任意。否则同上。

F. Dark Horse

考虑钦定你在 1 位置,最后再乘 2n 即可。这样剩余人可以分成 n 组,第 i 组人数 2i1。要求每组的最小值不能是 A 集合的人。

考虑容斥,钦定 S 组中的人是 A 集合的人。考虑 dp,将 A 集合依次放入。对于一个 ai,如果它是某个组的最小值,考虑构造该组的其他部分,显然只有没有放入其他集合的大于 ai 的数可行,直接 dp 集合。

复杂度 O(2nn2)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100010
#define mod 1000000007
using namespace std;
int fac[N],inv[N];
inline void add(int &x,int y){x+y>=mod?x=x+y-mod:x=x+y;}
int ksm(int a,int b=mod-2)
{
    int r=1;
    for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) r=1ll*r*a%mod;
    return r;
}
int C(int a,int b){return a<b?0:1ll*fac[a]*inv[b]%mod*inv[a-b]%mod;}
void init(int n=N-10)
{
    fac[0]=1;
    for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
    inv[n]=ksm(fac[n]);
    for(int i=n-1;i>=0;i--) inv[i]=1ll*inv[i+1]*(i+1)%mod;
}
int a[N],f[N];
int main()
{
    init();
    int n,m,q;
    scanf("%d%d",&n,&q);
    for(int i=1;i<=q;i++) scanf("%d",&a[i]);
    reverse(a+1,a+q+1);
    m=1<<n;
    f[0]=1;
    for(int i=1;i<=q;i++)
        for(int s=m-1;~s;s--)
        if(f[s])
            for(int k=0;k<n;k++)
            if(!(s>>k&1)) add(f[s^(1<<k)],1ll*f[s]*C(m-s-a[i],(1<<k)-1)%mod*fac[1<<k]%mod);
    int ans=0;
    for(int i=0;i<m;i++) add(ans,(__builtin_popcount(i)&1?mod-1ll:1ll)*f[i]%mod*fac[m-i-1]%mod);
    printf("%lld",(1ll<<n)*ans%mod);
    return 0;
}

AtCoder Regular Contest 094

AtCoder Regular Contest 095

F. Permutation Tree

假设有一个指针维护当前最大值,那么从右往左推的过程中每次都会使若干个点连向当前最大值,最后连出的一定是一个链套菊花(毛毛虫)。

考虑钦定,一个顺序,直接按 2,3,4,1,6,7,5 构造即可。

复杂度 O(n)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define N 100010
using namespace std;
vector<int>g[N];
int deg[N];
vector<int>f;
vector<int> solve(int n)
{
    vector<int>res(n);
    int tt=0,p=0;
    for(int u:f)
    {
        res[p+deg[u]]=++tt;
        for(int j=p;j<p+deg[u];j++) res[j]=++tt;
        p+=deg[u]+1;
    }
    if(tt<n){puts("-1");exit(0);}
    return res;
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),g[u].push_back(v),g[v].push_back(u);
    for(int u=1;u<=n;u++)
    if(g[u].size()>1)
    {
        int c=0;
        for(int v:g[u]) if(g[v].size()>1) c++;
        if(c==1)
        {
            for(int v:g[u]) if(g[v].size()==1){f.push_back(v);break;}
            f.push_back(u);
            for(int v:g[u]) if(g[v].size()>1) u=v;
            while(true)
            {
                int p=f.back();f.push_back(u);
                for(int v:g[u]) if(g[v].size()>1 && v!=p){u=v;break;}
                if(u==f.back())
                {
                    for(int v:g[u]) if(g[v].size()==1 && v!=p){f.push_back(v);break;}
                    break;
                }
            }
            break;
        }
    }
    if(!f.size())
    {
        for(int u=1;u<=n;u++)
        if(g[u].size()>1)
        {
            for(int v:g[u]) if(g[v].size()==1){f.push_back(v);break;}
            f.push_back(u);
            for(int v:g[u]) if(g[v].size()==1 && v!=*f.begin()){f.push_back(v);break;}
            break;
        }
        if(!f.size()){puts("1 2");return 0;}
    }
    for(int i=0;i<f.size();i++) if(i==0 || i==f.size()-1) deg[f[i]]=g[f[i]].size()-1;
    else deg[f[i]]=g[f[i]].size()-2;
    // for(int v:f) printf("%d ",v);puts("");
    // for(int i=1;i<=n;i++) printf("%d ",deg[i]);puts("");
    auto ans=solve(n);
    reverse(f.begin(),f.end());
    ans=min(ans,solve(n));
    for(int v:ans) printf("%d ",v);
    return 0;
}

AtCoder Regular Contest 096

E. Everything on It

考虑容斥,假设钦定 i 个数不到 2。换句话说要么 1 要么 0

考虑钦定一个垃圾桶集合,这样所有 0 的数认为扔到垃圾桶集合即可。

为了防止记重,再额外规定一个特殊的数,这个数所在集合就是垃圾桶集合。

这部分答案就是第二类斯特林数 {i+1j+1}

考虑剩下的数,要么任意组成集合方案数为 22ni,或者往前面集合扔方案数为 (2ni)j。直接 dp 即可。

复杂度 O(n2)

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 3012
using namespace std;
int mod,C[N][N],S[N][N];
int f[N],_2[N*N];
void init(int n=N-10)
{
    C[0][0]=S[0][0]=1;
    for(int i=1;i<=n;i++)
        for(int j=C[i][0]=1;j<=n;j++)
            C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod,
            S[i][j]=(S[i-1][j-1]+1ll*S[i-1][j]*j)%mod;
    for(int i=_2[0]=1;i<=n*n;i++) _2[i]=2ll*_2[i-1]%mod;
}
int ksm(int a,int b=mod-2,int m=mod)
{
    int r=1;
    for(;b;b>>=1,a=1ll*a*a%m) if(b&1) r=1ll*r*a%m;
    return r;
}
int main()
{
    int n;
    scanf("%d%d",&n,&mod);
    init();
    for(int i=0;i<=n;i++)
        for(int j=0;j<=i;j++) f[i]=(f[i]+1ll*S[i+1][j+1]*_2[(n-i)*j])%mod;
    int ans=0;
    for(int i=0;i<=n;i++) ans=(ans+(i&1?mod-1ll:1ll)*C[n][i]%mod*ksm(2,ksm(2,n-i,mod-1))%mod*f[i])%mod;
    printf("%d\n",ans);
    return 0;
}

F. Sweet Alchemy

考虑这显然是一个树形 dp,子树和一下等价于除了 1 号点其他点只能选 d 个。

考虑直接 dp 可以做到 O(xn2),显然过不去。

考虑一种错误的贪心:按 sici 排序,从大到小加。考虑要使这个结论正确,我们需要对于偏序 (i,j),有 j 可以选 si 个,因为这样换成若干 isj 个更优。

所以只需要对 min(n,D) 求背包即可。

考虑用二进制分组优化,复杂度 O(n4logn)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define ll long long
using namespace std;
const int N=60;
ll c[N];
int siz[N],n,x,d;
vector<int>g[N],h[N];
void dfs(int u)
{
    siz[u]=1;h[u].push_back(u);
    for(int v:g[u])
    {
        dfs(v);
        for(int i:h[v]) h[u].push_back(i);
        siz[u]+=siz[v];
        c[u]+=c[v];
    }
}
const int M=N*N*N;
ll f[M];
int solve(int m)
{
    memset(f,0x3f,sizeof(f));
    int mx=0,m0=m;
    vector<int>fact;
    for(int i=0;(1<<i)<=m;i++) fact.push_back(1<<i),m-=(1<<i);
    if(m) fact.push_back(m);
    f[0]=0;
    for(int v=1;v<=n;v++)
    {
        mx+=siz[v]*m0;
        for(int w:fact)
            for(int i=mx;i>=siz[v]*w;i--)
                f[i]=min(f[i],f[i-siz[v]*w]+c[v]*w);
    }
    return mx;
}
int id[N];
int main()
{
    scanf("%d%d%d",&n,&x,&d);
    scanf("%d",&c[1]);
    for(int i=2,p;i<=n;i++) scanf("%d%d",&c[i],&p),g[p].push_back(i);
    dfs(1);
    int m=min(n,d),mx=solve(m);
    for(int i=1;i<=n;i++) id[i]=i;
    sort(id+1,id+n+1,[&](int x,int y){return c[x]*siz[y]<c[y]*siz[x];});
    int ans=0;
    for(int i=0;i<=mx;i++)
    if(f[i]<=x)
    {
        int res=i,v=f[i];
        for(int j=1;j<=n;j++)
        {
            int u=id[j];
            if(u==1)
            {
                ll w=(x-v)/c[u];
                res+=w*siz[u];
                break;
            }
            int w=min((ll)d-m,(x-v)/c[u]);
            res+=w*siz[u],v+=w*c[u];
        }
        ans=max(ans,res);
    }
    printf("%d\n",ans);
    return 0;
}

AtCoder Regular Contest 097

F. Monochrome Cat

如果最后要求在出发点结束,那么每条边一定会被经过两次,故答案是分子。所以本质上就是选一条链要求减少的步数尽可能少。

找到一个不可能被去掉的点(白点),直接 dfs 一下即可。

复杂度 O(n)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define N 100010
using namespace std;
int dep[N];bool col[N],sc[N];
char s[N];
vector<int>g[N];
int ans=0,res=0;
void dfs(int u,int p)
{
    sc[u]=col[u];int d=col[u]^1;
    if(p) d^=1;
    for(int v:g[u]) if(v!=p)
    {
        dfs(v,u);
        if(sc[v]) continue;
        sc[u]=false;d^=1;
    }
    if(sc[u]) return;
    dep[u]=d*2;ans+=d;
    for(int v:g[u]) if(v!=p && !sc[v])
    {
        res=max(res,dep[u]+dep[v]);
        dep[u]=max(dep[u],dep[v]+d*2);
        ans+=2;
    }
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),g[u].push_back(v),g[v].push_back(u);
    scanf("%s",s+1);
    for(int i=1;i<=n;i++) col[i]=s[i]=='B';
    int rt=0;for(int i=1;i<=n;i++) if(!col[i]){rt=i;break;}
    if(!rt){puts("0");return 0;}
    dfs(rt,0);
    printf("%d\n",ans-res);
    return 0;
}
posted @   Flying2018  阅读(23)  评论(0编辑  收藏  举报
(评论功能已被禁用)
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示