2021.5.22 noip模拟1

Posted hzoi------fengwu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2021.5.22 noip模拟1相关的知识,希望对你有一定的参考价值。

这场考试考得很烂

连暴力都没打好

只拿了25分,,,,,,,,好好总结

T1序列

 

A. 序列

 

题目描述

HZ每周一都要举行升旗仪式,国旗班会站成一整列整齐的向前行进。
郭神作为摄像师想要选取其中一段照下来。他想让这一段中每个人的身高成等比数列,展示出最萌身高差,但他发现这个太难办到了,于是他决定放低要求,让等比数列的每两项之间可以是不连续的(例如 2,4,16……)。可他依然找不到满意的,便再次妥协,使这个等比数列可以是乱序的。
现在请在其中你找出最长的符合要求的一段,使得将这一段排序后为某个公比为q的等比数列的子序列(q∈N*,q<=1000)。

 

输入格式

第一行一个正整数n,表示有n个人。
接下来n行每行一个正整数a1,a2..an,依次表示n个人的身高。

 

输出格式

一行一个整数ans,表示最长的符合要求的连续一段。

 

样例

样例输入

【样例输入1】
  7
 2 
4 
6
6 
1 
36 
3
【样例输出1】
  3

样例输出

【样例输入2】
  10
 1 
2 
3 
4 
5 
6
7 
8 
9 
10
【样例输出2】
  2

 

数据范围与提示

对于20%的数据 n<=100
对于40%的数据 n<=1000
对于另外 20%的数据 ai<=100
对于100%的数据 n<=100000,ai<=10^18

 

这题应该是最简单的一个题了

思路倒是不难想,,考场上想出来了要nlogn做,思路大概对了一半,在处理的时候有一些问题

只得了5pts

重新看看这道题

题意:

  在一个序列中找到一个最长的子序列,使它排序后是一个不完整的等比数列(公比<=1000)

可以看到公比小的不的了,所以王天泽大佬提供了一种极其暴力却暴打我nlogn的算法

不需要找最小公比,直接枚举1-1000,找到比就好了,400ms掉打你们

还是说一下我的思路(题解的)

首先一定是公比为1;直接扫一遍,找到最长的相等的子串就可以了

其次就是不为1::

  我们知道任何一个数都可以质因数分解

  我们设a[ ]为这个序列里的数

  x = a [ i ] ;  y = a [ i - 1 ];

  设x>y,只有当y整除x时才有可能构成等比数列

  设p=x/y

  我们对p进行质因数分解

  得到p1q1*p2q3*p3q3......

  那么还有一个性质:::

  设q为q1 q2 q3的最大公因数

  那么p为p1q1/q*p2q2/q*p3q3/q....的整数倍((这个显而易见))

  那么我们则mini=p1q1/q*p2q2/q*p3q3/q....即为x,y的最小公比(就是能够构成等比数列的最小的公比)::4=>2   8=>2    27=>3   216=>6

  这样预处理出来所有的mini(2-n)

  如果两个mini相等,那么就可以构成等比序列,当然不能有重复的

  这时候就要用上map大法了,就像数组一样用只不过下标可以直接开到longlong,

  每一个map都存着这个数出现的位置,更新的时候就方便多了

  

  
