AtCoder Regular Contest 102 题解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_33229466/article/details/82292096

前言

开学之后估计就没办法打比赛了。
开场做掉A和B之后,C写了个NTT结果被卡常,然后就一直卡到快比赛结束,这时突然发现第二个生成函数只用预处理到一半!在还有两分钟结束的时候成功卡过,怒涨了一波rating。。。
这里写图片描述

题解

C - Triangular Relationship

乱做。。。

代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>

typedef long long LL;

const int N=200005;

int n,k,t[N];

int main()
{
    scanf("%d%d",&n,&k);
    for (int i=1;i<=n;i++) t[i%k]++;
    LL ans=0;
    for (int i=1;i<=n;i++)
        if (i%k==0) ans+=(LL)t[0]*t[0];
        else if (i%k*2==k) ans+=(LL)t[k/2]*t[k/2];
    printf("%lld\n",ans);
    return 0;
}

D - All Your Paths are Different Lengths

考虑除了1和n外,第i个点向第i+1个点连一条权值为0和一条权值为 2 n i 1 的边。然后把L拆成二进制,对于每个1都向对应的点连一条边就好了。

代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>

const int N=105;

int L,n,m,bin[N],a[N];
struct data{int x,y,w;}ans[N];

void add(int x,int y,int w)
{
    m++;
    ans[m].x=x;ans[m].y=y;ans[m].w=w;
}

int main()
{
    scanf("%d",&L);
    n=20;
    bin[0]=1;
    for (int i=1;i<=20;i++) bin[i]=bin[i-1]*2;
    for (int i=n-1;i>=2;i--) add(i,i+1,bin[n-i-1]),add(i,i+1,0);
    int top=0,sum=0;
    while (L>=262144) add(1,2,sum),sum+=262144,L-=262144;
    while (L) a[++top]=L%2,L/=2;
    for (int i=top;i>=1;i--)
    {
        if (!a[i]) continue;
        add(1,n-i+1,sum);
        sum+=bin[i-1];
    }
    printf("%d %d\n",n,m);
    for (int i=1;i<=m;i++) printf("%d %d %d\n",ans[i].x,ans[i].y,ans[i].w);
    return 0;
}

E - Stop. Otherwise…

考虑对每个询问分别求答案。设当前询问为t,现在把1到k分成若干份,若i和t-i都存在就把他们分到一份,否则就把i单独分为一份。对于有两个数的份,显然两个数至多选一个。然后这个可以用生成函数来算贡献。复杂度是 O ( n k l o g n )
正解是直接枚举有存在两个数的份有多少份至少选了一个数,然后直接用组合数算贡献,就可以做到 O ( n k ) 了,真的是又快又短。

代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>

typedef long long LL;

const int N=2005;
const int MOD=998244353;

int n,k,a[N*3],rev[N*3],f[N][N*3],g[N][N*3],L,ny,w1[N*3],w2[N*3],ans[N];

int ksm(int x,int y)
{
    int ans=1;
    while (y)
    {
        if (y&1) ans=(LL)ans*x%MOD;
        x=(LL)x*x%MOD;y>>=1;
    }
    return ans;
}

void NTT(int *a,int f)
{
    for (int i=0;i<L;i++) if (i<rev[i]) std::swap(a[i],a[rev[i]]);
    for (int i=1;i<L;i<<=1)
    {
        int wn=(f==1?w1[i]:w2[i]);
        for (int j=0;j<L;j+=(i<<1))
        {
            int w=1;
            for (int k=0;k<i;k++)
            {
                int u=a[j+k],v=(LL)a[j+k+i]*w%MOD;
                a[j+k]=(u+v)%MOD;a[j+k+i]=(u+MOD-v)%MOD;
                w=(LL)w*wn%MOD;
            }
        }
    }
    if (f==-1) for (int i=0;i<L;i++) a[i]=(LL)a[i]*ny%MOD;
}

