乐堕的魔法
总结
8.00~8.40
想 \(T1\) ,但咋都不会多项式复杂度的做法。
很快想到相当于找一棵生成树,如果颜色不同边权为1,否则为0,然后再加上不在树上的颜色个数。
突然发现,因为一种颜色一定在最右边被加进树里,所以只需要强制要求这棵树包含每种颜色最右边的点即可。
然后就可以线性 \(dp\) 了。
8.00~9.10
写了个 \(T2\) 的 \(O(n^3)\) 暴力。
9.10~10.00
\(T2\) 相当于给了六个不等式,那么我可以容斥一个子集,然后开始大分讨,各种容斥,都不行。
10.00~10.20
给 \(T1\) 写了个拍,发现数组开小了。
10.20~11.30
开始想 \(T3\) 的做法,推着推着发现了关键性质,然后就可以做到 \(O(n^2)\) 。
细节有点多。
11.30~12.00
因为是一堆组合数求和,所以感觉可以卷积,但是推了半天不会。
12.00~12.30
写了个 \(T2\) 的 \(O(n^2\log n)\)暴力 。
题解
T2
越想越蠢。
题解也还是容斥,但是容斥的和我想的不太一样。
首先加上 \(\binom{n}{3}\),然后考虑减掉多算的。
如果有一个数把另外两个严格偏序,那么是多算的,可以用 \(cdq\) 分治求出被 \(i\) 三维偏序的个数 \(c_i\),然后减掉 \(\binom{c_i}{2}\) 。
如果有一个数恰好两维把另外两个严格偏序,那么可以用钦定两个的(二维偏序)减掉钦定三个的 \((三维偏序)\),所以复杂度 \(O(n\log ^2n)\)
code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+7;
typedef long long LL;
struct node
{
int a,b,c,v;
}p[N],A[N],B[N],q[N],t[N];
int n;
bool atc(node x,node y)
{
return x.a<=y.a&&x.b<=y.b&&x.c<=y.c;
}
int a=0,b=0;
bool cmp(node x,node y)
{
if(x.a!=y.a) return x.a<y.a;
return x.b<y.b;
}
int c[N];
void upd(int x,int v)
{
for(int i=x;i<=n;i+=i&-i)
c[i]+=v;
}
int ask(int x)
{
int res=0;
for(int i=x;i;i-=i&-i)
res+=c[i];
return res;
}
LL solve()
{
LL res=0;
int i=1,j=1;
while(i<=a||j<=b)
{
if((i>a)||((j<=b)&&A[i].b>B[j].b))
{
upd(B[j++].c,1);
}
else
{
res+=ask(A[i++].c+1);
}
}
return res;
}
LL ans;
void cdq(int l,int r)
{
if(l==r)return;
int mid=(l+r)>>1;
cdq(l,mid);cdq(mid+1,r);
int i=l,j=mid+1,k=l;
while(i<=mid||j<=r)
{
if(j>r||(i<=mid&&p[i].b<=p[j].b))
{
upd(p[i].c,1);
q[k++]=p[i++];
}
else
{
p[j].v+=ask(p[j].c);
q[k++]=p[j++];
}
}
for(i=l;i<=mid;i++)upd(p[i].c,-1);
for(i=l;i<=r;i++)p[i]=q[i];
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
scanf("%d %d %d",&p[i].a,&p[i].b,&p[i].c);
ans=n;
ans+=1ll*n*(n-1)/2ll;
sort(p+1,p+n+1,cmp);cdq(1,n);
ans+=1ll*n*(n-1)*(n-2)/6;
for(int i=1;i<=n;i++)
{
int c=p[i].v;
ans-=c;
ans+=2ll*c*(c-1)/2;
}
for(int i=1;i<=n;i++)t[i]=p[i],t[i].v=0;
for(int i=1;i<=n;i++)p[i]=t[i],p[i].c=n;
sort(p+1,p+n+1,cmp);cdq(1,n);
for(int i=1;i<=n;i++)
{
int c=p[i].v;
ans-=1ll*c*(c-1)/2;
}
for(int i=1;i<=n;i++)p[i]=t[i],p[i].b=n;
sort(p+1,p+n+1,cmp);cdq(1,n);
for(int i=1;i<=n;i++)
{
int c=p[i].v;
ans-=1ll*c*(c-1)/2;
}
for(int i=1;i<=n;i++)p[i]=t[i],p[i].a=n;
sort(p+1,p+n+1,cmp);cdq(1,n);
for(int i=1;i<=n;i++)
{
int c=p[i].v;
ans-=1ll*c*(c-1)/2;
}
cout<<ans;
return 0;
}
T3
太牛了!
相当于有 \(n\) 个区间 \([a_{2i-1},a_{2i}]\),然后令 \(x=a_0\),依次 \(\forall i\in [1,n]\) ,如果 \(x<a_{2i-1},x=a_{2i-1}\),如果 \(x>a_{2i},x=a_{2i}\) 。
考虑对于一个固定的 \(x\) 求答案。
注意到若区间 \([l,r]\) 满足 \(l<x<r\) ,我们删掉这个区间不影响答案,这些区间称为五小区间,那么删掉之后剩下了若干 \(r<x\) 的区间和若干 \(l>x\) 的区间。
假设 \(x\) 是某个区间的右端点,左端点类似。
考虑 \(x\) 在排列中的位置 \(i\),那么要求 \((i,n]\) 这些区间都是无效区间 ,并且 \(i\) 左面第一个非无效区间的 \(l>x\) 或者第一个非无效区间的位置是 \(a_0\) 。
那么就可以计数,首先特判掉 \(a_0=x\) 的情况,此时 \(x=n\),贡献 \(n!n!\) 。
然后枚举极长的的无效区间后缀长度 \(i\),然后把 \(x\) 所在区间插进去,
若 \(x\) 是右端点,贡献为:
其中 \(f_n\) 为把 \(n\) 个数排成 \(a_{2i-1}<a_{2i}\) 的方案数,显然有递推 \(f_{n}=\binom{n}{2}f_{n-2}\),展开后 \(f_n=\frac{n!}{2^{n/2}}\)
推一波式子:
若 \(x\) 为区间左端点,贡献是类似的,为:
加起来得到:
还有一种情况是所有区间都是无效区间,此时也满足 \(x=n\),贡献也是好算的,我们只需要处理上面的求和式。
设
其中 \(p,q\in \{0,1\}\) 。
我们考虑从 \(f_x\) 推到 \(f_{x+1}\) ,我们可以得到如下式子:
即 \(f_{x,p,0}=f_{x-1,p,1}+f_{x,p,1}\) 。
此外还有:
即
需要注意一点, \(\binom{2n-2n}{x-n}\) 只有在 \(x=n\) 时才有值,因此需要对 \(x=n\) 特判一下,减掉一个常数。
有了四个式子,我们就可以 \(O(n)\) 递推了。
code
#include<bits/stdc++.h>
using namespace std;
const int N = 2e7+7;
int fac[N],ifac[N];
const int mod = 998244353;
int Pow(int a,int b)
{
int res=1;
while(b)
{
if(b&1)res=1ll*res*a%mod;
a=1ll*a*a%mod;
b>>=1;
}
return res;
}
void init(int n)
{
fac[0]=1;
for(int i=1;i<=n;i++)fac[i]=1ll*fac[i-1]*i%mod;
ifac[n]=Pow(fac[n],mod-2);
for(int i=n-1;i>=0;i--)ifac[i]=1ll*ifac[i+1]*(i+1)%mod;
}
int binom(int n,int m)
{
if(n<0||m<0||n<m)return 0;
return 1ll*fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}
int n,m;
int f[N/2][2][2];
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline int sub(int a,int b){return a-b<0?a-b+mod:a-b;}
inline int mul(int a,int b){return 1ll*a*b%mod;}
int main()
{
cin>>n>>m;
init(2*n+1);
int inv2=Pow(2,mod-2);
inv2=Pow(inv2,n);
int pw=Pow(2,n);
for(int x=1;x<=n;x++)
{
f[x][0][0]=add(sub(mul(2,f[x-1][0][1]),f[x-1][0][0]),mul(2,binom(2*n-1,x-1)));
if(x==n+1||x==n)f[x][0][0]=sub(f[x][0][0],pw);
f[x][1][0]=add(f[x][0][0],add(mul(2,f[x-1][1][1]),sub(f[x-1][0][0],f[x-1][1][0])));
if(x==n+1||x==n)f[x][1][0]=sub(f[x][1][0],1ll*(n-1)*pw%mod);
f[x][0][1]=sub(f[x][0][0],f[x-1][0][1]);
f[x][1][1]=sub(f[x][1][0],f[x-1][1][1]);
}
while(m--)
{
int x;
scanf("%d",&x);
x=min(x,2*n-x);
int ans=f[x][1][0];
int coef=1ll*fac[x]*fac[2*n-x]%mod;
ans=1ll*ans*coef%mod*inv2%mod;
if(x==n) ans=(ans+1ll*fac[n]%mod*fac[n]%mod);
if(x==n)
{
ans=(ans+1ll*coef*2*n%mod)%mod;
}
printf("%d ",ans);
}
return 0;
}

浙公网安备 33010602011771号