#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll unsigned long long
const int N=100005;
int n;
ll a[N];
int ans=0,res=0;
ll prime[N],vis[N],cnt;
ll mini[N],q[N],ch[N];
ll st[N][120];
ll ksm(ll x,int y){
    ll ret=1;
    while(y){
        if(y&1)ret=ret*x;
        x=x*x;
        y>>=1;
    }
    return ret;
}
ll gcd(ll x,ll y){
    if(y==0)return x;
    return gcd(y,y%x);
}
void getmin(ll x,ll y,int z){
    if(x==0||y==0)return ;
    if(x>y)swap(x,y);
    ll p=y%x;
    //cout<<z<<" "<<y<<" "<<x<<" "<<p<<"sb"<<endl;
    if(p!=0){
        mini[z]=0;
        return ;
    }
    p=y/x;ch[z]=p;
    //cout<<p<<endl;
    for(re i=1;i<=cnt;i++){
        q[i]=0;
        while(p%prime[i]==0)
            p/=prime[i],q[i]++;
    }
    int pd;
    ll g;
    for(re i=1;i<=cnt;i++){
        if(q[i]==0)continue;
        if(g==0){
            g=q[i];
            continue;
        }
        g=gcd(g,q[i]);
    }
    for(re i=1;i<=cnt;i++){
        if(q[i]==0)continue;
        mini[z]*=pow(prime[i],q[i]/g);
    }
}
map<ll,int> mp;
int main(){
    scanf("%d",&n);
    for(re i=2;i<=1002;i++){
        if(vis[i]==0)prime[++cnt]=i;
        for(re j=1;j<=cnt&&i*prime[j]<=n;j++){
            vis[i*prime[j]]=1;
            if(i%prime[j]==0)break;
        }
    }
    mini[1]=0;
    for(re i=2;i<=n;i++)mini[i]=1;
    for(re i=1;i<=n;i++){
        scanf("%lld",&a[i]);
        if(a[i]==a[i-1])res++;
        else res=1;
        ans=max(ans,res);
        getmin(a[i-1],a[i],i);
        //cout<<i<<" "<<mini[i]<<endl;
    }
    int fro,beh;//前后端点
    for(re i=2;i<=n;i++){
        //while(mini[i-1]!=0&&ch[i]%mini[i-1]==0&&ch[i]!=0)ch[i]/=mini[i-1];
        if(mini[i]==mini[i-1]&&mini[i]!=0){
            if(mp[a[i]]&&mp[a[i]]>=fro)fro=mp[a[i]]+1;
            mp[a[i]]=i;beh=i;
        }
        else{
            mp.clear();
            mp[a[i-1]]=i-1;
            mp[a[i]]=i;
            fro=i-1;
            beh=i;
        }
        ans=max(ans,beh-fro+1);
    }
    printf("%d",ans);
}
code

T2熟练剖分

题目描述

为什么你们noip的时候shulian(树链)剖分敲得那么shulian(熟练)啊?
你们到底,敲了多少遍树链剖分了啊?
为了考察noip选手们对树链剖分的掌握是否足够熟练,liu_runda决定出个树链剖分题.
可是,SD_le并不认为所有准备联赛的选手都学过树链剖分,因为他自己就不会,liu_runda便决定简述一下这个算法:
本题中使用的树链剖分算法在一棵有根树上进行.根节点编号为1.每个节点有若干个儿子.
每个非叶节点有且仅有一个重儿子,其他儿子称作轻儿子. 重儿子和父亲之间的边称作重边,轻儿子和父亲之间的边称作轻边.
对一个节点进行一次”精彩操作”付出的时间代价是这个节点到根节点路径上的轻边数量.根节点自身进行精彩操作的代价一定是0.我们只关注最坏情况下的时间复杂度,求出每个节点进行一次精彩操作的代价,然后在这些代价中取最大值,就得到了这棵树此时的最坏时间复杂度.
在本题中,liu_runda决定考察选手对树链剖分的灵活应用,因此每一个非叶节点将在它的所有儿子中等概率随机选择一个作为重儿子.
给出一棵树,按上面的方式随机选择重儿子,求最坏时间复杂度的期望.期望一定是一个有理数,可以表示成a/b的形式(a,b均为整数).只需要输出a乘上b的逆元后对10^9+7取模的结果.

输入格式

第一行一个正整数n,表示有n个点。
接下来n行,每行有一个整数k,表示i号点有k个儿子,接下来k个整数表示它的所有儿子。

输出格式

一行一个整数ans。

样例

样例输入

【样例输入1】
 4
2 2 3 
0 
1 4 
0
【样例输出1】
 1

样例输出

【样例输入2】
7
1 5
2 7 3
0
1 6
0
0
2 1 4
【样例输出2】
 500000005

数据范围与提示

对于20%的数据 n<=20
对于另外10%的数据 保证是一条链
对于另外10%的数据 保证数据随机
对于另外5%的数据 所有边都连到了一个点上
对于另外15%的数据 保证是一棵完全二叉树
对于另外15%的数据 n<=300
对于100%的数据 n<=3000

hint

根节点不一定是1

这题20pts

考场上没别的心思--对着部分分就是一顿操作

考场下才想正解,说实话,这正解是真的玄乎;;;;;;;

什么树链剖分,分明就是概率期望

借着体面吓唬人

这个主要是求概率;

不妨设f [i] [j]为以i为根节点,目前遍历的儿子节点中,能让i的最大深度<=j的概率

好看出来由儿子向父亲转移

设h[j]为目前最大深度为j的概率(h[1]+h[2]+...+h[j]=f[i][j])

g[j]为目前重儿子深度<=j的概率     初值均为1(因为一开始都为0,前缀和一下都是1)

先循环选择哪一个为重儿子,再遍历每一个儿子,里面再套一层深度

