noip模拟1[序列·熟练剖分·建造游乐园]

这场考试考得很烂

连暴力都没打好

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

T1序列

 

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

思路倒是不难想,,考场上想出来了要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熟练剖分

 

这题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建造游乐园

 

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

首先设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)

 

posted @ 2021-05-22 19:42  fengwu2005  阅读(147)  评论(2编辑  收藏  举报