CWOI 计数专题
非常好题目,爱来自坤皇。
A
考虑状压,\(f_{i,s}\) 表示前 \(i\) 列,最后一列状态为 \(s\) 的方案数,其中 0/1 表示选/不选。可以枚举下一列的填法来转移。用矩阵是 \(\mathcal{O}(7(2^7)^3\log w)\) 的。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=1e9+7;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct mat{
int a[130][130];
mat(){memset(a,0,sizeof(a));};
void init(){for(int i=0;i<(1ll<<7);i++)a[i][i]=1;}
mat operator *(const mat &b)const{
mat c;
for(int i=0;i<(1ll<<7);i++){
for(int k=0;k<(1ll<<7);k++){
if(a[i][k]==0)continue;
for(int j=0;j<(1ll<<7);j++){
c.a[i][j]=(c.a[i][j]+a[i][k]*b.a[k][j]%mod)%mod;
}
}
}
return c;
}
};
mat qpow(mat b,int p){
mat res;res.init();
for(;p;p>>=1,b=b*b)if(p&1ll)res=res*b;
return res;
}
mat init(int n){
mat res;
for(int i=0;i<(1ll<<n);i++){
for(int j=0;j<(1ll<<n);j++){
for(int k=0;k<(1ll<<(n-1));k++){
if((i|j|k|(k<<1))==(1ll<<n)-1)res.a[i][j]++;
}
}
}
return res;
}
int w[10];
signed main(){
for(int i=1;i<=7;i++)w[i]=read();
mat res;res.init();
for(int i=1;i<=7;i++)if(w[i])res=res*qpow(init(i),w[i]);
printf("%lld\n",res.a[0][0]);
return 0;
}
B
套路的求前缀和,然后单调栈求每个点右边第一个小于它的位置 \(p_i\),即每个点作为左端点左侧点的右端点的合法范围。
对于前缀和相同的位置,我们一起拿出来看。从左往右扫过去,显然 \(p\) 不降,于是可以找到每个点能和哪些点组成区间。随便用查分维护一下即可。时间复杂度 \(\mathcal{O}(n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=1e9+7;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int a[1000005],p[1000005],q[1000005],c[1000005];char s[1000005];vector<int>v[2000005];
void solve(){
scanf("%s",s+1);int n=strlen(s+1),top=0;
for(int i=1;i<=n;i++)a[i]=a[i-1]+((s[i]=='(')?1:-1);
a[n+1]=-inf,q[++top]=n+1;
for(int i=n;i>=0;i--){
while(top&&a[q[top]]>=a[i])top--;
p[i]=q[top],q[++top]=i;
}
for(int i=0;i<=n;i++)v[a[i]+n].push_back(i);
for(int i=0;i<=n+n;i++){
if((int)v[i].size()<2){v[i].clear();continue;}
int siz=(int)v[i].size();vector<int>d1,d2;d1.resize(siz+5),d2.resize(siz+5);
for(int j=0,k=0;j<siz;j++){
while(k<siz&&v[i][k]<=p[v[i][j]]-1)k++;
if(j+1!=k)d1[k-1]+=k,d1[j]-=k,d2[k-1]-=1,d2[j]+=1;
}
for(int j=siz-2;j>=0;j--)d1[j]+=d1[j+1],d2[j]+=d2[j+1];
for(int j=siz-1;j>=1;j--)c[v[i][j]]+=d1[j]+d2[j]*j,c[v[i][j-1]]-=d1[j]+d2[j]*j;
v[i].clear();
}
for(int i=n-1;i>=1;i--)c[i]+=c[i+1];
int ans=0;
for(int i=1;i<=n;i++)ans+=c[i]*i%mod;
memset(a,0,sizeof(a));
memset(p,0,sizeof(p));
memset(q,0,sizeof(q));
memset(c,0,sizeof(c));
printf("%lld\n",ans);
}
signed main(){
int T=read();
while(T--)solve();
return 0;
}
C
考虑状压,\(\mathcal{O}(2^5n)\),可以压成矩阵,\(\mathcal{O}((2^5)^3\log n)\)。可以进一步优化,但没必要。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=1e9+7;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct mat{
int a[33][33];
mat(){memset(a,0,sizeof(a));};
mat operator *(const mat &b)const{
mat c;
for(int i=0;i<32;i++){
for(int k=0;k<32;k++){
if(a[i][k]==0)continue;
for(int j=0;j<32;j++){
c.a[i][j]=(c.a[i][j]+a[i][k]*b.a[k][j]%mod)%mod;
}
}
}
return c;
}
};
mat qpow(mat b,int p){
mat res=b;p--;
for(;p;p>>=1,b=b*b)if(p&1ll)res=res*b;
return res;
}
signed main(){
int n=read();mat f;
for(int j=0;j<32;j++)for(int k=0;k<5;k++){
if(!(((j>>1)>>k)&1ll))f.a[j][(j>>1)|(1ll<<k)]=1;
}
mat tmp;tmp.a[0][7]=1;tmp=tmp*qpow(f,n);
printf("%lld\n",tmp.a[0][7]);
return 0;
}
D
读错题了()
首先需要注意的一点是,被你选的矩形围住的格子不一定全都要用上,所以显然是选最大的矩形更优。
枚举两个矩形的左上角坐标,算围住了多少个 RGB,求二维后缀最大值算出在选 \(r\) 个 R,\(g\) 个 G 拼链时,最多可以用多少个 B。假如各选了 \(r,g,b\) 个,那么在考虑方向性的情况下可以拼出 \(\dfrac{(r+g+b)!}{r!\times g!\times b!}\) 条链。
去掉方向性,就是减去回文链除以 2 再加上回文链。当 \(r,g,b\) 全为偶数时,回文链数量是 \(\dfrac{(\frac{r}{2}+\frac{g}{2}+\frac{b}{2})!}{(\frac{r}{2})!\times(\frac{g}{2})!\times(\frac{b}{2})!}\),因为安排了左边右边是对应的。同理,当 \(r,g,b\) 中只有一个奇数时,不妨设 \(b\) 为奇数,此时回文链数量是 \(\dfrac{(\frac{r}{2}+\frac{g}{2}+\frac{b-1}{2})!}{(\frac{r}{2})!\times(\frac{g}{2})!\times(\frac{b-1}{2})!}\)。
在进行计算时,我们可以先枚举 \(r+g\),预处理出 \(b\) 在任意取值时上式的分子除最右边的分母的值,然后再枚举 \(r,g\) 进行计算。时间复杂度 \(\mathcal{O}(n^2m^2)\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+9,i2=(mod+1)/2;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int r[85][85],g[85][85],b[85][85],c[6405][6405],mx[6405][6405];char s[85][85];
int askr(int x1,int y1,int x2,int y2){
return r[x2][y2]-r[x1-1][y2]-r[x2][y1-1]+r[x1-1][y1-1];
}
int askg(int x1,int y1,int x2,int y2){
return g[x2][y2]-g[x1-1][y2]-g[x2][y1-1]+g[x1-1][y1-1];
}
int askb(int x1,int y1,int x2,int y2){
return b[x2][y2]-b[x1-1][y2]-b[x2][y1-1]+b[x1-1][y1-1];
}
int andr(int x1,int y1,int x2,int y2,int x3,int y3,int x4,int y4){
if((x2<x3||x4<x1)||(y2<y3||y4<y1))return askr(x1,y1,x2,y2)+askr(x3,y3,x4,y4);
return askr(x1,y1,x2,y2)+askr(x3,y3,x4,y4)-askr(max(x1,x3),max(y1,y3),min(x2,x4),min(y2,y4));
}
int andg(int x1,int y1,int x2,int y2,int x3,int y3,int x4,int y4){
if((x2<x3||x4<x1)||(y2<y3||y4<y1))return askg(x1,y1,x2,y2)+askg(x3,y3,x4,y4);
return askg(x1,y1,x2,y2)+askg(x3,y3,x4,y4)-askg(max(x1,x3),max(y1,y3),min(x2,x4),min(y2,y4));
}
int andb(int x1,int y1,int x2,int y2,int x3,int y3,int x4,int y4){
if((x2<x3||x4<x1)||(y2<y3||y4<y1))return askb(x1,y1,x2,y2)+askb(x3,y3,x4,y4);
return askb(x1,y1,x2,y2)+askb(x3,y3,x4,y4)-askb(max(x1,x3),max(y1,y3),min(x2,x4),min(y2,y4));
}
int jc[6405],iv[6405],ij[6405],s1[6405],s2[6405],s3[6405];
signed main(){
int n=read(),m=read(),k=read();
jc[0]=1;for(int i=1;i<=n*m;i++)jc[i]=1ll*jc[i-1]*i%mod;
iv[1]=1;for(int i=2;i<=n*m;i++)iv[i]=mod-1ll*(mod/i)*iv[mod%i]%mod;
ij[0]=1;for(int i=1;i<=n*m;i++)ij[i]=1ll*ij[i-1]*iv[i]%mod;
for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
r[i][j]=r[i-1][j]+r[i][j-1]-r[i-1][j-1]+(s[i][j]=='R');
g[i][j]=g[i-1][j]+g[i][j-1]-g[i-1][j-1]+(s[i][j]=='G');
b[i][j]=b[i-1][j]+b[i][j-1]-b[i-1][j-1]+(s[i][j]=='B');
}
for(int i=0;i<=n*m+1;i++)for(int j=0;j<=n*m+1;j++)c[i][j]=mx[i][j]=-1;
for(int x=1;x<=n;x++)for(int y=1;y<=m;y++){
for(int z=1;z<=n;z++)for(int w=1;w<=m;w++){
int cr=andr(x,y,min(x+k-1,n),min(y+k-1,m),z,w,min(z+k-1,n),min(w+k-1,m));
int cg=andg(x,y,min(x+k-1,n),min(y+k-1,m),z,w,min(z+k-1,n),min(w+k-1,m));
int cb=andb(x,y,min(x+k-1,n),min(y+k-1,m),z,w,min(z+k-1,n),min(w+k-1,m));
c[cr][cg]=max(c[cr][cg],cb);
}
}
for(int i=n*m;i>=0;i--)for(int j=n*m;j>=0;j--){
mx[i][j]=max({mx[i][j+1],mx[i+1][j],mx[i+1][j+1],c[i][j]});
}
int ans=0,tmp=0;
for(int rg=0;rg<=n*m;rg++){
s1[0]=(rg?1ll*jc[rg+0]*ij[0]%mod:0ll);
for(int b=1;b<=n*m-rg;b++)s1[b]=(s1[b-1]+1ll*jc[rg+b]*ij[b]%mod)%mod;
s2[0]=(rg?1ll*jc[rg/2+0/2]*ij[0/2]%mod:0ll);
for(int b=1;b<=n*m-rg;b++)s2[b]=(s2[b-1]+1ll*(1ll-(b&1ll))*jc[rg/2+b/2]*ij[b/2]%mod)%mod;
s3[0]=(rg?1ll*jc[rg/2+0/2]*ij[0/2]%mod:0ll);
for(int b=1;b<=n*m-rg;b++)s3[b]=(s3[b-1]+1ll*jc[rg/2+b/2]*ij[b/2]%mod)%mod;
for(int r=0,g=rg;r<=rg;r++,g--){
int o=mx[r][g];if(o==-1)continue;
ans=(ans+1ll*ij[r]*ij[g]%mod*s1[o]%mod)%mod;
if((r&1ll)&&(g&1ll))continue;
if((r&1ll)||(g&1ll))tmp=(tmp+1ll*ij[r/2]*ij[g/2]%mod*s2[o]%mod)%mod;
else tmp=(tmp+1ll*ij[r/2]*ij[g/2]%mod*s3[o]%mod)%mod;
}
}
printf("%lld\n",(1ll*(ans-tmp+mod)%mod*i2%mod+tmp)%mod);
return 0;
}
E
又又又是 zjk 讲过的题。sto zjk orz
令 \(x_i\) 是第 \(i\) 个人给第 \(i\bmod n+1\) 个人的球数,首先如果 \(\forall i\in[1,n],x_i\ge 1\),那么我们可以让所有人都少给一个球,得到的结果是一样的。所以我们只用统计 \(\min\{x_i\}=0\) 的操作序列对应的答案就行了,也就是用 \(\min\{x_i\}\ge 0\) 的减去 \(\min\{x_i\}\ge 1\) 的,容易发现这是不重不漏的。
现在需要对 \(\prod b_i\) 进行求和,考虑其组合意义:每个人在自己最终有的球中选一个的方案数。每个人的球可以根据来源分为两个部分:原有的球和上一个人给的球。定义 \(f_{i,0}\) 表示考虑前 \(i\) 个人,第 \(i\) 个人选自己的球,前 \(i-1\) 个人的方案数;\(f_{i,1}\) 表示考虑前 \(i\) 个人,第 \(i\) 个人选上一个人给自己的球,前 \(i\) 个人的方案数。这样定义是为了方便考虑第 \(i\) 个人向下一个人传球的情况。以 \(\min\{x_i\}\ge 0\) 时为例,令 \(s_k(n)=\sum\limits_{i=1}^ni^k\),有转移式
解释一下系数:第一个式子前半部分是 \(\sum\limits_{c\ge 0}(a_i-c)=s_1(a_i)\),后半部分是 \(\sum\limits_{c\ge 0}1=(a_i+1)\);第二个式子前半部分是 \(\sum\limits_{c>0}c(a_i-c)=a_is_1(a_i)-s_2(a_i)\),后半部分是 \(\sum\limits_{c>0}c=s_1(a_i)\),因为下一个人一定要拿到 \(i\) 的球都有所以 \(c>0\)。
于是断环成链,枚举第一个人的选择,对应的吧 \(f_{1,0/1}\) 分别赋成 1 跑 dp,答案就是初始位置 dp 值减 1(因为我们初始化的时候赋为 1 了)。时间复杂度 \(\mathcal{O}(n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=998244353,i2=499122177,i6=166374059;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int s(int ty,int n){
if(ty==0)return (n+1)%mod;
else if(ty==1)return n*(n+1)%mod*i2%mod;
else return n*(n+1)%mod*(2*n+1)%mod*i6%mod;
}
int n,a[100005],f[2][100005];
inline void add(int &x,int y){
x+=y;if(x>=mod)x-=mod;
}
int calc(int o1,int o2){
for(int i=1;i<=n;i++)f[0][i]=f[1][i]=0;
f[o1][1]=1;
for(int i=1;i<=n;i++){
add(f[0][i%n+1],f[0][i]*s(1,a[i]-o2)%mod);
add(f[0][i%n+1],f[1][i]*s(0,a[i]-o2)%mod);
add(f[1][i%n+1],f[0][i]*(a[i]*s(1,a[i])%mod-s(2,a[i])+mod)%mod%mod);
add(f[1][i%n+1],f[1][i]*s(1,a[i])%mod);
}
return (f[o1][1]-1+mod)%mod;
}
signed main(){
n=read();for(int i=1;i<=n;i++)a[i]=read();
printf("%lld\n",((calc(0,0)+calc(1,0))%mod-(calc(0,1)+calc(1,1))%mod+mod)%mod);
return 0;
}
F
原:CF722E. Research Rover。算是很经典。
定义 \(f_{i,j}\) 表示从 \((1,1)\) 出发到 \((x_i,y_i)\) 恰好经过了 \(j\) 个障碍物的方案数。这个东西并不好求,考虑转化,定义 \(g_{i,j}\) 表示从 \((1,1)\) 出发到 \((x_i,y_i)\) 至少经过了 \(j\) 个障碍物的方案数,容易发现 \(f_{i,j}=g_{i,j}-g_{i,j+1}\)。对于 \(g\),我们可以单步容斥得到递推式
同时在经过不多的障碍物后 \(s\) 会变成 1,故 \(j\) 的取值范围为 \(\mathcal{O}(\log s)\) 级别。总复杂度 \(\mathcal{O}(k^2\log s)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=1e9+7;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int jc[200005],iv[200005],ij[200005];
int C(int n,int m){
if(n<0||m<0||n-m<0)return 0;
return jc[n]*ij[m]%mod*ij[n-m]%mod;
}
struct Node{
int x,y;
}p[2005];
int cmp(Node x,Node y){
if(x.x^y.x)return x.x<y.x;
return x.y<y.y;
}
int f[2005][25],g[2005][25];
int qpow(int b,int p){
int res=1;
for(;p;p>>=1,b=b*b%mod)if(p&1ll)res=res*b%mod;
return res;
}
signed main(){
int n=read(),m=read(),k=read(),s0=read(),flag=0;
jc[0]=1;for(int i=1;i<=n+m;i++)jc[i]=jc[i-1]*i%mod;
iv[1]=1;for(int i=2;i<=n+m;i++)iv[i]=mod-(mod/i)*iv[mod%i]%mod;
ij[0]=1;for(int i=1;i<=n+m;i++)ij[i]=ij[i-1]*iv[i]%mod;
for(int i=1;i<=k;i++){
p[i].x=read(),p[i].y=read();
if(p[i].x==n&&p[i].y==m)flag=1;
}
if(!flag)p[++k]=(Node){n,m},s0*=2;
sort(p+1,p+k+1,cmp);int lim=0;
for(int j=1,s=s0;s!=1;j++)lim=j,s=(s+1)/2;
lim=min(lim+1,k)+1;
for(int i=1;i<=k;i++){
g[i][1]=C(p[i].x+p[i].y-2,p[i].x-1);
for(int j=2;j<=lim;j++){
for(int t=1;t<=i-1;t++){
if(p[t].x<=p[i].x&&p[t].y<=p[i].y){
g[i][j]=(g[i][j]+f[t][j-1]*C(p[i].x-p[t].x+p[i].y-p[t].y,p[i].x-p[t].x)%mod)%mod;
}
}
}
for(int j=1;j<=lim;j++)f[i][j]=(g[i][j]-g[i][j+1]+mod)%mod;
}
int ans=0;
for(int i=0;i<=lim;i++)ans=(ans+f[k][i]*s0%mod)%mod,s0=(s0+1)/2;
printf("%lld\n",ans*qpow(C(n+m-2,n-1),mod-2)%mod);
return 0;
}
G
思考有没有办法能把每一个点上一个编号使得距离与编号相关。容易发现以下对应关系:有一个 01 串,还有一个人初始在根。从前往后看 01 串的每一位,如果当前位是 0 人就不动,否则向这个点新加的叶子走,最后到达的点就是 01 串对应的点。显然这是一一对应的,且两个点的距离是刨去它们的 LCP 后剩下的 1 之和(其实这个对应关系跟这道题相同)。
问题转化为有多少对长为 \(n\) 的 01 串距离为 \(d\)。枚举去掉 LCP 后的长度 \(i\),答案就是 \(\sum\limits_{i=1}^n2^{n-i}\dbinom{2i-2}{d-1}\)。需要减一是因为刨去 LCP 后第一位必须不同。令 \(n\gets n-1\),\(d\gets d-1\),\(i\gets i-1\),\(f(d)=\sum\limits_{i=1}^n2^{n-i}\dbinom{2i-2}{d-1}\),开始推式子:
解释一下为什么会这样推:往上推两次是因为这样才能化为相似的形式,第四行到第五行 \(i\gets i-1\) 了。
再化简一下,可以得到递推式:\(f(d)=f(d-2)+2f(d-1)-\dbinom{2n}{d-2}-2\dbinom{2n}{d-1}-\dbinom{2n}{d}\)。可以推出 \(f(0)=2^{n+1}-1\),\(f(1)=2^{n+2}-2(n+2)\),预处理一下 \(\dbinom{2n}{i}\) 就可以递推了。时间复杂度 \(\mathcal{O}(\log n+d)\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int qpow(int b,int p,int mod){
int res=1;
for(;p;p>>=1,b=1ll*b*b%mod)if(p&1ll)res=1ll*res*b%mod;
return res;
}
int iv[10000005],C[10000005],f[10000005];
signed main(){
int n=read()-1,d=read()-1,mod=read();
iv[1]=1;for(int i=2;i<=d;i++)iv[i]=mod-1ll*(mod/i)*iv[mod%i]%mod;
C[0]=1;for(int i=0;i<d;i++)C[i+1]=1ll*C[i]*(2*n-i)%mod*iv[i+1]%mod;
f[0]=(qpow(2,n+1,mod)-1+mod)%mod,f[1]=(qpow(2,n+2,mod)-2ll*(n+2)%mod+mod)%mod;
for(int i=2;i<=d;i++)f[i]=((f[i-2]+2ll*f[i-1]%mod)%mod-((C[i-2]+2ll*C[i-1])%mod+C[i])%mod+mod)%mod;
printf("%d\n",f[d]);
return 0;
}
I
神仙题。
我们使用 1234 的排列来表示子序列每个位置的相对大小关系,x 表示可以填任何数。比如 1234 表示 \(t_1<t_2<t_3<t_4\),1x3x 表示 \(t_1<t_2<t_3<t_4\) 或 \(t_1<t_4<t_3<t_2\)。
直接计算题目中的偏序关系是困难的,不妨对原式进行一定的变形:
下面依次进行计算。记 \(l_i\) 表示 \(p_i\) 左边有多少个比它小的,\(r_i\) 表示 \(p_i\) 右边有多少个比它大的。
-
1x2x:枚举 2 的位置 \(i\),右边有一个比它大的,剩下的要满足 \(j<k<i\),\(p_j<p_i<p_k\)。如果只考虑 \(p_j<p_i\),\(j<i,k<i\),情况有 \(l_i(i-1)\) 种。不合法的情况有 \(j<k\) 且 \(p_k<p_i\) 和 \(j\ge k\)。前者相当于在 \(l_i\) 个数中选两个,后者对 \(p_k\) 没有限制,\(k\) 可以取遍 \([1,j]\),相当于求 \(\sum\limits_{j<i\land p_j<p_i}j\),树状数组维护;
-
1xxx:枚举 1 的位置 \(i\),相当于在 \(r_i\) 个数中选三个;
-
13xx:枚举 3 的位置 \(i\),右边有一个比它大的,剩下的要满足 \(j<i<k\) 且 \(p_j<p_k<p_i\)。如果只考虑 \(j<i<k\) 且
\(p_j<p_i,p_k<p_i\),情况有 \(l_i(p_i-1-l_i)\) 种。不合法的情况是 \(j<i<k\) 且 \(p_j>p_k\)。如果认为所有满足 \(j<i,j<k\) 且 \(p_j>p_k\) 的 \(\sum\limits_{j<i\land p_j<p_i}(n-j-r_j)\) 种情况都不合法,会多减 \(j<k<i\) 且 \(p_j>p_k\) 的 \(\dbinom{l_i}{2}-\sum\limits_{j<i\land p_j<p_i}l_j\) 种情况,树状数组维护; -
1234:求长度为 4 的上升子序列,树状数组维护。
时间复杂度 \(\mathcal{O}(n\log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e18,mod=(1ll<<24);
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int C2(int n){
return (n*(n-1)/2)%mod;
}
int C3(int n){
return (n*(n-1)*(n-2)/6)%mod;
}
int n,a[200005];
struct BIT{
int c[200005];
void clear(){
for(int i=1;i<=n;i++)c[i]=0;
}
void add(int x,int y){
for(;x<=n;x+=x&-x)c[x]=(c[x]+y)%mod;
}
int ask(int x){
int res=0;
for(;x;x-=x&-x)res=(res+c[x])%mod;
return res;
}
}Tr;
int ans,f[5][200005],L[200005],R[200005];
signed main(){
n=read();
for(int i=1;i<=n;i++)a[i]=read();
Tr.clear();for(int i=1;i<=n;i++)L[i]=Tr.ask(a[i]),Tr.add(a[i],1);
Tr.clear();for(int i=n;i>=1;i--)R[i]=(n-i)-Tr.ask(a[i]),Tr.add(a[i],1);
//1234
for(int i=1;i<=n;i++)f[1][i]=1;
for(int j=2;j<=4;j++){Tr.clear();for(int i=1;i<=n;i++)f[j][i]=Tr.ask(a[i]),Tr.add(a[i],f[j-1][i]);}
for(int i=1;i<=n;i++)ans=(ans+f[4][i])%mod;
//1xxx
for(int i=1;i<=n;i++)ans=(ans-C3(R[i])+mod)%mod;
//1x2x
Tr.clear();
for(int i=1;i<=n;i++)ans=(ans+((L[i]*(i-1)%mod-C2(L[i])-Tr.ask(a[i]))%mod+mod)%mod*R[i]%mod)%mod,Tr.add(a[i],i);
//13xx
Tr.clear();
for(int i=1;i<=n;i++){
ans=(ans+(L[i]*(n-i-R[i])%mod+C2(L[i])-Tr.ask(a[i])+mod)%mod*R[i]%mod)%mod;
Tr.add(a[i],a[i]-1);
}
printf("%lld\n",ans);
return 0;
}