AtCoder Grand Contest 041

链接

A. Table Tennis Training

\(\color{gray}{\text{skipped}}\)

B. Voting Judges

\(\color{gray}{\text{skipped}}\)

C. Domino Quality

构造题。

考虑我们是可以构造 \(4\times 4\),\(5\times 5\),\(6\times 6\),\(7\times 7\) 的构造,且每行每列都只有 2。

所以直接将矩形对角线分成若干上述矩形即可,其他全部置 0。

注意特判 3。

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 100010
using namespace std;
const char s[4][8][8]={
    {
        "abcc",
        "abdd",
        "ccab",
        "ddab"},
    {
        "abbcc",
        "ad..e",
        "ed..e",
        "e.aac",
        "aabbc"},
    {
        "aabbcc",
        ".d.d.d",
        ".d.d.d",
        "d.d.d.",
        "d.d.d.",
        "aabbcc"},
    {
        ".aabbcc",
        "abcc...",
        "ab.a...",
        "bcca...",
        "b...abb",
        "a...a.a",
        "a...bba"}
};
int p=1,n;
char res[1010][1010];
void print(int x)
{
    n-=x+4;
    for(int i=0;i<x+4;i++)
        for(int j=0;j<x+4;j++) res[p+i][p+j]=s[x][i][j];
    p+=x+4;
}
int main()
{
    scanf("%d",&n);
    int m=n;
    if(n<=3)
    {
        if(n<=2) puts("-1");
        else puts("a..\na..\n.bb");
        return 0;
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++) res[i][j]='.';
    print(n%4);
    while(n) print(0);
    for(int i=1;i<=m;i++) printf("%s\n",res[i]+1);
    return 0;
}

D. Problem Scores

考虑这个 \(S\)\(T\) 假如存在交集,就意味着交集部分是可以去掉的,显然不影响相对大小,所以不妨钦定 \(S\)\(T\) 不交。

可以发现最坏情况下一定是 \(S\) 取前 \(\lfloor\frac {n-1} 2\rfloor\) 大的部分,\(T\) 取剩余部分。所以我们只需要保证这时候的结论成立即可。

显然要分奇偶讨论。先考虑偶数的情况。

可以发现原序列我们可以看成,先钦定中间点的位置,然后依次将前缀 -1,后缀 +1。可以发现这是等价的。

分别枚举减哪个前缀和减多少,然后直接 dp 即可。稍微优化一下就是 \(O(n^2\log n)\)

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 5010
using namespace std;
int f[N][N],mod,g[N][N];
int main()
{
    int n;
    scanf("%d%d",&n,&mod);
    f[0][0]=g[0][0]=1;
    for(int i=1;i<=(n+1)/2;i++)
        for(int j=0;j<=n;j++)
            for(int k=j;k<=n;k+=i) f[i][k]=(f[i][k]+f[i-1][j])%mod;
    for(int i=(n-1)/2;i;i--)
        for(int j=0;j<=n && i*j<n;j++)
            for(int k=i*j;k<=n-i;k++) g[j+1][k+i]=(g[j+1][k+i]+g[j][k])%mod;
    for(int i=0;i<=n;i++)
        for(int j=0;j<=n;j++)
        g[i][j]=(1ll*g[i][j]+(i?g[i-1][j]:0)+(j?g[i][j-1]:0)-(i && j?g[i-1][j-1]:0)+mod)%mod;
    int ans=0;
    for(int i=1;i<=n;i++)
        for(int j=0;j<i;j++) ans=(ans+1ll*f[n/2][j]*g[n-i][i-j-1])%mod;
    printf("%d\n",ans);
    return 0;
}

E. Balancing Network

构造好题。

首先考虑第一种情况,即要把所有行都移到同一行。可以发现,如果存在一条边链接 \((u,v)\),那么 \(u\) 能到的点我们可以认为 \(v\) 也能到,\(v\) 同理。

所以考虑算出每个点能到的点。可以证明,如果存在一个点可以被所有点到达,那么一定存在方案。

对于构造考虑从后往前处理。如果一条边链接的两个点中一个点已经构造好了到达目标点的方案,那么另一个点连向它,否则随便连一条。可以证明这样是对的。

然后考虑第二种情况。可以发现只有当 \(n=2\) 时无解,否则一定有解。

那么我们只考虑前 3 行,我们最后目的是让至少两个位置不同。所以从后往前,可以发现的是如果有 \(u\rightarrow v\),那么 \(v\) 的所有流量都会流到 \(u\) 上,反过来,\(u\) 的部分流量也可以回到 \(v\) 上。那么我们钦定这次操作是由流量少的点向多的点连边。

用 bitset 优化,复杂度 \(O(\frac{n^2}{\omega})\).

