总之就是 | ZROI NOIP21 冲刺 Day2

「启」

在连续三场爆零之后,连续上分三场,本以为脱离了爆零魔咒,但是这次还是爆零了(

是 Day2 的原因是 Day1 是杂题选讲,所以就没有 Day1 了(

「缺省源」

#include <bits/stdc++.h>

#define Heriko return
#define Deltana 0
#define Romanno 1
#define S signed
#define LL long long
#define R register
#define I inline
#define CI const int
#define mst(a, b) memset(a, b, sizeof(a))
#define ON std::ios::sync_with_stdio(false);cin.tie(0)
#define Files() freopen("RNMTQ.in","r",stdin);freopen("RNMTQ.out","w",stdout)

using namespace std;

template<typename J>
I void fr(J &x)
{
    short f(1);x=0;char c=getchar();
    
    while(c<'0' or c>'9')
    {
        if(c=='-') f=-1;
        
        c=getchar();
    }
   
    while (c>='0' and c<='9') 
    {
        x=(x<<3)+(x<<1)+(c^=48);
        c=getchar();
    }
   
    x*=f;
}

template<typename J>
I void fw(J x,bool k)
{
    if(x<0) x=-x,putchar('-');
   
    static short stak[35];short top(0);
   
    do
    {
        stak[top++]=x%10;
        x/=10;
    }
    while(x);
   
    while(top) putchar(stak[--top]+'0');
   
    k?puts(""):putchar(' ');
}

「A」数字变换

这题虽然场上的结论是对的,但是推出来的柿子太【数据销毁】我【数据销毁】不会算。

「A」题目简述

现对于二元组 \((x,y)\),可以有以下变换:

  • 变为 \((2x\bmod p,(y+p-x)\bmod p);\)

  • 变为 \(((x+p-y)\bmod p,2y\bmod p).\)

给出两个二元组 \((a,b)\)\((c,d)\),求问能否经过变换后使 \((a,b)\) 变为 \((c,d)\)

若能,输出需要变换几次,否则输出 \(-1.\)

「A」思路简述

\(s=a+b\),发现在经过变换之后,在 \(\bmod p\) 意义下 \(s\) 的值是不变的,所以我们可以利用这一点来进行判断是否有解,即为判断 \(a+b\) 是否等于 \(c+d.\)

因为 \(s\) 是一定的,所以我们可以只考虑 \(a\)。发现 \(a\) 在经过变换之后只会变为 \(2^ka-ts,(t \in [0,2^k))\),于是就有 \(c=2^ka-ts.\)

我们考虑在经历了 \(k\) 次操作之后,剪掉一个 \(c\) 看看有多少个 \(s\),如果 \(s\) 的数量大于 \(2^k\),说明存在一个 \(t\) 使得上式成立,所以最后的柿子是:

\[\dfrac{2^ka-c}{s}<2^k \]

枚举 \(k\) 即可。

「A」Code

LL MOD,T;

I LL FstPow(LL x,LL y)
{
    LL res(1);

    while(y)
    {
        if(y&1) (res*=x)%=MOD;

        (x*=x)%=MOD;y>>=1;
    }

    Heriko res;
}

S main()
{
    Files();
    fr(MOD),fr(T);

    while(T--)
    {
        LL a,b,c,d;
        fr(a),fr(b),fr(c),fr(d);
        
        if(((a+b)%MOD)!=((c+d)%MOD)) {puts("-1");continue;}

        if(a==c and b==d) {puts("0");continue;}

        LL k(1),Invs(FstPow(a+b,MOD-2)%MOD);

        while(((1<<k)*a%MOD-c+MOD)*Invs%MOD>=(1<<k)) ++k;

        fw(k,1);
    } 
    
    Heriko Deltana;
}

「B」均分财产

场上并没有想出来什么(

但是貌似很好乱搞的样子,正解的部分证明也是建立在数据随机上的(

「B」题目简述

给出有 \(n\) 个数的序列 \(a_i\)\(a_i \le 2 \times 10^5\) 且随机),任意删除不超过 \(k,(\min(25,n-2) \le k \le n-2)\) 个数,要求将剩下的数分为两个和相等的可重集合。

「B」思路简述

由于 \(k\) 范围的特殊性,所以进行分类讨论(bushi

  • \(k \le 25\) 的时候,暴力枚举所有的子集来找元素和相等的两个集合,设为 \(S_1,S_2\),最终答案即为 \(S_1'=S_1-S_1 \cap S_2,S_2'=S_2-S_1 \cap S_2\),复杂度为 \(O(n^2).\)

  • \(k>25\) 时,先对前 \(n-25\) 个元素进行贪心的分类,若当前的差值 \(dlt<0\)\(a_i\) 就分到 \(S_1\)\(dlt=dlt+a_i\),否则分到 \(S_2\)\(dlt=dlt-a_i\)。而剩下的 \(25\) 个元素只需要暴力枚举所有子集即可。时间复杂度为 \(O(n+2^{25}).\)

对于这 \(25\) 个元素的所有子集,一定有元素和小于等于 \(25W\),而一共有 \(2^{25}\) 个子集,比 \(26W\) 大许多,由于是随机数据,几乎一定会出现两个集合元素差为 \(dlt\) 的情况,在这个题这里就是相当于一定有解。

最终用的 \(O(3^n)\) 暴力枚举子集。

「B」Code

template<typename J>
I J Hmin(const J &x,const J &y) {Heriko x<y?x:y;}

CI MXX(1e6+1);

int n,k,m,a[MXX],dlt,co[MXX][3],s[MXX],ans(-1);;

void DFS(int t,int cnt,int lsm,int rsm)
{
    if(~ans) Heriko;

    if(t==m+1)
    {
        if(lsm-rsm==dlt) ans=1;

        Heriko;
    }

    if(cnt<k and !(~ans)) s[t]=0,DFS(t+1,cnt+1,lsm,rsm);

    if(!(~ans)) s[t]=1,DFS(t+1,cnt,lsm+a[t],rsm);

    if(!(~ans)) s[t]=2,DFS(t+1,cnt,lsm,rsm+a[t]);
}

S main()
{
    Files();

    fr(n),fr(k);
    m=Hmin(n,25);int tot[3]={0,0,0};

    for(int i(1);i<=n;++i) fr(a[i]);

    for(int i(26);i<=n;++i)
        if(dlt>0) dlt-=a[i],co[++co[0][1]][1]=i;
        else dlt+=a[i],co[++co[0][2]][2]=i;

    DFS(1,0,0,0);

    if(!(~ans)) {puts("-1");Heriko Deltana;}

    for(int i(1);i<=m;++i) ++tot[s[i]];

    fw(tot[1]+co[0][1],0);
    
    for(int i(1);i<=co[0][1];++i) fw(co[i][1],0);

    for(int i(1);i<=m;++i)
        if(s[i]==1)
            fw(i,0);

    putchar('\n');

    fw(tot[2]+co[0][2],0);

    for(int i(1);i<=co[0][2];++i) fw(co[i][2],0);

    for(int i(1);i<=m;++i)
        if(s[i]==2)
            fw(i,0);

    Heriko Deltana;
}

「C」查询工资

据说是场上最难拿全分的一道题?但是我都爆零了我还管这?(大嘘

「C」题目简述

给定一棵共有 \(n\) 个节点且以 \(1\) 为根节点的树,同时给定参数 \(k.\)

每个节点上有一个权值,现在有两种询问权值的操作(不能直接询问):

  • \(x\) 的子节点个数 \(\ge k\),则可以得到其所有儿子的权值之和。

  • 若以 \(x\) 为根的子树中包含的点 \(>k\),则可以得到这个子树的权值和(包括 \(x\))。

如果一个点是叶子节点,则可以被删除,求经过适当的删除操作之后可以通过询问得到权值的点的个数。

「C」思路简述

先不看如何去删除,先来考虑如何能得到一个结点的权值:

  • 如果 \(x\) 的子树大小 \(>k\),那么它的权值可以通过其父亲为根的子树减去以 \(x\) 为根的子树的权值和得到;

  • 如果 \(x\) 不为叶子且的子树大小 \(\le k\),那么它和它的子树中的点的权值是无法区分的,不能得到 \(x\) 的权值;

  • 如果 \(x\) 为叶子节点,它的权值只能是通过减去其父亲子树中其它结点来得到,那么就需要它满足:

    • \(x\) 没有兄弟;

    • \(x\) 的父亲的父亲(祖父)至少要有 \(k\) 个儿子;

    • \(x\) 的祖父的其它子树要不然是叶子节点,要不然子树大小 \(>k.\)

考虑用 \(f(x)\) 去记录以 \(x\) 为根的子树(不包含自己)中能得到权值的点的最大数量。

那么分别对上面的情况进行考虑:

  • \(x\) 的儿子 \(y\) 出现第一种情况,尝试用 \(f(y)+1\) 更新 \(f(x);\)

  • 若有一个子树大小 \(\le k\) 的儿子,且 \(x\)\(\ge k\) 个儿子,可以将它删成大小为 \(2\) 的子树。那么可以将所有子树大小不足 k+1 的儿子删为叶子,那么此时可以用所有儿子的 \(f_sum+1\) 进行更新。

「C」Code

template<typename J>
I J Hmax(const J &x,const J &y) {Heriko x>y?x:y;}

CI MXX(8e5+1);

int n,k;

struct Node
{
    int nex,to;
}

r[MXX];int cnt,head[MXX];

I void Add(CI &x,CI &y) {r[++cnt]=(Node){head[x],y};head[x]=cnt;}

int sz[MXX],son[MXX],f[MXX];

void DFS(int x)
{
    sz[x]=1;son[x]=f[x]=0;bool flg(0);

    for(int i(head[x]);i;i=r[i].nex)
    {
        int y(r[i].to);

        DFS(y);sz[x]+=sz[y];f[x]+=f[y];++son[x];

        if(!f[y] and sz[y]>1) flg=1;
    }

    if(son[x]>=k and flg) ++f[x];

    for(int i(head[x]);i;i=r[i].nex)
        if(sz[r[i].to]>k)
            f[x]=Hmax(f[x],f[r[i].to]+1);
}

S main()
{
    Files();
    int T,x;fr(T);

    while(T--)
    {
        cnt=0;mst(head,0);

        fr(n),fr(k);

        for(int i(2);i<=n;++i) fr(x),Add(x,i);

        DFS(1);fw(f[1],1);
    }
    
    Heriko Deltana;
}

「D」多项式题

就普遍理性而论,这种标着多项式的题的都不用多项式。

「D」题目简述

有一个长度为 \(n\) 的数字串,定义其一种划分的权值为划分形成的若干个十进制数字的乘积,求其所有划分的权值和,\(n \le 2 \times 10^5.\)

「D」思路简述

场上最一开始读错题了(

\(f(i)\) 为将前 \(i\) 位划分为若干段的价值之和,答案即为 \(f(n).\)

根据乘法分配律可以得到:

\[f(i) = \sum\limits_{j=0}^{i-1}val(j+1,i)\times f(i) \]

其中 \(val(x,y)\) 代表第 \(x\) 到第 \(y\) 位置表示的十进制数字。

直接计算这个柿子的复杂度为 \(O(n^2)\),过不去,于是考虑进行优化。

我们从 \(val\) 考虑优化,对于上式,我们有:

\[val(j+1) = val(1,i)-val(1,j) \times 10^{i-j} \]

那么重新定义 \(val(i)\) 为前 \(i\) 位组成的十进制数,原柿就变成了:

\[\begin{aligned} f(i) &= \sum\limits_{j=0}^{i-1}(val(i)-val(j) \times 10^{i-j})\times f(i) \\ &=val(i)\sum\limits_{j=0}^{i-1}f(i)-10^i\sum\limits_{j=0}^{i-1}10^{-j} \times val(j) \times f(j) \\ \end{aligned} \]

我们再用一步优化,即设 \(g(i)=g(i-1)+f(i),h(i)=h(i-1)+10^{-i}\times val(i) \times f(i).\)

这样就能在 \(O(n)\) 的时间复杂度下求解 \(f(i).\)

「D」Code

CI MXX(2e5+1),MOD(998244353);

int n;

char s[MXX];

LL f[MXX],inv[MXX],val[MXX],pw10[MXX],g[MXX],h[MXX];

I LL FstPow(LL x,LL y)
{
    LL res(1);

    while(y)
    {
        if(y&1) (res*=x)%=MOD;

        (x*=x)%=MOD;y>>=1;
    }

    Heriko res;
}

S main()
{
    Files();
    fr(n);scanf("%s",s+1);pw10[0]=f[0]=g[0]=1;
    
    for(int i(1);i<=n;++i) pw10[i]=pw10[i-1]*10%MOD;

    for(int i(0);i<=n;++i) inv[i]=FstPow(pw10[i],MOD-2);

    for(int i(1);i<=n;++i) val[i]=(val[i-1]*10+s[i]-48+MOD)%MOD;

    for(int i(1);i<=n;++i)
    { 
        f[i]=((val[i]*g[i-1]%MOD)-(pw10[i]*h[i-1]%MOD)+MOD)%MOD;
        g[i]=(g[i-1]+f[i])%MOD;
        h[i]=(h[i-1]+(val[i]*f[i]%MOD*inv[i]%MOD)+MOD)%MOD;
    }

    fw(f[n],1);

    Heriko Deltana;
}

「终」

其实还有好多没写完(

所以时间上的顺序好像也不太对的样子。

全是正睿,氪金多了属于是(

距离 CSP-S2 就 10 天了,RP++.

posted @ 2021-10-13 16:01  HerikoDeltana  阅读(57)  评论(0编辑  收藏  举报