• NOIP模拟测试3「序列·熟练剖分·建造游乐园(play)」


    ---恢复内容开始---

    序列

    刚调出来样例就A了,假装是水题。

    因为是乱序,我们要求出来每两项之间最小公比,而不是直接比

    求出来每两项之间最小公比,然后扫一遍就完了。(还要注意重复情况)

    那么问题就转化成了怎么求最小公比。

    完了

    以下是本人丑陋的代码

    #include<bits/stdc++.h>
    #define ll long long
    #define A 100000
    using namespace std;
    ll n,a[10*A],tot=0,maxlen[10*A],nowlen=0,dl[10*A],zuixiaogongbi,prime[10*A],mark[10*A],ges[1010],ml=0,big[A];   
    set <ll> s;
    ll gcd(ll x,ll y)
    {
        return y==0?x:gcd(y,x%y);
    }
    bool pol(ll ooo){
        ll x=ooo;
        for(ll i=1;i<=tot;i++){
            if(prime[i]>x) break;
            while(x!=prime[i]){
                if(x%prime[i]==0){
                    if(!ges[prime[i]])
                        dl[++dl[0]]=prime[i];
                    ges[prime[i]]++;
                    x=x/prime[i];
            }
                else break;
            }
            if(x==prime[i]){
                if(!ges[x])dl[++dl[0]]=x;
                ges[x]++;
                break;
            }
        }
        if(x)return 0;
        return 1;
    }
    bool check(ll x,ll y)
    {
        memset(ges,0,sizeof(ges));
        dl[0]=0;
        if(x<=y) swap(x,y);
        if(x%y) return 0;
        ll z=x/y,gd=1;
        if(pol(z))return 0;
        if(dl[0]!=1)
        {
            gd=gcd(ges[dl[1]],ges[dl[2]]);
            zuixiaogongbi=1;
            for(ll i=3;i<=dl[0];i++)
                gd=gcd(ges[dl[i]],gd);
        }
        else
            gd=ges[dl[1]],zuixiaogongbi=1;
    //    for(ll i=1;i<=dl[0];i++){
    //        printf("ges=%lld dl=%lld 
    ",ges[dl[i]],dl[i]);
    //    }
    //    printf("pd=%lld
    ",gd);
        for(ll i=1;i<=dl[0];i++)
            ges[dl[i]]/=gd,zuixiaogongbi*=ges[dl[i]]*dl[i];
        
        return 1;
    }
    int main()
    {
        for(ll i=2;i<=1000;i++){
            if(!mark[i]){
                prime[++tot]=i;        
            }
            for(ll j=1;j<=tot;j++){
                if(i*prime[j]>1000) break;
                mark[i*prime[j]]=1;
                if(i%prime[j]==0){break;}
            }
        }
        ll man=0;
        scanf("%lld",&n);
        for(ll i=1;i<=n;i++)
            scanf("%lld",&a[i]);
        ll len=1;
        for(ll i=2;i<=n;i++)
            if(a[i]==a[i-1]){len++;}
            else{man=max(man,len);len=1;}
        man=max(man,len);
        for(ll i=2;i<=n;i++){
            if(check(a[i],a[i-1]))
            big[i]=zuixiaogongbi;
            else big[i]=-1;
        }
        len=1;s.insert(a[1]);
        for(ll i=2;i<=n;i++)//枚举开头
        {
            if((big[i]!=-1)&&(big[i]==big[i-1]||big[i-1]==0||s.size()==1))
            {
                ll x=a[i];
                bool ok=0;
                if((s.find(x))==s.end())
                {
    //                printf("insert a[%lld]=%lld big[%lld]=%lld big[%lld]=%lld
    ",i,a[i],i-1,big[i-1],i-2,big[i-2]);
                    s.insert(x);
                    ll sz=s.size();
                    man=max(man,sz);
                }
                else 
                {
    //                printf("因重复而清空 i=%lld
    ",i);
                    ll sz=s.size();
                    man=max(man,sz);
                    s.clear();s.insert(a[i]);
                    ll ss=s.size();
    //                printf("目前size=%lld
    ",ss);
                }
            }
            else 
            {
                
                ll sz=s.size();
    //            printf("因不相等而清空 i=%lld sz=%lld
    ",i,sz);
                man=max(man,sz);
                s.clear();
                s.insert(a[i]);
            }
            ll w=s.size();
    //        printf("w=%lld
    ",w);
        }
        ll sz=s.size();
        man=max(man,sz);
        cout<<man<<endl;
    }
    View Code

    熟练剖分

    这个题还是挺好的。

    这是学长的题解

    时间复杂度为O(n^2)的树上dp,关键在如何设计状态以及如何合并dp数组

    对于这个关键部分可以有很多种不同的设计,欢迎同学们积极设计自己的状态定义以及转移方式

    我分享一下我的做法

    f[i][j]表示对于点i,其子树内最大代价为j的方案数

    转移方式为

    1. dfs为大框架进行
    2. 对于每个节点先处理所有的儿子节点,最后将已获得的所有子节点信息进行合并得到该节点信息
    3. 合并时,依次将每一个子节点的信息纳入暂时的动态的一个dp数组储存,该dp数组分为0/1两个数组,大概长成g[0/1][j]这个样子(可以使用滚动数组变成g[0/1][0/1][j]0数组表示之前的子节点中不含重边的情况,1表示之前的子节点中已含有重边,j表示对应状况下,最大代价为j(不是前缀和),数组内存储信息为该情况下的方案数
    4. 转移就简单了,枚举g数组的每一种情况以及正在合并的子节点dp数组的每一种情况,进行转移,转移时注意代价与重边的变化
    5. 这个时间复杂度的计算来自于点对总数,所以一定要把合并时的时间复杂度准确控制,不要错误写成O(n^3)dp
    #include<bits/stdc++.h>
    #define ll long long
    #define A 10000
    using namespace std;
    const ll mod=1e9+7;
    ll g[2][2][A],f[A][A],son[A][A],size[A],n,sum;
    bool flag[A];
    inline ll meng(ll x,ll k){
        ll ans=1;
        for(;k;k>>=1,x=x*x%mod)
            if(k&1)
                ans=ans*x%mod;
        return ans;
    }
    
    void dfs(ll x){
        if(!son[x][0]){f[x][0]=1;return ;}
        for(ll i=1;i<=son[x][0];i++){
            ll y=son[x][i];
            dfs(y);size[x]=max(size[x],size[y]+1);
        }
        ll cur=0,maxn=size[son[x][1]]+1;
        memset(g,0,sizeof(g));
        for(ll i=1;i<=maxn;i++){
            g[0][0][i]=f[son[x][1]][i-1];//因为g选择的是x的儿子,当前面没有选择重链时应该由i-1转移过来
            g[0][1][i]=f[son[x][1]][i];//因为g选择了重链,所以i代价应该减1
        }
        g[0][1][0]=f[son[x][1]][0];
        for(ll i=2;i<=son[x][0];i++){
            memset(g[cur^1],0,sizeof(g[cur^1]));
            for(ll j=0;j<=maxn;j++){
                for(ll k=0;k<=size[son[x][i]];k++){//枚举儿子的深度
                    g[cur^1][0][max(j,k+1)]=(g[cur^1][0][max(j,k+1)]+f[son[x][i]][k]*g[cur][0][j])%mod;//还没有重链
                    g[cur^1][1][max(j,k+1)]=(g[cur^1][1][max(j,k+1)]+f[son[x][i]][k]*g[cur][1][j])%mod;//之前的点已经有了重链
                    g[cur^1][1][max(j,k)]=(g[cur^1][1][max(j,k)]+f[son[x][i]][k]*g[cur][0][j])%mod;//选择当前点为重链
                }
            }
            cur^=1;
            maxn=max(maxn,size[son[x][i]]+1);
        }
        memcpy(f[x],g[cur][1],sizeof(f[x]));
    }
    int main(){
        scanf("%lld",&n);
        sum=1;
        for(ll i=1;i<=n;i++){
            scanf("%lld",&son[i][0]);
            if(son[i][0]){
                for(ll j=1;j<=son[i][0];j++){
                    ll r;scanf("%lld",&r);
                    son[i][j]=r;
                    flag[r]=1;
                }
            }
            (sum*=((son[i][0]==0)?1:meng(son[i][0],mod-2)))%=mod;
        }
        for(ll i=1;i<=n;i++){
            if(!flag[i]){
                dfs(i);
                ll ans=0;
                for(ll j=1;j<=size[i];j++)
                    {ans=(ans+f[i][j]*j)%mod;}
                ans=sum*ans%mod;
                printf("%lld
    ",ans);
                return 0;
            }
        }
    }
    View Code

    建造游乐园

    首先我们要知道无向图的一条性质,一个无向图一定有偶数个度数为奇数的点。
    那么我们在i-1中随意连边(可连可以不连,共2^种情况)$C_{i-1}^{2}$是哪两个点之间连边的情况,而$2^{C_{i-1}^{2}}$则是每个边都可以选择或者不选,

    那么即使有度数为奇数的点,我们让i与它相连就保证了i点入度仍为偶数,并且其他奇数入度点变成了偶数。
    这样我们就保证了它一定是偶数条边

    设g=$2^{C_{i-1}^{2}}$(为可能不连通的欧拉图)   f为符合的连通的欧拉图
    因为欧拉图必须所有点都入度为偶数
    只要有入度为奇数的点就不符合
    于是我们用一个类似于容斥的东西只要有不联通就不行。

    g[j]中本身包含只有一个点连通,只有两个点连通一直到j个点连通我们让g[j]减去1--j-1的连通情况就构成了j个点连通。

    我们让f包含i点,我们还要从剩余i-1个点中,选择j-1个点使它与i点组成连通图f[j]。

    剩余i-j个点随意连(g图与f图完全分割,没有连边)

    然后我们从i-1个点中选择j-1个让j-1个点与i相连。

    于是我们得到了:
    $f[i]=g[i]-sum limits_{j=1}^{i-1}f[j]*g[i-j]*{C_{i-1}^{j-1}}$

    至于为什么不是i个里面选,会选重复

    完了

    以下是本人丑陋的代码

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    #define A 2100
    const ll mod=1000000007;
    ll n,sum[A],c=0,ans,cishu,C[A][A],g[A],f[A];
    ll meng(ll x,ll k){
        ll ans=1;
        for(;k;k>>=1,x=x*x%mod)
            if(k&1)    ans=ans*x%mod;
        return ans;
    }
    int main()
    {
        scanf("%lld",&n);
        C[0][0]=1;
        for(ll i=1;i<=n;i++) C[i][0]=1;
        for(ll i=1;i<=n;i++)
            for(ll j=1;j<=i;j++)
                C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
        for(ll i=1;i<=n;i++)
            g[i]=meng(2,C[i-1][2]);
        //首先我们要知道无向图的一条性质,一个无向图一定有偶数个度数为奇数的点
        //那么我们在i-1中随意连边(可连可以不连,共2^种情况),即使有度数为奇数的点我们相连仍为偶数
        for(ll i=1;i<=n;i++){
            f[i]=g[i];
            for(ll j=1;j<=i-1;j++){
                f[i]-=f[j]*g[i-j]%mod*C[i-1][j-1]%mod;
                //乘i-1,j-1原因。很简单,我们从i-1个数中选,让他们与i相连
            }
            f[i]=(f[i]%mod+mod)%mod;
        }
        cout<<(f[n]*C[n][2]%mod+mod)%mod<<endl;
    }
    View Code


     

    ---恢复内容结束---

    序列

    刚调出来样例就A了,假装是水题。

    因为是乱序,我们要求出来每两项之间最小公比,而不是直接比

    求出来每两项之间最小公比,然后扫一遍就完了。(还要注意重复情况)

    那么问题就转化成了怎么求最小公比。

    完了

    以下是本人丑陋的代码

    View Code

    熟练剖分

    这个题还是挺好的。

    一切尽在代码之中

    tj
    View Code

    建造游乐园

    首先我们要知道无向图的一条性质,一个无向图一定有偶数个度数为奇数的点。
    那么我们在i-1中随意连边(可连可以不连,共2^种情况)$C_{i-1}^{2}$是哪两个点之间连边的情况,而$2^{C_{i-1}^{2}}$则是每个边都可以选择或者不选,

    那么即使有度数为奇数的点,我们让i与它相连就保证了i点入度仍为偶数,并且其他奇数入度点变成了偶数。
    这样我们就保证了它一定是偶数条边

    设g=$2^{C_{i-1}^{2}}$(为可能不连通的欧拉图)   f为符合的连通的欧拉图
    因为欧拉图必须所有点都入度为偶数
    只要有入度为奇数的点就不符合
    于是我们用一个类似于容斥的东西只要有不联通就不行。

    g[j]中本身包含只有一个点连通,只有两个点连通一直到j个点连通我们让g[j]减去1--j-1的连通情况就构成了j个点连通。

    我们让f包含i点,我们还要从剩余i-1个点中,选择j-1个点使它与i点组成连通图f[j]。

    剩余i-j个点随意连(g图与f图完全分割,没有连边)

    然后我们从i-1个点中选择j-1个让j-1个点与i相连。

    于是我们得到了:
    $f[i]=g[i]-sum limits_{j=1}^{i-1}f[j]*g[i-j]*{C_{i-1}^{j-1}}$

    至于为什么不是i个里面选,会选重复

    完了

    以下是本人丑陋的代码

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    #define A 2100
    const ll mod=1000000007;
    ll n,sum[A],c=0,ans,cishu,C[A][A],g[A],f[A];
    ll meng(ll x,ll k){
        ll ans=1;
        for(;k;k>>=1,x=x*x%mod)
            if(k&1)    ans=ans*x%mod;
        return ans;
    }
    int main()
    {
        scanf("%lld",&n);
        C[0][0]=1;
        for(ll i=1;i<=n;i++) C[i][0]=1;
        for(ll i=1;i<=n;i++)
            for(ll j=1;j<=i;j++)
                C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
        for(ll i=1;i<=n;i++)
            g[i]=meng(2,C[i-1][2]);
        //首先我们要知道无向图的一条性质,一个无向图一定有偶数个度数为奇数的点
        //那么我们在i-1中随意连边(可连可以不连,共2^种情况),即使有度数为奇数的点我们相连仍为偶数
        for(ll i=1;i<=n;i++){
            f[i]=g[i];
            for(ll j=1;j<=i-1;j++){
                f[i]-=f[j]*g[i-j]%mod*C[i-1][j-1]%mod;
                //乘i-1,j-1原因。很简单,我们从i-1个数中选,让他们与i相连
            }
            f[i]=(f[i]%mod+mod)%mod;
        }
        cout<<(f[n]*C[n][2]%mod+mod)%mod<<endl;
    }
    View Code


     

    我已没有下降的余地
  • 相关阅读:
    小程序云开发(一)
    原来你是这样的"layui"啊
    记个笔记(项目中遇到的关于input的一些操作)
    vue的学习之路 vue-cli与axios
    小程序 上传图片
    文字滚动
    css 隐藏滚动条
    linux基础
    接口测试
    整理python循环,列表,字典笔记
  • 原文地址:https://www.cnblogs.com/znsbc-13/p/11187024.html
Copyright © 2020-2023  润新知