总之就是 | ZROI NOIP联测 Day2

「0.0」序言

我是傻逼。

按照惯例还是简单描述题面,毕竟私题。虽然感觉描述的和原题面差不多了(

照例缺省源:

#include <iostream>
#include <stdio.h>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <vector>

#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(' ');
}

「1.0」小游戏竟成阴影

\(2^n\) 个人玩剪刀石头布,且每个人出的手势固定。已知共有 \(r\) 个人会出石头,\(p\) 个人出布,\(s\) 个人出剪刀,保证 \(r+p+s=2^n\)

比赛的顺序是 \(1\)\(2\)\(3\)\(4\)……

数据范围 \((n \le 15)\)

「1.1」思路简述

实际上很显然能看出来整个的比赛流程是一颗满二叉树,因此最后的胜利者是根。

因为输赢关系是确定的,所以我们在确定了一个节点的根之后,就能确定整个二叉树,也就是说能得到一个对应的序列。

但是题目要求我们输出一个字典序最小的序列,所以我们在生成这个序列的时候需要贪心的按照 P R S 的顺序去生成。

考场写的 DFS,但是很不幸写挂了,甚至写出了阴影,于是现在改为其它大佬的方程写法。简单来说就是设方程为 ans(dep,ri,pi,si),表示在第 dep 层的序列。

这里是直接用的 string 拼接,所以写起来会简单很多。

「1.2」Code

int t,n,r,p,s;

string ans[16][2][2][2];

I void Pre()
{
    ans[0][1][0][0]="P",ans[0][0][1][0]="R",ans[0][0][0][1]="S";

    for(int i(1);i<=15;++i)
        if((1<<i)%3==1)
        {
            ans[i][0][1][0]=ans[i-1][1][1][0]+ans[i-1][0][1][1];
            ans[i][1][0][0]=ans[i-1][1][1][0]+ans[i-1][1][0][1];
            ans[i][0][0][1]=ans[i-1][1][0][1]+ans[i-1][0][1][1];
        }
        else if((1<<i)%3==2)
        {
            ans[i][0][1][1]=ans[i-1][0][1][0]+ans[i-1][0][0][1];
            ans[i][1][1][0]=ans[i-1][1][0][0]+ans[i-1][0][1][0];
            ans[i][1][0][1]=ans[i-1][1][0][0]+ans[i-1][0][0][1];
        }
    
}

S main()
{
    Pre();fr(t);
    
    while(t--)
    {
        fr(r),fr(p),fr(s);n=r+p+s;

        if(r-p>=2 or r-s>=2 or p-s>=2 or p-r>=2 or s-r>=2 or s-p>=2) {puts("-1");continue;}

        short res(-1);int dep(n);

        while(dep)
        {
            ++res;
            dep>>=1;
        }

        puts(ans[res][p-(n/3)][r-(n/3)][s-(n/3)].c_str());
    }
    
    Heriko Deltana;
}

「2.0」暴力分竟成全部

给出一个数字 \(a\),以及一个含 \(k\) 个数的数位集合 \(d_1 \cdots d_k\),求一个最小的无前缀 \(0\) 且不包含数位集合 \(d\) 中任意元素的 \(b = ax \ (x \in \Z^+)\)

若无解输出 \(-1\),保证 \(b\) 的位数 \(\le 10^6,a \le 10^6,k \le 10\)

「2.1」思路简述

这个题目涉及到了数位相关,这就很容易让人想到数位 DP,但是这个数据范围太大了,数位 DP 显然不是正解。

因为考场上写 A 写心态炸了,所以 B 就随便敲了个暴力。考场上没那么大胆只放了 \(b \le 10^5\)\(30\) pts,但是实际上暴力能跑 \(b \le 10^9\)\(50\) pts。然后就™只得了这 30 分

那么如果不用数位 DP 的话,我们就考虑如何去实现题目中的条件。题目中最重要的条件就是两个:倍数关系和不包含 \(d_i\) 这个数。

先考虑第一个,\(a\) 的倍数除了题面中的描述外,还能表现为 \(b \bmod a = 0\)。而满足这样条件的数可以从一个 \(\bmod a \ne 0\) 的数 \(x\) 变换最低位得到。