void pre()
{
    int lg=0;
    for (L=1;L<=n*2;L<<=1,lg++);
    for (int i=0;i<L;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(lg-1));
    for (int i=1;i<L;i<<=1) w1[i]=ksm(3,(MOD-1)/i/2),w2[i]=ksm(3,MOD-1-(MOD-1)/i/2);
    ny=ksm(L,MOD-2);
    for (int i=0;i<=n;i++) f[1][i]=1,g[1][i]=2;
    g[1][0]=f[0][0]=g[0][0]=1;
    NTT(f[1],1);NTT(g[1],1);NTT(f[0],1);NTT(g[0],1);
    for (int i=2;i<=k/2;i++)
    {
        for (int j=0;j<L;j++) f[i][j]=(LL)f[i-1][j]*f[1][j]%MOD,g[i][j]=(LL)g[i-1][j]*g[1][j]%MOD;
        NTT(f[i],-1);NTT(g[i],-1);
        for (int j=n+1;j<=n*2;j++) f[i][j]=g[i][j]=0;
        NTT(f[i],1);NTT(g[i],1);
    }
    for (int i=k/2+1;i<=k;i++)
    {
        for (int j=0;j<L;j++) f[i][j]=(LL)f[i-1][j]*f[1][j]%MOD;
        NTT(f[i],-1);
        for (int j=n+1;j<=n*2;j++) f[i][j]=0;
        NTT(f[i],1);
    }
}

int main()
{
    scanf("%d%d",&k,&n);
    pre();
    for (int i=2;i<=k+1;i++)
    {
        int s=0;
        for (int j=1;j<=k;j++) if (j*2!=i&&j<i&&i-j<=k) s++;
        for (int j=0;j<L;j++) a[j]=(LL)f[std::max(0,k-s-((i&1)?0:1))][j]*g[s/2][j]%MOD;
        NTT(a,-1);
        if (i&1) printf("%d\n",ans[i]=a[n]);
        else printf("%d\n",ans[i]=(a[n]+a[n-1])%MOD);
    }
    for (int i=k+2;i<=k*2;i++) printf("%d\n",ans[k-(i-(k+2))]);
    return 0;
}

F - Revenge of BBuBBBlesort!

考虑判断能否从初始排列顺着构造成给定排列。首先有个结论就是如果我们交换了对i两侧的数进行交换,那么肯定不会对i-1和i+1进行操作。那么显然一个数只能朝着一个方向走。如果答案合法那么我们一定可以将序列分成若干个区间[l,r],其中l,l+2,…,r都是一定要移动的,其他位置则不需要移动。首先这些数必须要构成一个l到r的排列,其次因为往同一个方向移动的两个数的相对位置必然不变,所以这个区间合法当且仅当往左移的数的目标位置单调递增,往右移的数同理。那么就可以 O ( n ) 出解了。

代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>

const int N=300005;

int n,a[N],pos[N];

void fff()
{
    puts("No");
    exit(0);
}

int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        if (a[i]%2!=i%2) fff();
        pos[a[i]]=i;
    }
    pos[0]=pos[n+1]=-1;
    for (int i=1;i<=n;i++)
        if (pos[i-1]!=i-1&&pos[i]!=i&&pos[i+1]!=i+1) fff();
    int l=1;
    while (l<=n)
    {
        while (l<=n&&pos[l]==l) l++;
        if (l>n) break;
        int r=l;
        while (r+2<=n&&pos[r+2]!=r+2&&pos[r+1]==r+1) r+=2;
        for (int i=l;i<=r;i+=2) if (pos[i]<l||pos[i]>r) fff();
        int ls1=0,ls2=0;
        for (int i=l;i<=r;i+=2)
            if (pos[i]<i)
            {
                if (pos[i]<ls1) fff();
                ls1=pos[i];
            }
            else
            {
                if (pos[i]<ls2) fff();
                ls2=pos[i];
            }
        l=r+1;
    }
    puts("Yes");
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_33229466/article/details/82292096