来自学长的馈赠3

来自学长的馈赠3

rank  31    125分   潜力股!

题纲

T1:子区间最值涵盖区间问题(单调栈确定一个值可以控制最大值区间)
T2:分块莫队

T3:转化思想+cdp分治(线段树)+单调队列

T4:预设性DP

T1

给你一个序列,让你求最大值位K的子区间有几个

首先一个区间最值,单调栈的经典问题 离散化之后扫一遍出来每个最值控制的区间,乘法原理算出来有几个区间 cnt[]权值上的数组记录,前缀数组优化

[考场爆0]:k,你要注意就是虽然离散化之后的权值是连续的 但是离散化之前的数不一定是连续的,所以k<=max(a)不能判断它就一定有等于的出现,好比说a(6,8,9)k=7,那=的询问还是0 小于和大于也一样 <:lower_bound()-1一定是 当k出现,=-1,k没出先,=+1-1 合理 其他的也一样,分k出没出现讨论

const int N=100000+10,MOD=1e9+7;
int st[100000+100],top;
ll cnt[100000+100];//这个数最多控制的区间数量
int mp_a[100000+100],a[100000+100],mp_n,n,qu;
char s[2];
int lth[100000+10],rth[100000+10];
int  main()
{
    //freopen("seq2.in","r",stdin);
    n=re();qu=re();
    _f(i,1,n)
    {
        a[i]=re();mp_a[i]=a[i];
    }
    sort(a+1,a+1+n);
    mp_n=unique(a+1,a+1+n)-a-1;
    _f(i,1,n)
    {
        mp_a[i]=lower_bound(a+1,a+1+mp_n,mp_a[i])-a;
    }//离散化一下
    mp_a[n+1]=1e6;
   // a[cnt+1]=2147483647;
    _f(i,1,n+1)
    {
        while(top>0&&mp_a[st[top]]<mp_a[i])//为什么要这样??
        {//对于st[top]来说
            rth[st[top]]=i-1;lth[st[top]]=st[top-1]+1;
            //cnt[mp_a[st[top]]]=cnt[mp_a[st[top]]]+(ll)(st[top]-lth)*(rth-st[top])+rth-lth+1;
         //   chu("%d:%d--%d\n",st[top],lth,rth);
            --top;
        }
        //lth[i]=st[top]+1;
        st[++top]=i;
    }
    /*
      while(top > 0 && a[i] > a[stk[top]])
        {
            R[stk[top]] = i-1; top--;
        }
        L[i] = stk[top]+1;
        stk[++top] = i;
    }
    */
   // _f(i,1,4)chu("%d\n",cnt[i]);
   _f(i,1,n)
   {
       cnt[mp_a[i]]=cnt[mp_a[i]]+(ll)(i-lth[i]+1)*(rth[i]-i+1);
   }
   _f(i,1,mp_n)cnt[i]=cnt[i-1]+cnt[i];
   _f(i,1,qu)
   {
       scanf("%s",s);int kk=re();
        if(s[0]=='<')
       {
           int x=lower_bound(a+1,a+1+mp_n,kk)-a;
            chu("%lld\n",cnt[x-1]);
       }
       else if(s[0]=='>')
       {
           int x=upper_bound(a+1,a+1+mp_n,kk)-a;
          chu("%lld\n",cnt[mp_n]-cnt[x-1]);
       }
       else
       {
           int x=lower_bound(a+1,a+1+mp_n,kk)-a;
           if(a[x]!=kk)chu("0\n");
           else chu("%lld\n",cnt[x]-cnt[x-1]);
       }
   }
View Code

 

T2:定义S(n,m)=sigema(C(n,i))(0<=i<=m)

给出q个询问l,r求S(l,r)

首先找规律,打表也可以,推式子也可以,S(n,m)=2*S(n-1,m)-C(n-1,m)

S(n,m)=S(n,m-1)*C(n,m)

所以n,m可以左右也可以上下移动,l--,l++,r--,r++

莫队处理

优化:强制规定1--maxn是区间大小,进行分块和奇偶性排序优化就行

注意:对于l,r的移动要保证不会出现不合法的区间计算

最好先拓展再删除,l--,r++,l++,r--

但是就题论题,这道题是必须保证n>m,所以n++,m--,n--,m++

 

const int N=100000+10,MOD=1e9+7;
int id,que,lenth,mx;
ll ny[100000+10],nys[100000+10],fac[100000+10];
void pre()
{
    ny[0]=ny[1]=nys[0]=nys[1]=fac[0]=fac[1]=1;
    _f(i,2,100000)
    {
        fac[i]=(fac[i-1]*i%MOD);
        ny[i]=(MOD-MOD/i)*ny[MOD%i]%MOD;
        nys[i]=nys[i-1]*ny[i]%MOD;
    }
}
struct node
{
    int ni,mi,id;
    bool operator<(const node&A)const
    {
        if(ni/lenth==A.ni/lenth)
        {
            if((ni/lenth)&1)return mi<A.mi;
            return mi>A.mi;
        }
        return ni/lenth<A.ni/lenth;
    }
}e[N];//暂时还不知道怎么分块
inline ll C(int x,int y)
{
    return fac[x]*nys[x-y]%MOD*nys[y]%MOD;
}
inline ll S(int x,int y)
{
    ll ans=0;
    _f(i,0,y)
    ans=(ans+C(x,i))%MOD;
    return ans;
}
ll qans[100000+10];
int  main()
{
    id=re();
    que=re();
    pre();
    _f(i,1,que)
    {
        e[i].ni=re(),e[i].mi=re();e[i].id=i;
        mx=max(mx,e[i].ni);
    }
    lenth=sqrt(mx);
    sort(e+1,e+1+que);
    ll ans=0;
    int l=0,r=0;
    ans=1;
    for( int i=1;i<=que;++i)
    {
       // chu("(fenkuai)%d--%d\n",e[i].ni,e[i].mi);

       
        while(l<e[i].ni)//l--,l++,r++,l--
        ans=(ans*2%MOD-C(l++,r)+MOD)%MOD;//一定要+MOD!!!
       
        while(r>e[i].mi)
        ans=(ans-C(l,r--)+MOD)%MOD;
          while(r<e[i].mi)
        ans=(ans+C(l,++r))%MOD;
        while(l>e[i].ni)
        ans=(ans+C(--l,r))%MOD*(ll)ny[2]%MOD;
        qans[e[i].id]=ans;
      //  chu("(nowans)%lld\n",ans);
    }
    _f(i,1,que)
    chu("%lld\n",qans[i]);
    return 0;
}
View Code

 

 T3

给你n*n矩阵,每个格子里只能放一枚棋子,每行每列只能放一枚棋子,称s*s的方格里有恰好s枚棋子的矩阵位是完美子图,求矩阵的完美子图数量

对于每行每列只能放一枚棋子,就有很简单的转化思路:a[x]=y:表示x行的棋子在y列,就是求有多少个a的子序列是在数值上连续的

因为这是一个排列,所以可以转化成设子序列S:Max[S]-Min[S]=Sr-Sl

怎么维护区间最大值最小值?

我们假设从1开始,计算每次向右移动1个R可以找到的从(1--R)~R的最值

就是考虑当前加入的值能不能更新之前的值

单调队列:Max递减,Min递增

每当x位置可以删除某个位置(y),那么它实际更新了st[top-1]+1~st[top]的位置(因为从st[top-1]+1~st[top]-1是被st[top]更新过的,那么它们的最大值一定小于st[top],所以一定可以被当前最大的更新)

方法1:cdq分治

对于一个区间,分成左区间子问题、右区间子问题,左区间右区见合并子问题(考虑最大值最小值出现的区间)

方法2:线段树

我们假设从1开始,计算每次向右移动1个R可以找到的从(1--R)~R的最值

维护线段树:

对于每个位置:维护max-min+posx(表示从当前位置到R的区间内的max-min+L)的值

对于最值的更改通过区间加减体现,然后维护一个最小值
如果max-min+posx==R则记录入答案
因为max-min+l>=r,所以==相当于min(),所有位置的max-min+l=r的个数就是最值
因为更新的时候需要区间加区间减,所以线段树维护区间最小值
int l,r,cnt,mx,dat//只有叶子的dat有意义

T4:计数类DP
给你一个N,K,问有多少个1--n的排列,满足恰好有K个位置Pos,a[Pos-1]<a[Pos]<a[Pos+1]
首先是分析可重叠部分:如果从1--n往里面加数,那么无论当前Pos的位置是什么,因为Pos不能相邻,所以我当前插入的数就只和Pos的个数有关系
状态转移:dp[i][j]:放完第i个数,有j个Pos的方案数
const int N=100000+10,MOD=998244353;
int n,K;//f[i][j]:前i个数,有j个合法的位置的方案数量
int dp[2020][2020];
int main()
{
    n=re(),K=re();
    int maxk=min(K,n-2);
    dp[0][0]=1;dp[1][0]=1;dp[2][0]=1;dp[3][0]=4;dp[3][1]=2;
    _f(i,4,n)
    {
        _f(j,0,maxk)
        {
            //如果是放在了数列两边或者是合法位置旁边
            dp[i][j]=((ll)dp[i][j]+(ll)dp[i-1][j]*(2*j+2))%MOD;
            //如果放在不合法的位置上,合法位置+1
             dp[i][j]=((ll)dp[i][j]+(ll)dp[i-1][j-1]*(i-2*j))%MOD;
        }
    }
    chu("%d",dp[n][K]);
    return 0;
}

考场上没有分析那么清楚,直接搞了个dp[i][j][k]前i个数,有j个空位,k是Pos数量的方案数

但是它和1不一样

1是如果你不强行设置空格的概念,那么两个数A_B之间插入数值后,你没办法确定怎么更新K的值

但是这个可以

 1 int f[2][2010][2010];//f[i][j][k]:放完第i个数字,有j个空格,满足要求的数字有k个的方案数
 2 int n,K;
 3 int main()
 4 {
 5     //freopen("seq2.in","r",stdin);
 6     n=re(),K=re();
 7    //f[1][0][0]=1;
 8     f[1][0][0]=1;
 9    // f[1][1][0]=2;
10    // f[1][2][0]=1;边上没有空格!!!!
11     bool now=0;
12     _f(i,2,n)
13     {
14        // chu("now:%d\n",now);
15          int bianjie=min(i-2,K);//min(k,i-2)
16         _f(j,0,n+1)
17         {
18             _f(k,0,n-1)f[now][j][k]=0;
19         }
20         _f(j,0,i+1)//最多这么多空格
21         {
22             _f(k,0,bianjie)//你才放了i个,最多i-2个满足要求
23             {
24                // chu("lst:%d\n",f[now][j][k]);
25                 if(j>0)f[now][j][k]=((ll)f[now][j][k]+(ll)f[!now][j-1][k]*2)%MOD;//放两边,隔空  +1
26               //  chu("(1)f[(%d)[%d[%d:%d\n",(i),j,k,f[now][j][k]);
27                 f[now][j][k]=((ll)f[now][j][k]+(ll) f[!now][j][k]*2  )%MOD;//放两边,不隔空
28             //    chu("(2)f[(%d)[%d[%d:%d\n",(i),j,k,f[now][j][k]);
29                 //if(j<1)continue;//没有空格就没有中间
30                 if(j>0)f[now][j][k]=((ll)f[now][j][k]+(ll) f[!now][j-1][k]*(j-1)   )%MOD;//放中间,隔2,+1
31               //  chu("(3)f[(%d)[%d[%d:%d    %d*%d\n",(i),j,k,f[now][j][k],f[!now][j-1][k],j-1);
32                 f[now][j][k]=((ll)f[now][j][k]+(ll) f[!now] [j][k]*j*2  )%MOD;//中间,隔1,
33               //  chu("(4)f[(%d)[%d[%d:%d\n",(i),j,k,f[now][j][k]);
34                 if(k>0)f[now][j][k]=((ll)f[now][j][k]+(ll) f[!now][j+1][k-1]*(j+1)  )%MOD;//
35                 //chu("(5)f[(%d)[%d[%d:%d\n",(i),j,k,f[now][j][k]);
36              // if(i>=90&&j==0)  chu("f[(%d)[%d[%d:%d\n",(i),j,k,f[now][j][k]);
37             }
38         }
39         now^=1;
40     }
41     now=now^1;
42    chu("%d",f[now][0][K]);
43 
44     return 0;
45 }
65%(TLE)

 



 
 
posted on 2022-07-24 19:38  HZOI-曹蓉  阅读(44)  评论(1编辑  收藏  举报