#include<iostream>
#include<cstdio>
#include<cstring>
#include<bitset>
#define N 50010
#define M 100010
using namespace std;
int a[N],n,m;
bitset<N>v[N],t;
int x[M],y[M];
bool vis[M],ans[M];
int c[]={0,1,1,1},g[]={0,1,2,3};
int main()
{
    int T;
    scanf("%d%d%d",&n,&m,&T);
    for(int i=1;i<=m;i++) scanf("%d%d",&x[i],&y[i]);
    if(T==1)
    {
        t.set();
        for(int i=1;i<=n;i++) v[i].set(i);
        for(int i=m;i;i--) v[x[i]]=(v[y[i]]|=v[x[i]]);
        for(int i=1;i<=n;i++) t&=v[i];
        for(int i=1;i<=n;i++)
        if(t.test(i))
        {
            vis[i]=true;
            for(int p=m;p;p--)
            if(vis[y[p]]) ans[p]=true,vis[x[p]]=true;
            else vis[y[p]]=vis[x[p]];
            for(int i=1;i<=m;i++) putchar(ans[i]?'v':'^');
            return 0;
        }
        puts("-1");
    }
    else
    {
        if(n<=2){puts("-1");return 0;}
        for(int i=m;i;i--)
        if(y[i]<4)
        {
            ans[i]=c[g[x[i]]]>c[g[y[i]]];
            if(c[g[x[i]]]>c[g[y[i]]]) swap(x[i],y[i]);
            c[g[x[i]]]++,c[g[y[i]]]--;
            g[y[i]]=g[x[i]];
        }
        for(int i=1;i<=m;i++) putchar(ans[i]?'v':'^');
    }
    return 0;
}

F. Histogram Rooks

首先考虑容斥,即钦定有 \(x\) 列有格子没有被覆盖。

那么这些格子的对应行和列也一定没有车。

考虑由于有删除格子的部分,每次我们找到当前区间的最低点,先分治左右两边,然后处理当前格子贡献。

可以发现,由于是区间最低点,区间任意高于最低点位置的车都不会影响当前列。所以可以影响的部分变成一个矩形。

考虑 dp。用 \(f_{i,j}\) 表示节点 \(i\) 的区间中钦定 \(j\) 列没有选的方案数。

但是直接这样容斥会有问题,因为我们并不知道这些列是否真的存在格子没有被选中。所以考虑再容斥,即集合 \(T\) 中的列虽然没有车,但是被其他列的车覆盖了。

直接转移看起来是 \(O(n^3)\)。但这其实也是树形 dp 的经典套路,我们每次枚举的长度只有左右区间的长度,那么任意两列的贡献只会被计算一次。复杂度 \(O(n^2)\)

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 410
#define mod 998244353
using namespace std;
int ksm(int a,int b=mod-2)
{
    int r=1;
    for(;b;b>>=1)
    {
        if(b&1) r=1ll*r*a%mod;
        a=1ll*a*a%mod;
    }
    return r;
}
int f[N][2][N],_2[N],h[N],cnt;
int siz[N];
void solve(int &u,int l,int r,int pre)
{
    if(l>r) return ;
    int x=l;
    for(int i=l+1;i<=r;i++)
    if(h[i]<h[x]) x=i;
    u=++cnt;int ls=0,rs=0;
    solve(ls,l,x-1,h[x]);solve(rs,x+1,r,h[x]);
    auto gl=f[ls],gr=f[rs],g=f[u];
    for(int i=0;i<=siz[ls];i++)
        for(int j=0;j<=siz[rs];j++)
        {
            g[0][i+j]=(g[0][i+j]+1ll*gl[0][i]*gr[0][j])%mod;
            g[1][i+j]=(g[1][i+j]+1ll*gl[1][i]*gr[1][j]-1ll*gl[0][i]*gr[0][j]%mod+mod)%mod;
            g[0][i+j+1]=(g[0][i+j+1]-1ll*gl[0][i]*gr[0][j]%mod+mod)%mod;
            g[1][i+j+1]=(g[1][i+j+1]+1ll*gl[0][i]*gr[0][j])%mod;
        }
    siz[u]=siz[ls]+siz[rs]+1;
    for(int j=0;j<=siz[u];j++)
        g[0][j]=1ll*g[0][j]*ksm(_2[siz[u]-j],h[x]-pre)%mod,
        g[1][j]=(g[0][j]+1ll*g[1][j]*ksm(_2[siz[u]-j]-1,h[x]-pre))%mod;
}
int main()
{
    int n;
    scanf("%d",&n);
    _2[0]=f[0][0][0]=f[0][1][0]=1;
    for(int i=1;i<=n;i++) scanf("%d",&h[i]),_2[i]=_2[i-1]*2%mod;
    int u=0,ans=0;
    solve(u,1,n,0);
    for(int i=0;i<=n;i++) ans=(ans+f[u][1][i])%mod;
    printf("%d\n",ans);
    return 0;
}
posted @ 2023-09-20 18:58  Flying2018  阅读(16)  评论(0)    收藏  举报