令 f [ i ] f[i] f[i] 为 1 到 i 1 到 i 1到i 错排的方案数,考虑加入第 i i i 个数,则 i i i 必然要放在前面的某一个位置 a a a,那么就有两种情况:
对于 a a a 的选择有 i − 1 i-1 i−1 种,故 f [ i ] = ( i − 1 ) ∗ ( f [ i − 1 ] + f [ i − 2 ] ) f[i]=(i-1)*(f[i-1]+f[i-2]) f[i]=(i−1)∗(f[i−1]+f[i−2]) ,初始化 f [ 1 ] = 0 f[1]=0 f[1]=0, f [ 2 ] = 1 f[2]=1 f[2]=1。
令 f [ i ] [ j ] f[i][j] f[i][j] 为球在第 i i i 个人手里,已经传了 j j j 次的方案数,递推式 f [ i ] [ j ] = f [ l ( i ) ] [ j − 1 ] + f [ r ( i ) ] [ j − 1 ] f[i][j]=f[l(i)][j-1]+f[r(i)][j-1] f[i][j]=f[l(i)][j−1]+f[r(i)][j−1],注意对于圆圈的处理。
若令小蛮为第一个人,则初始化
f
[
1
]
[
0
]
=
1
f[1][0]=1
f[1][0]=1,最终求
f
[
1
]
[
m
]
f[1][m]
f[1][m] 即为答案。
数的划分
设 f [ i ] [ j ] f[i][j] f[i][j] 为 i i i 划分为 j j j 段的方案数,对于 i i i 和 j j j 的大小关系分别讨论:
栈只有两种操作:push和pop。
设 f [ i ] [ j ] f[i][j] f[i][j] 为当前有 i i i 个数未输出, j j j 个数没入过栈的方案数,可以得到:
故 f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i ] [ j − 1 ] f[i][j]=f[i-1][j]+f[i][j-1] f[i][j]=f[i−1][j]+f[i][j−1],初始化 f [ i ] [ 0 ] = 1 f[i][0]=1 f[i][0]=1(没有数可以入栈,那么只能不断出栈), f [ n ] [ n ] f[n][n] f[n][n] 即为答案。
设 f [ i ] [ 0 ] f[i][0] f[i][0] 为以 a [ i ] a[i] a[i] 结尾,当前数列单调不升的方案数, f [ i ] [ 1 ] f[i][1] f[i][1] 为以 a [ i ] a[i] a[i] 结尾,当前数列单调不减的方案数,则:
按给的公式暴力记搜
做的时候降智了没推几组就以为是两个10110两个11010交替,光荣爆零
继续手推发现,若记第 i i i 次操作后得到的序列为 s [ i ] s[i] s[i],那么有 s [ i ] = s [ i − 1 ] + s [ i − 2 ] s[i]=s[i-1]+s[i-2] s[i]=s[i−1]+s[i−2],令 s i z [ i ] siz[i] siz[i] 为第 i i i 次操作后得到的序列长度, n u m [ i ] num[i] num[i] 为第i次操作后得到的序列中1的个数,就可以利用前缀和思想得到答案。
code:
#include
#define int long long
#define ff(i,s,e) for(int i(s);i<=(e);++i)
using namespace std;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=92;
int q,a,b;
int siz[N],num[N];
inline int qwq(int x){
ff(i,0,90){
if(siz[i]==x) return num[i];
else if(siz[i]>x) return num[i-1]+qwq(x-siz[i-1]);
//找到第一个长度小于x的序列,由于序列是递推重复的,那么siz[i-1]+1~x的对答案贡献
//和1~x-siz[i-1]的贡献是一样的,可以递归求解
}
return 0;
}
signed main(){
q=read();
siz[1]=1,siz[2]=2,num[1]=num[2]=1;
ff(i,3,90) siz[i]=siz[i-1]+siz[i-2],num[i]=num[i-1]+num[i-2];
while(q--){
a=read(),b=read();
printf("%lld\n",qwq(b)-qwq(a-1));
}
return 0;
}
神仙题
考虑将每个序列 b b b 都用矩阵表示,例如 1 , 5 , 3 , 4 , 2 {1,5,3,4,2} 1,5,3,4,2 可表示为:
1 0 0 0 0
0 0 0 0 1
0 0 1 0 0
0 0 0 1 0
0 1 0 0 0
那么发现每个 a [ i ] a[i] a[i] 在矩阵中的意义变为: ( 1 , 1 ) (1,1) (1,1) 到 ( i , i ) (i,i) (i,i) 构成的方阵里有多少个1(这是怎么想到的qwq
由于矩阵的每行每列都有且只有一个1,就可以进行分类讨论:
看好题:“这种青蛙总是以直线跳过稻田”说明直线一定都得贯穿地图!!!
由于两点确定一条直线,看到数据范围发现可以枚举每一对点,则当前直线斜率与直线上点间距均变为已知,将点按横纵坐标升序排序来保证在结点 i i i 之前枚举的结点 j j j 们全部在 i i i 的左或上方,就可以用 i i i 关于 j j j 的对称点 k k k 来进行递推:
若 k k k 超出地图范围,说明 j − > i j->i j−>i 可以作为一条路径的起点, f [ i ] [ j ] = 2 f[i][j]=2 f[i][j]=2
若 k k k 位置正好存在某个结点,则 … − > k − > j − > i …->k->j->i …−>k−>j−>i 可作为一条路径, f [ i ] [ j ] = f [ j ] [ k ] + 1 f[i][j]=f[j][k]+1 f[i][j]=f[j][k]+1,由于之前排序过,而 j j j 在 i i i 的左或上方, k k k 也一定在 i i i 的左或上方,所以 f [ j ] [ k ] f[j][k] f[j][k] 一定被更新过,可以放心递推。
否则当前路径不合法, f [ i ] [ j ] = − i n f f[i][j]=-inf f[i][j]=−inf
递推完成,可是还有一个问题:当前只保证了路径向左或向上穿过地图,向右或向下呢?
这个问题也不难解决,只需在统计答案时计算 j j j 关于 i i i 的对称点 k k k,只有 k k k 越界的时候才更新答案即可。
code:
#include
#define ll long long
#define ff(i,s,e) for(int i(s);i<=(e);++i)
using namespace std;
inline int read(){
int x=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=5005,M=3005;
int n,r,c;
struct qwq{
int x,y;
}a[N];
int f[N][N],s[N][N];
inline bool cmp(qwq a,qwq b){
if(a.y!=b.y) return a.y<b.y;
return a.x<b.x;
}
inline qwq get(qwq a,qwq b){
return qwq{(a.x<<1)-b.x,(a.y<<1)-b.y};
}
signed main(){
r=read(),c=read();
n=read();
ff(i,1,n) a[i].x=read(),a[i].y=read();
sort(a+1,a+n+1,cmp);
ff(i,1,n) s[a[i].x][a[i].y]=i;
int ans=0;
ff(i,2,n){
ff(j,1,i-1){
qwq k=get(a[j],a[i]);
if(k.x<1||k.x>r||k.y<1||k.y>c) f[i][j]=2;
else if(s[k.x][k.y]) f[i][j]=f[j][s[k.x][k.y]]+1;
else f[i][j]=-1e9;
}
}
ff(i,2,n){
ff(j,1,i-1){
qwq k=get(a[i],a[j]);
if((k.x<1||k.x>r||k.y<1||k.y>c)&&f[i][j]>=3) ans=max(ans,f[i][j]);
}
}
printf("%d",ans);
return 0;
}