分二种情况

  目前儿子等于重儿子

    h[i]=g[j]*(f[i][j]-f[i][j-1])+(g[j]-g[j-1])*f[i][j]-(f[i][j]-f[i][j-1])*(g[j]-g[j-1]);

    //重儿子等于j,轻儿子小于等于j//////重儿子小于等于j,轻儿子等于j

  重儿子不等于轻儿子

    看代码吧,和上面差不多

你可能会怀疑为什么每一个f[i][j]都是中间变量,在还没运算到最后结果时

就自己调用自己,为什么是正确的

因为,每一个f[i][j]都是由前面的儿子转移的,可以当作一开始没有儿子,后来一个一个的加进来,这样就好理解了

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
const int mod=1e9+7;
const int N=3005;
int n,m[N];
int to[N],nxt[N],head[N],rp;
int fa[N],root,siz[N];
ll inv[N];
void add_edg(int x,int y){
    to[++rp]=y;
    nxt[rp]=head[x];
    head[x]=rp;
}
ll f[N][N],g[N],h[N];
void dfs(int x){
    siz[x]=1;
    int maxn=0;
    for(re i=head[x];i;i=nxt[i]){
        dfs(to[i]);
        siz[x]+=siz[to[i]];
        maxn=max(maxn,siz[to[i]]);
    }
    for(re i=head[x];i;i=nxt[i]){
        for(re j=0;j<=n;j++)g[j]=1;
        int u=to[i];
        for(re j=head[x];j;j=nxt[j]){
            int v=to[j];
            for(re k=0;k<=siz[v];k++){
                ll gs=g[k],fs=f[v][k];
                if(k){
                    gs-=g[k-1];
                    fs-=f[v][k-1];
                }
                if(u==v)h[k]=(gs*f[v][k]%mod+fs*g[k]%mod-fs*gs%mod+mod)%mod;
                else{
                    fs=f[v][k-1];
                    if(k>1)fs-=f[v][k-2];
                    h[k]=(g[k]*fs%mod+gs*f[v][k-1]%mod-fs*gs%mod+mod)%mod;
                }
            }
            g[0]=h[0];
            for(re k=1;k<=siz[v];k++)
                g[k]=(g[k-1]+h[k])%mod;
        }
        for(re j=siz[x];j>=1;j--)g[j]=(g[j]-g[j-1]+mod)%mod;
        for(re j=0;j<=siz[x];j++)f[x][j]=(f[x][j]+g[j]*inv[m[x]]%mod)%mod;
    }
    if(!head[x])f[x][0]=1;
    for(re i=0;i<=siz[x];i++){
        f[x][i]=(f[x][i]+f[x][i-1])%mod;
    }
}
signed main(){
    scanf("%d",&n);
    inv[1]=inv[0]=1;
    for(re i=2;i<=n;i++)
        inv[i]=(mod-mod/i)*inv[mod%i]%mod;
    for(re i=1;i<=n;i++){
        scanf("%d",&m[i]);
        int son;
        for(re j=1;j<=m[i];j++){
            scanf("%d",&son);
            fa[son]=i;
            add_edg(i,son);
        }
    }
    root=1;
    while(fa[root])root=fa[root];
    //cout<<root<<endl;
    dfs(root);
    ll ans=0;
    for(re i=n;i>=1;i--){
        ans=(ans+i*(f[root][i]-f[root][i-1]+mod)%mod)%mod;
    }
    printf("%lld",ans);
}
Code

T3建造游乐园

题目描述

在学生的联名提议下,HZ决定在校园内建造一个新型游乐场。
游乐场一共有n个项目,在不同的项目之间有一些走廊相连。
为了保证学生能顺利的游玩完游乐场的每个项目,一个建造方案需要满足以下的条件:

  1. 将游乐园抽象为一张图,把走廊视为无向边,整张图无重边,无自环。
  2. 可以通过恰好一次操作使得图中存在一条欧拉回路(从某个点出发经过所有边恰好一次并最终回到起点的路径),其中操作可以是添加一条不在图中的边或是删去一条图中的边。要求操作后的图仍满足条件1,且图中任意两点可以互相到达。

设计游乐场布局的任务被委托给了学生会会长小R,小R想先统计出所有的方案,但她数数向来数不清,所以你能不能帮小R统计一下一共有多少种建造方案。

输入格式

一行一个整数n,表示游乐园有n个项目。

输出格式

仅一行一个整数表示答案对1000000007取模的结果。

样例

样例输入

【输入样例1】 
3 
【输出样例1】 
3

样例输出