同时注意到,设数 \(t \bmod a = dlt\),那么在 \(t\) 后面加上一位 \(\beta\) 之后对 \(a\) 取模的结果显然是和 \((dlt \times 10 + \beta) \bmod a\) 等价,于是这样就之和模之后的余数有关而和 \(t\) 无关了。根据题意我们要找到最小的 \(b\),于是我们只需要 BFS 枚举填充下一位,不能出现的判掉即可,这样哦我们就解决了这两个问题。

「2.2」Code

CI MXX(2e6+1);

struct node
{
    int val,from,dlt;
}

r[MXX];

void FW(int x) {if(!x) Heriko;FW(r[x].from);putchar(r[x].val+'0');}

bool vis[MXX],d[11];

S main()
{   
    queue<int> q;int a,k,cnt;
    fr(a),fr(k);

    for(int i(1),x;i<=k;++i) fr(x),d[x]=1;

    r[0]=(node){0,-1,0};q.push(0);

    while(q.size())
    {
        int x(q.front());q.pop();

        for(int i(r[x].dlt?0:1);i<=9;++i)
        {
            if(!d[i])
            {
                int y(((r[x].dlt<<3)+(r[x].dlt<<1)+i)%a);

                if(!vis[y])
                {
                    vis[y]=1;r[++cnt]=(node){i,x,y};

                    if(!y)
                    {
                        FW(cnt);
                        Heriko Deltana;
                    }
                    
                    q.push(cnt);
                }
            }
        }
    }

    puts("-1");

    Heriko Deltana;
}

「3.0」图论题竟成 DP

给定一个 \(2n\ (n \le 25)\) 个点的二分图,最初有 \(m\) 条边,要求添加尽可能少的边使得随意选取 \(n\) 条边形成的都是完全二分图

「3.1」思路简述

如果要满足条件显然需要 \(n^2\) 条边,否则不能构成完全二分图。那么最差的情况下要连接 \(n^2-m\) 条边,但是整个过程实际上是连通块之间的合并。

于是不断去枚举连通块合并,也就是不断的枚举点形成连通块,再进一步合并直到满足要求。对于每个连通块我们维护其左右部分各有多少点,对于形态一样的连通块我们可以剪枝。

「3.2」Code

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

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

CI MXX(100005);

int t,n,fa[MXX],lsz[MXX],rsz[MXX],m,sum,ans;

bool vis[MXX];

vector< pair < int , int > > v;

char s[MXX];

int Find(int x)
{
    if(fa[x]!=x) fa[x]=Find(fa[x]);

    Heriko fa[x];
}

I void Uni(const int &x,const int &y)
{
    int fx(Find(x)),fy(Find(y));

    if(fx!=fy)
    {
        fa[fx]=fy;
        lsz[fy]+=lsz[fx];rsz[fy]+=rsz[fx];
    }
}
/*参数分别代表已经处理的联通块个数(k),选择的上一个要合成的联通块的位置(lst),

钦定要合并的连通块的左部点的个数之和(tot),

单个左部点个数(lt),单个右部点个数(rt),当前并入的联通块多出的右部点个数(dlt)*/
void DFS(int k,int lst,int tot,int lt,int rt,int dlt)
{
    if(sum+Hmax(lt,rt)>=ans) Heriko;

    if(!(~lst)) {if(k==v.size()) {ans=Hmin(ans,sum+lt);Heriko;}}
    else
    {
        sum+=((tot+Hmax(0,dlt))*(tot+Hmax(0,dlt)));

        if(dlt>0) {if(lt>=dlt) DFS(k,-1,0,lt-dlt,rt,0);}
        else {if(rt>=-dlt) DFS(k,-1,0,lt,rt+dlt,0);}

        sum-=((tot+Hmax(0,dlt))*(tot+Hmax(0,dlt)));
    }

    pair<int,int> now(make_pair(0,0));

    for(int i(lst+1);i<(int)v.size();++i)
        if(!vis[i] and v[i]!=now)/*剪枝*/
        {
            now=v[i];vis[i]=1;
            DFS(k+1,i,tot+v[i].first,lt,rt,dlt-v[i].first+v[i].second);
            vis[i]=0;
        }
}


