来自学长的馈赠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]); } }
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; }
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 }