【输入样例2】 
9
【输出样例2】
21124449
【样例解释】 
第一个样例三个合法的图分别是:
(1 ? 2),(2 ? 3)
(1? 2),(1?3) 
(1 ? 3),(2 ? 3) 

数据范围与提示

20% 的数据:n ≤ 10
60% 的数据:n ≤ 100
100% 的数据:2 ≤ n ≤ 2000

不是这个题简单是高俊垚大神讲的好(((呱叽呱叽)))

首先设f[n]为n个点时可以得到的联通欧拉图;

g[n]为n个点时可以得到的联不联通都行的;

那么最终答案就是ans=f[n]*Cn2

显然g[i]=2^Ci-12因为现在有一个i-1个点的随便一张图,那么要想让每一个点的度都为偶数

就要让第i个点连接那些奇数点这样一一对应得到了欧拉图

f[i]=g[i]-∑j(1-i-1)f[j]*g[i-j]*Ci-1j-1

这个式子怎么来的呢

减去的这一堆一定是不联通的那么肯定有很多个联通块

只需要找出两个就够了所以选出来几个数在外面

循环j(几个数在外面)

得到本式

#include<bits/stdc++.h>
using namespace std;
#define re register int 
#define ll long long
const int mod=1e9+7;
const int N=2005;
int n;
ll jc[N],inv[N],innv[N];
ll f[N],g[N];
ll ksm(ll x,ll y){
    ll ret=1;
    while(y){
        if(y&1)ret=ret*x%mod;
        x=x*x%mod;
        y>>=1;
    }
    return ret%mod;
}
ll C(ll x,ll y){
    if(x>y)return 0;
    return jc[y]*inv[x]%mod*inv[y-x]%mod;
}
int main(){
    scanf("%d",&n);
    jc[1]=1;jc[0]=1;
    inv[1]=inv[0]=1;
    innv[1]=innv[0]=1;
    for(re i=2;i<=n;i++){
        jc[i]=jc[i-1]*i%mod;
        innv[i]=(mod-mod/i)*innv[mod%i]%mod;
        inv[i]=innv[i]*inv[i-1]%mod;
    }
    g[1]=1;
    for(re i=2;i<=n;i++){
        g[i]=ksm(2,C(2,i-1));
    }
    f[1]=1;
    for(re i=2;i<=n;i++){
        ll tmp=0;
        for(re j=1;j<i;j++){
            tmp=(tmp+f[j]*g[i-j]%mod*C(j-1,i-1)%mod)%mod;
        }
        f[i]=(g[i]-tmp+mod)%mod;
    }
    printf("%lld",f[n]*C(2,n)%mod);
}
Code
 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define re register int 
 4 #define ll long long
 5 const int mod=1e9+7;
 6 const int N=2005;
 7 int n;
 8 ll jc[N],inv[N],innv[N];
 9 ll f[N],g[N];
10 ll ksm(ll x,ll y){
11     ll ret=1;
12     while(y){
13         if(y&1)ret=ret*x%mod;
14         x=x*x%mod;
15         y>>=1;
16     }
17     return ret%mod;
18 }
19 ll C(ll x,ll y){
20     if(x>y)return 0;
21     return jc[y]*inv[x]%mod*inv[y-x]%mod;
22 }
23 int main(){
24     scanf("%d",&n);
25     jc[1]=1;jc[0]=1;
26     inv[1]=inv[0]=1;
27     innv[1]=innv[0]=1;
28     for(re i=2;i<=n;i++){
29         jc[i]=jc[i-1]*i%mod;
30         innv[i]=(mod-mod/i)*innv[mod%i]%mod;
31         inv[i]=innv[i]*inv[i-1]%mod;
32     }
33     g[1]=1;
34     for(re i=2;i<=n;i++){
35         g[i]=ksm(2,C(2,i-1));
36     }
37     f[1]=1;
38     for(re i=2;i<=n;i++){
39         ll tmp=0;
40         for(re j=1;j<i;j++){
41             tmp=(tmp+f[j]*g[i-j]%mod*C(j-1,i-1)%mod)%mod;
42         }
43         f[i]=(g[i]-tmp+mod)%mod;
44     }
45     printf("%lld",f[n]*C(2,n)%mod)

 

以上是关于2021.5.22 noip模拟1的主要内容,如果未能解决你的问题,请参考以下文章

XJOI NOIP模拟题1

神奇的幻方(NOIP2015)(真·纯模拟)

P1065 [NOIP2006 提高组] 作业调度方案(模拟)

NOIp模拟1 Incr(LIS的nlogn算法)

玩具谜题(NOIP2016)(纯模拟)

20181101noip模拟赛T1