S main()
{
    fr(t);

    while(t--)
    {
        fr(n);int k(n<<1);

        for(int i(1);i<=k;++i)
        {
            fa[i]=i;
            lsz[i]=(i<=n);
            rsz[i]=(i>n);
        }

        ans=n*n;sum=m=0;v.clear();

        for(int i(1);i<=n;++i)
        {
            scanf("%s",s+1);
            for(int j(1);j<=n;++j)
                if(s[j]=='1') 
                    ++m,Uni(i,j+n);
        }

        int lt(0),rt(0);

        for(int i(1);i<=k;++i)
            if(fa[i]==i)
            {
                if(lsz[i]==rsz[i]) sum+=(lsz[i]*lsz[i]);
                else if(!rsz[i]) ++lt;
                else if(!lsz[i]) ++rt;
                else v.push_back(make_pair(lsz[i],rsz[i]));
            }
        sort(v.begin(),v.end());
        DFS(0,-1,0,lt,rt,0);fw(ans-m,1);
    }
    
    Heriko Deltana;
}

「4.0」图论题又成 DP

有一张有向图,有 \(2n^2\) 个,分成 \(n\) 组,每组有 \(2n\) 个点。

对于每一组内,对于所有 \(1≤i<n\) 都有 \(i→i+1,n+i→n+i+1\) 的边,对于所有 \(1≤i≤n\) 都有 \(i→n+i\) 的边。

然后对于所有 \(2≤i≤n\),第 \(1\) 组的 \(i+n−1\) 号点向第 \(i\) 组的 \(1\) 号,第 \(1\) 组的 \(i\) 号点向第 \(i\) 组的 \(n+1\) 号点都有连边。求这张图的拓扑序个数,答案很大,对读入的 \(mod\) 取模。

\(n=3\) 时图为:

「4.1」思路简述

虽然题目写的是图论成 DP,但是这种计数类问题显然是需要 DP 思想的。

发现如果选择点 \((1,i)\)\((1,n+i-1)\) 之后(当然前提是这两个点能选),就有一些点独立出来了,也就是说其它点不会影响它的拓扑序,于是第一组点如何去选就不会影响到这一些了。

于是我们考虑对第一组做 DP。

\(f(i,j)\) 表示第一组第一行选了 \(i\) 个点,第二行选了 \(j\) 个点的方案数,\(g(i,j)\) 表示当前位置可行的排列数量。

更加详细的可以去 Dfkuaid 的笔记查看(

当然不是我懒,只是因为我太菜不会描述(

「4.2」Code

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

CI NXX(3001),MXX(1.8e7+1);

int n,fac[MXX],inv[MXX],mod;

LL f[NXX][NXX],g[NXX][NXX];

int C(int a,int b)
{
    if(a-b<mod and mod<=a) Heriko Deltana;

    Heriko 1ll*fac[a]*inv[b]%mod*inv[a-b]%mod;
}

S main()
{   
    fr(n),fr(mod);int m((n*n)<<1);

    fac[0]=inv[0]=inv[1]=1;

    for(int i(1);i<=m;++i)
        if(i==mod) fac[i]=fac[i-1];
        else fac[i]=1ll*fac[i-1]*i%mod;
    
    for(int i(2);i<=m;++i)
        if(i>=mod) inv[i]=inv[i-mod];
        else inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;

    for(int i(1);i<=m;++i) inv[i]=1ll*inv[i-1]*inv[i]%mod;

    f[1][0]=1;g[n][n]=1;

    for(int i(n);~i;--i)
        for(int j(Hmin(n-1,i));~j;--j)
            g[i][j]=(g[i+1][j]+g[i][j+1])%mod;
    
    for(int i(1);i<=n;++i)
        for(int j(0);j<i;++j)
        {
            int dlt((n<<1)*(n-j)-i-j);

            if(j==i-1)
            {
                if(i==n) {fw(f[i][j],1);Heriko Deltana;}

                (f[i+1][j+1]+=1ll*f[i][j]*C(dlt-2,n<<1)%mod*g[1][0]%mod)%=mod;

                for(int k(1);k<=n;++k) (f[i+1][j+1]+=1ll*f[i][j]*C(dlt-k-2,(n<<1)-k)%mod*g[k][0]%mod)%=mod;
            }
            else (f[i][j+1]+=1ll*f[i][j]*C(dlt-1,n<<1)%mod*g[1][0]%mod)%=mod;

            if(i<n) (f[i+1][j]+=f[i][j])%=mod;
        }

    Heriko Deltana;
}

「5.0」尾声

嗯最后成绩就那 \(30\) pts.

posted @ 2021-09-07 17:11  HerikoDeltana  阅读(71)  评论(2编辑  收藏  举报