2023牛客暑期多校训练营3

World Fragments I 签到结论题

 

Auspiciousness 组合数学 计数DP

Ama no Jaku 扩展与并查集(01并查集)结论

Koraidon, Miraidon and DFS Shortest Path  BFS树上建立支配树,拓扑排序,LCA

Until the Blue Moon Rises 哥德巴赫猜想,分类讨论

Fine Logic 拓扑序,构造

Beautiful Matrix  字符串哈希,Manacher回文串

2023牛客暑期多校训练营3_第1张图片

本题有“三必拿走” ,第一个必拿走,第二个必定拿走,第i个猜对与否,第i+1个必被拿走。有“一不拿走" 第i个猜错,第i+2个一定不再去拿dp[i][j][0/1]代表当前选了i个数字 [第i个数字正在猜] ,j个第一类数字,当前是第几类的方案数。无论第i+1个数字是否被猜对,我们都会选它。故直接由i状态推出选i+1时对答案的贡献。dp存的仅仅是方案数。初始时,dp[0][0][0]=dp[0][0][1]=1,答案直接加上拿了一张牌的方案数,ans+=fac[2n],值得注意的时,这里的“拿了一张牌”指的是全部至少拿了一张牌的方案数,也把拿了三张,两张的“第一张”都包含在内了,故采用方案数可以巧妙的把方案数的累加变成总个数的累加。而后dp[1][1][0]=n,dp[1][0][1]=n,即第一个位置填<=n的有n种方法,>n的也有n种。在此基础上,更新拿了两张牌的方案数 ,(dp[1][1][0]+dp[1][0][1])*fac[2n-1] 即顺利拿了第一张牌后,剩下的2n-1张牌乱拍即可dp[i>=2]的更新方式为每次确立当前能选的第一类数字的[L,R]区间,L=max(0,i-n) R =min(i,n) 然后分两种情况讨论。即当前i应该选择第一类数还是第二类数,i属于的那一段的长度。一旦我们确定了i选第几类数,以第一类数为例,又已知i之前(包括i)第一类数字的个数,那么我们只需要直接组合数学选数即可。按照规则来看,一旦数种类选定,则顺序既定,而顺序既定,则顺序正确,而顺序正确,则证明能够“安全到达i",也就是能进一步选择i+1.综上,本题更像是一道分贡献的组合数学题

# include
using namespace std;
typedef long long int ll;
ll C[610][610],fac[610];
ll dp[610][310][2];

