• ZJOI 2022 题目选做


    题目描述

    点此看题

    解法

    考虑这样的一个计数方法:对于一个点,我们考虑其在第一棵树上是叶子,第二棵树上是非叶子;或者在第一棵树上是非叶子,第二棵树上是叶子的状态。那么记录第一棵树前面的非叶节点个数,记录第二棵树后面的非叶节点个数,就可以方便地计算方案数。

    但是这样做会出现一个明显的问题:我们的非叶节点可能并没有被连边,解决这个问题可能需要记录更多的状态。但是我们可以容斥没有被连边的非叶节点数量,每多一个这样的点就记上 \(-1\) 的容斥系数。

    \(f[j][k]\) 表示考虑到当前点,第一棵树前面有 \(j\) 个非叶节点,第二棵树后面有 \(k\) 个非叶节点,状态中的非叶节点是不包含 \(1/n\) 的,所以计算方案数的时候还要把连向它们的边考虑到,转移:

    • \(i\) 在第一棵树上是叶子,第二棵树上是非叶子:\(f[j][k]\leftarrow (j+1)\cdot (k+1)\cdot f[j][k+1]\);如果在第二棵树上是没有被连边的非叶子:\(f[j][k]\leftarrow -(j+1)\cdot (k+1)\cdot f[j][k]\)
    • \(i\) 在第一棵树上是非叶子,第二棵树上是叶子:\(f[j][k]\leftarrow j\cdot (k+1)\cdot f[j-1][k]\);如果在第一棵树上是没有被连边的非叶子:\(f[j][k]\leftarrow -(j+1)\cdot (k+1)\cdot f[j][k]\)

    时间复杂度 \(O(n^3)\)

    总结

    一开始我直接对不合法的状态容斥(都是叶子,都是非叶子),结果复杂度爆炸。

    所以事实证明容斥不只有直接容斥这一种思考方向,本题的逻辑就是先给出一种会算重的计数方法,然后找出这种计数方法算重的原因,然后对这个算重的原因容斥就得到了复杂度很美妙的做法。

    #include <cstdio>
    const int M = 505;
    #define int long long
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,MOD,f[M][M],g[M][M];
    signed main()
    {
        n=read();MOD=read();
        for(int i=0;i<=n;i++) f[0][i]=i+1;
        puts("1");
        for(int i=2;i<n;i++)
        {
            for(int j=0;j<=i;j++)
                for(int k=0;k<=n-i;k++)
                    g[j][k]=f[j][k],f[j][k]=0;
            for(int j=0;j<=i;j++)
                for(int k=0;k<=n-i;k++)
                {
                    int r=0;
                    r+=(j+1)*(k+1)*g[j][k+1];
                    r-=(j+1)*(k+1)*g[j][k];
                    if(j) r+=j*(k+1)*g[j-1][k];
                    r-=(j+1)*(k+1)*g[j][k];
                    f[j][k]=(r%MOD+MOD)%MOD;
                }
            int ans=0;
            for(int j=0;j<=i;j++)
                ans=(ans+(j+1)*f[j][0])%MOD;
            printf("%lld\n",ans);
        }
    }
    

    众数

    题目描述

    点此看题

    解法

    众数问题基本上不能 \(poly\ log\),所以我们着重考虑一些根号算法。

    可以对不同颜色的出现次数根号分治,称出现次数 \(\geq \sqrt n\) 的为大颜色,否则为小颜色。

    首先考虑大颜色对其他颜色的贡献,可以把大颜色打在原来的序列上,做完前缀和之后枚举其他颜色,按顺序扫描,记录前缀最小值就可以做到 \(O(n)\),这一部分总时间 \(O(n\sqrt n)\)

    然后考虑小颜色对其他颜色的贡献,这时候要抓住关键性质:选取的区间众数应当 \(\leq \sqrt n\),那么我们外层枚举众数大小 \(x\),然后双指针就可以获得 \(p_r\) 表示右端点 \(r\) 对应最大左端点,使得众数为 \(x\),得到这个数组之后我们枚举颜色 \(y\),对于 \(y\) 的每一个点,我们双指针维护最大的左端点使得两点间区间众数为 \(x\),贡献就便于计算了,这一部分时间复杂度也是 \(O(n\sqrt n)\)

    #include <cstdio>
    #include <vector>
    #include <iostream>
    #include <algorithm>
    #include <cmath>
    using namespace std;
    const int M = 200005;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int T,n,m,k,a[M],b[M],c[M],s[M],ans[M],p[M],mi[M];
    vector<int> g[M];
    void work()
    {
        n=read();k=sqrt(n);
        for(int i=1;i<=n;i++)
            a[i]=b[i]=read(),c[i]=ans[i]=0,g[i].clear();
        sort(b+1,b+1+n);m=unique(b+1,b+1+n)-b-1;
        for(int i=1;i<=m;i++) g[i].push_back(0);
        for(int i=1;i<=n;i++)
        {
            c[a[i]=lower_bound(b+1,b+1+m,a[i])-b]++;
            g[a[i]].push_back(i);
        }
        for(int i=1;i<=m;i++) g[i].push_back(n+1);
        for(int x=1;x<=m;x++) if(c[x]>=k)
        {
            for(int i=1;i<=n;i++)
                s[i]=s[i-1]+(a[i]==x);
            s[n+1]=s[n];
            for(int y=1;y<=m;y++) if(x^y)
            {
                int mn=0;
                for(int i=1;i<g[y].size();i++)
                {
                    int nw=s[g[y][i]]-i;
                    ans[y]=max(ans[y],nw-mn+1);
                    mn=min(mn,nw);
                }
            }
        }
        for(int x=k;x>=1;x--)
        {
            for(int i=1;i<=m;i++) s[i]=0;
            for(int r=1,l=1;r<=n;r++)
            {
                s[a[r]]++;
                while(s[a[r]]>=x) s[a[l]]--,l++;
                p[r]=l;
            }
            for(int y=1;y<=m;y++) if(ans[y]<x)
            {
                vector<int> &t=g[y];
                for(int i=1,j=0,k=0;i<t.size();i++)
                {
                    if(p[k=t[i]-1]<2) continue;
                    while(j<i && t[j+1]<p[k]-1) j++;
                    if(j<i) ans[y]=max(ans[y],x-(i-j-1));
                }
            }
        }
        int mx=0;
        for(int i=1;i<=m;i++) mx=max(mx,ans[i]+c[i]);
        printf("%d\n",mx);
        for(int i=1;i<=m;i++)
            if(ans[i]+c[i]==mx) printf("%d\n",b[i]);
    }
    signed main()
    {
        T=read();
        while(T--) work();
    }
    
  • 相关阅读:
    Python_离线包下载地址
    JMeter_简单控制线程组(Thread Group)组件的执行顺序
    Python_pkgutil.walk_packages_查询包下所有文件
    Grafana_数据可视化工具
    Python_dir+getattr_获取模块下所有属性对象
    Jmeter_BeanShell Assertion自定义断言
    python连接数据库及使用
    python题目
    div 内容超出部分隐藏
    在C#类库中使用App.config文件自定义配置
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/16277483.html
Copyright © 2020-2023  润新知