排列计数
一、题目
二、解法
既然题目要恰好
m
m
m个在原位,我们就先固定
m
m
m个,方案数
C
n
m
C_{n}^{m}
Cnm,再求错排。
考试时候想到这里,结果错排不会,推了一下才推出来。
我们考虑用容斥原理,用全部排列减去有重复的排列,
c
p
=
(
1
−
1
+
1
/
2
−
1
/
6
+
1
/
24
⋯
)
×
i
!
cp=(1-1+1/2-1/6+1/24\cdots)\times i!
cp=(1−1+1/2−1/6+1/24⋯)×i!
设
c
p
[
i
]
cp[i]
cp[i]为
i
i
i个数来错排,然后就有
c
p
[
i
]
=
c
p
[
i
−
1
]
×
i
+
(
i
%
2
?
−
1
:
1
)
cp[i]=cp[i-1]\times i+(i\%2?-1:1)
cp[i]=cp[i−1]×i+(i%2?−1:1)
解释一下,就是修改一下
c
p
cp
cp里的阶乘,然后加上
1
/
i
!
1/i!
1/i!,注意考虑奇偶性。
#include <cstdio>
const int MOD = 1e9+7;
const int MAXN = 1000005;
int read()
{
int x=0,flag=1;char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*flag;
}
int T,fac[MAXN],inv[MAXN],cp[MAXN];
int init(int n)
{
inv[0]=inv[1]=fac[0]=fac[1]=cp[0]=1;
for(int i=2;i<=n;i++) inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
for(int i=2;i<=n;i++) fac[i]=1ll*fac[i-1]*i%MOD;
for(int i=2;i<=n;i++) inv[i]=1ll*inv[i]*inv[i-1]%MOD;
for(int i=2;i<=n;i++) cp[i]=1ll*cp[i-1]*i%MOD+(i%2?-1:1);
}
int main()
{
T=read();
init(1e6);
while(T--)
{
int n=read(),m=read();
int ans=1ll*fac[n]*inv[n-m]%MOD*inv[m]%MOD*cp[n-m]%MOD;
printf("%d\n",(ans+MOD)%MOD);
}
}
脑洞治疗仪
一、题目
二、解法
发现很多操作都是我们所熟悉的,唯一的问题在于怎么在一段区间中填充脑子。
其实这个问题很想区间第k小,我们先考虑自己,在考虑左儿子,然后考虑右儿子,返回这一段区间用了多少脑子即可,时间复杂度
O
(
n
log
n
)
O(n\log n)
O(nlogn)。
#include <cstdio>
#include <iostream>
using namespace std;
const int MAXN = 400005;
int read()
{
int x=0,flag=1;
char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*flag;
}
int n,m;
struct node
{
int l,r,lv,rv,mv,sum,lazy;//-1 clear while 1 add
node operator + (const node &R) const
{
return node{l,R.r,lv+(lv==r-l+1)*R.lv,R.rv+(R.rv==R.r-R.l+1)*rv,max(rv+R.lv,max(mv,R.mv)),sum+R.sum,0};
}
bool operator == (const node &R) const
{
return l==R.l&&r==R.r&&lv==R.lv&&rv==R.rv&&mv==R.mv&&sum==R.sum;
}
void clear()
{
lv=rv=mv=r-l+1;
sum=0;
lazy=-1;
}
void add()
{
lv=rv=mv=0;
sum=r-l+1;
lazy=1;
}
int req()
{
return r-l+1-sum;
}
} tr[MAXN<<1],empty;
void build(int i,int l,int r)
{
if(l==r)
{
tr[i]=node{l,r,0,0,0,1,0};
return ;
}
int mid=(l+r)>>1;
build(i<<1,l,mid);
build(i<<1|1,mid+1,r);
tr[i]=tr[i<<1]+tr[i<<1|1];
}
void Push_down(int i)
{
if(tr[i].lazy==-1)
{
tr[i<<1].clear();
tr[i<<1|1].clear();
tr[i].lazy=0;
return ;
}
if(tr[i].lazy==1)
{
tr[i<<1].add();
tr[i<<1|1].add();
tr[i].lazy=0;
return ;
}
}
void clear(int i,int l,int r)
{
if(tr[i].r<l || tr[i].l>r) return ;
if(l<=tr[i].l && tr[i].r<=r)
{
tr[i].clear();
return ;
}
Push_down(i);
clear(i<<1,l,r);
clear(i<<1|1,l,r);
tr[i]=tr[i<<1]+tr[i<<1|1];
}
int find(int i,int l,int r)
{
if(tr[i].r<l || tr[i].l>r)
return 0;
if(l<=tr[i].l && tr[i].r<=r)
return tr[i].sum;
Push_down(i);
return find(i<<1,l,r)+find(i<<1|1,l,r);
}
node count(int i,int l,int r)
{
if(tr[i].r<l || tr[i].l>r)
return empty;
if(l<=tr[i].l && tr[i].r<=r)
return tr[i];
Push_down(i);
node a=count(i<<1,l,r),b=count(i<<1|1,l,r);
if(a==empty) return b;
if(b==empty) return a;
return a+b;
}
int add(int i,int l,int r,int num)
{
if(tr[i].r<l || tr[i].l>r || num<=0) return 0;
if(l<=tr[i].l && tr[i].r<=r && tr[i].req()<=num)
{
int t=tr[i].req();
tr[i].add();
return t;
}
Push_down(i);
int t1=add(i<<1,l,r,num);
int t2=add(i<<1|1,l,r,num-t1);
tr[i]=tr[i<<1]+tr[i<<1|1];
return t1+t2;
}
int main()
{
n=read();
m=read();
build(1,1,n);
for(int i=1; i<=m; i++)
{
int op=read(),l=read(),r=read();
if(op==0)
{
clear(1,l,r);
}
if(op==1)
{
int l1=read(),r1=read();
int t=find(1,l,r);
clear(1,l,r);
add(1,l1,r1,t);
}
if(op==2)
{
node t=count(1,l,r);
printf("%d\n",t.mv);
}
}
}
守卫
一、题目
二、解法
本题误导性极强,有很多大佬一上来就写凸包,单调栈之类的省选玩意。
我们发现
[
l
,
r
]
[l,r]
[l,r]中,
r
r
r是肯定要选的,作者手绘了一幅图:
如图,最高的是
a
[
r
]
a[r]
a[r],可以看到图中的两个点,标红的区域就是视觉盲区,我们发现每个视觉盲区是相对独立的,这就涉及到了子结构问题,我们从这一方面切入。
定义
f
[
l
]
[
r
]
f[l][r]
f[l][r]为在
[
l
,
r
]
[l,r]
[l,r]安放守卫的最小花费,受到上图的启发,我们可以固定右端点,然后把左端点向左扩展,如果扩展的点能被
r
r
r看到,那么我们就又多了一个独立的子结构,可以用以前的
f
f
f的到这个子结构的花费然后累加。如果不能被看到,则最后一个子结构就多了一个点,重新计算答案,移动端点的时候记录已经形成独立子结构的花费总和
s
u
m
sum
sum和最远的一个能被看到的点
p
o
s
pos
pos,我们这样写,时间复杂度就是
O
(
n
2
)
O(n^2)
O(n2),判断一个点的是否能被观望点看到的方法是和上一个被看到的点比较和观望点的斜率,保证斜率单调递减即证明可被看到。
??
#include <cstdio>
#include <iostream>
using namespace std;
const int MAXN = 5005;
int read()
{
int x=0,flag=1;char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*flag;
}
int n,ans,a[MAXN],f[MAXN][MAXN];
double slope(int x1,int x2)
{
return (double(a[x2]-a[x1]))/(x2-x1);
}
int main()
{
n=read();
for(int i=1;i<=n;i++)
a[i]=read();
for(int r=1;r<=n;r++)
{
ans^=(f[r][r]=1);
for(int l=r-1,p=0,sum=1;l>=1;l--)
{
if(!p || slope(l,r)<slope(p,r)) sum+=min(f[l+1][p-1],f[l+1][p]),p=l;
ans^=(f[l][r]=sum+min(f[l][p-1],f[l][p]));
}
}
printf("%d\n",ans);
}