void init(int n,int mod)
{
    C[0][0]=1;
    C[1][0]=C[1][1]=1;
    for(int i=2;i<=n;i++)
    {
        C[i][0]=C[i][i]=1;
        for(int j=1;j>t;
    while(t--)
    {
        int n,m;
        cin>>n>>m;
        init(2*n,m);
        for(int i=0;i<=2*n;i++)
        {
            for(int j=0;j<=n;j++)
            {
                dp[i][j][0]=dp[i][j][1]=0;
            }
        }
        dp[0][0][0]=dp[0][0][1]=1;
        ll ans=0;
        for(int i=1;i<=n+n;i++)
        {
           int l=max(0,i-n);
           int r=min(i,n);
           for(int j=l;j<=r;j++)
           {
               for(int k=1;k<=j;k++)
               {
                   dp[i][j][0]=(dp[i][j][0]+dp[i-k][j-k][1]*C[n-(j-k)][k]%m)%m;
               }
               for(int k=1;k<=i-j;k++)
               {
                   dp[i][j][1]=(dp[i][j][1]+dp[i-k][j][0]*C[n-(i-j-k)][k]%m)%m;
               }
               if(i!=n+n)
               {
                   ans=(ans+(dp[i][j][0]+dp[i][j][1])%m*fac[n+n-i]%m)%m;
               }
           }
        }
        cout<<(ans+fac[n+n])%m<<'\n';
    }
    return 0;
}

2023牛客暑期多校训练营3_第2张图片

 首先根据样例猜测结论为,只有当全为1或者全为0的时候才能满足要求。那么现在问题变成,给定目标矩阵,每次翻转行或者列,求最小翻转次数。可以利用扩展与并查集,这里为01并查集,代表两种不同的奇偶。当目标矩阵为全1时,若某行为1,则证明其行和列的翻转奇偶性不同,反之相同。做完并查集之后,最多有两个集合。两个集合时,奇偶性不同。也就是一个不反转,一个翻转。让较小者翻转即可。一个集合时,根本不需要翻转。

# include
using namespace std;
typedef long long int ll;
char s[2020][2020];
int f[100000+10],n;
int getf(int x)
{
    if(x==f[x])
        return f[x];
    else
    {
        f[x]=getf(f[x]);
        return f[x];
    }
}
void hebing(int x,int y)
{
    int t1=getf(x),t2=getf(y);
    if(t1!=t2)
        f[t1]=t2;
}
int check(char now)
{

    for(int i=1; i<=4*n; i++)
    {
        f[i]=i;
    }
    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<=n; j++)
        {
            if(s[i][j]!=now)
            {
                hebing(i,j+3*n);
                hebing(j+n,i+2*n);
            }
            else
            {
                hebing(i,j+n);
                hebing(i+2*n,j+3*n);
            }
        }
    }
    for(int i=1; i<=n; i++)
    {
        if(getf(i)==getf(i+2*n))
            return -1;
        if(getf(i+n)==getf(i+3*n))
            return -1;
    }
    mapcnt1;
    for(int i=1; i<=n; i++)
    {
        cnt1[getf(i)]++;
        cnt1[getf(i+n)]++;
    }
    int minn1=2*n;
    for(auto it:cnt1)
        minn1=min(minn1,it.second);

    return min(2*n-minn1,minn1);
}
int main ()
{

    cin>>n;
    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<=n; j++)
        {
            cin>>s[i][j];
        }
    }
    int ans1=check('0');
    int ans2=check('1');
    if(ans1==-1)
    {
        cout<<-1;
        return 0;
    }
    cout<

2023牛客暑期多校训练营3_第3张图片

边权为1,BFS得出的正确dis为最短路。DFS乱序遍历的结果,要想保证正确。需要避免以下情况。首先给定图的最短路已经确定。

若有u->v一条边(u!=v)

dis[u]=dis[v]时,一定非法,即先到u后会搅乱v的最短路。

dis[u]v的边,则这条边就是最短路边,不会对答案造成影响

dis[u]>dis[v]时,若到u之前,v一定会被遍历到,则v已经被标记,最短路不会受到影响。此时v在u最短路的路径上,这点很重要,根据这一点可以得出结论,u v只有在BFS生成树上才可能产生合法的u->v连接。

所以,题目变成了一个求支配树的题目。可以直接套用任意图支配树,也可以根据BFS树的性质---有向无环图(DAG),直接跑一个较为容易实现的有向无环图上的支配树即可。

先进行BFS,若边为最短路边,直接加入新图,若最短路不唯一,也要加入新图(防止将非支配点误判为支配点)。

而DAG上的支配树,从1号点开始拓扑排序,每当遇到度数为0的点,求这些点的LCA,LCA指在支配树上的LCA,然后将这一LCA改为这一点的直系父亲,直系父亲指的也是支配树上的直系父亲。这样拓扑排序后,支配树上一点的全部祖先,都是其支配点。

# include
using namespace std;
typedef long long int ll;
vectorv[500000+10];
vectorpre[500000+10],vv[500000+10];
int book[500000+10],dis[500000+10];
int x[500000+10],y[500000+10];
int fa[500000+10][31],du[500000+10],dep[500000+10];
void swim(int &x,int cha)
{
    for(int i=0;i<=30;i++)
    {
        if((cha)&(1<=0;i--)
    {
        if(fa[x][i]!=fa[y][i])
        {
            x=fa[x][i];
            y=fa[y][i];
        }
    }
    return fa[x][0];
}
void work(int now)
{
    int lca=pre[now][0];
    for(auto it:pre[now])
    {
        lca=getlca(lca,it);
    }
    dep[now]=dep[lca]+1;
    fa[now][0]=lca;
    for(int i=1;i<=20;i++)
    {
        fa[now][i]=fa[fa[now][i-1]][i-1];
    }
}
int main()
{
   
    int t;
    cin>>t;
    while(t--)
    {
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
        {
            v[i].clear();
            pre[i].clear();
            vv[i].clear();
            book[i]=0;
            dis[i]=1e9;
            du[i]=0;
            dep[i]=0;
        }
        for(int i=0;i<=n;i++)
        {
            for(int j=0;j<=20;j++)
            {
                fa[i][j]=0;
            }
        }
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&x[i],&y[i]);
            if(x[i]==y[i])
                continue;
            v[x[i]].push_back(y[i]);
        }
        queueq;
        q.push(1);
        book[1]=1;
        dis[1]=0;
        while(!q.empty())
        {
            int now=q.front();
            q.pop();
            for(auto it:v[now])
            {
                if(book[it])
                {
                    if(dis[now]+1==dis[it])
                    {
                        pre[it].push_back(now);
                        vv[now].push_back(it);
                        du[it]++;
                    }
                }
                else
                {
                    dis[it]=dis[now]+1;
                    pre[it].push_back(now);
                    vv[now].push_back(it);
                    book[it]=1;
                    du[it]++;
                    q.push(it);
                }
            }
        }
        q.push(1);
        fa[1][0]=1;
        while(!q.empty())
        {
            int now=q.front();
            q.pop();
            for(auto it:vv[now])
            {
                du[it]--;
                if(du[it]==0)
                {
                    q.push(it);
                    work(it);
                }
            }
        }
        int flag=0;
        for(int i=1;i<=m;i++)
        {
            if(x[i]==y[i])
                continue;
            if(dis[x[i]]==dis[y[i]])
            {
                flag=1;
            }
            if(dis[x[i]]>dis[y[i]])
            {
                if(getlca(y[i],x[i])!=y[i])
                {
                    flag=1;
                }
            }
        }
        if(flag)
        {
            cout<<"No"<<'\n';
        }
        else
        {
            cout<<"Yes"<<'\n';
        }
    }
    return 0;
}

2023牛客暑期多校训练营3_第4张图片

首先没有环的时候,很显然是拓扑序直接搞就行。一旦有环,考虑到构造一种k=2的方式,即1->n n->1两个排列。这样任意x

#include 
using namespace std;
typedef long long ll;
vector v[1000000 + 10];
inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch))
    {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}

inline void write(ll x)
{
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
int chu[1000000 + 10], ru[1000000 + 10];
int book[1000000 + 10], tot;
vector ans;
int vis[1000000 + 10];
int main()
{
    int n, m;
    cin >> n >> m;

    for (int i = 1; i <= m; i++)
    {
        int x, y;
        x = read();
        y = read();
        v[x].push_back(y);
        ru[y]++;
    }
    int k = 0;
    queue q;
    for (int i = 1; i <= n; i++)
    {
        if (ru[i] == 0)
        {
            q.push(i);
        }
    }
    while (!q.empty())
    {
        int now = q.front();
        q.pop();
        tot++;
        ans.push_back(now);
        for (auto it : v[now])
        {
            ru[it]--;
            if (ru[it] == 0)
            {
                q.push(it);
            }
        }
    }
    if (ans.size() == n)
    {
        cout << 1 << endl;
        for (auto it : ans)
        {
            cout << it << " ";
        }
        return 0;
    }
    else
    {
        cout << 2 << endl;
        for (int i = 1;i<=n;i++)
        {
            cout << i << " ";
        }
        cout << endl;
        for (int i = n; i >= 1;i--)
        {
            cout << i << " ";
        }
    }

    return 0;
}

你可能感兴趣的:(多校真题,区域赛,ICPC,算法)