第一章:递推问题
题目:【例1】错排问题
思路:
这个题的话好像是有个错排公式
\(f(x) = (i-1) * (f(i-1) * f(i-2))\)
如果你不会,没关系,打几个表基本也就出来了(bushi)
我们来推一下这个柿子:
对于每一个a,显然它不能放在自己的位置上,则可以放在(i-1)个地方
那么考虑它放在了b的位置上
再考虑b放在哪
有两种可能:
1、b放在a的位置上,则考虑其他数的错排,即\(f(i - 2)\)
2、b放在其他的位置上,那么相当于除a以外其他数的错排,即\(f(i - 1)\)
所以共\(f(i - 1) + f(i - 2)\)种可能
没想出来,是卡在哪里了:
没推出错排公式
提交次数:
2
挂在哪了(代码细节):
不开ll见祖宗!
调题心得:
打表万岁
代码:
点击查看代码
#include <bits/stdc++.h>
using namespace std;
int n, i = 3;
long long k[25];
int main() {
scanf("%d", &n);
k[1] = 0;
k[2] = 1;
while (i <= n) {
k[i] = (i - 1) * (k[i - 1] + k[i - 2]);
i++;
}
printf("%lld", k[n]);
}
题目:【例2】传球游戏
思路:
初始时球在1号手中,故有\(f(1,0) = 1\),其他均为零,即传零次球且球在1号手中时,只有一种方案,其他方案均不合法,为零。
然后进行m次迭代,即\(f(n,m) = f(n - 1,m - 1) + f(n + 1, m - 1)\),数组不断迭代即可;
这里不能开滚动数组,因为既有从前面转移过来的,也有从后面转移过来的
没想出来,是卡在哪里了:
第一次根本没往递推上想,爆搜骗了60pts
试图剪枝剪错了,只剩30pts
想起递推,于是ac了
提交次数:
3
挂在哪了(代码细节):
null
调题心得:
不要一上来就敲代码啊(无语)
代码:
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 35;
int f[N][N], n, m;
int main() {
scanf("%d%d", &n, &m);
f[1][0] = 1;
for (int j = 1; j <= m; j++) {
for (int i = 1; i <= n; i++) {
int x = i - 1;
int y = i + 1;
if (x == 0)
x = n;
if (y == n + 1)
y = 1;
f[i][j] = f[x][j - 1] + f[y][j - 1];
}
}
printf("%d", f[1][m]);
return 0;
}
题目:【例3】数的划分
思路:
考虑非降原则(但是我代码里好像写成非升了,一样的吧(扶额))
设f[i][j][k]表示推到第i份,i份数的和为j,上一位为k时的方案数
则这一位的数最大不能超过k
没想出来,是卡在哪里了:
唔这个好像是自己想出来的
提交次数:
1a 好耶
挂在哪了(代码细节):
枚举上一位数的大小时不能超过j,同时这一位数的大小不能超过上一位,但可以相等
代码:
点击查看代码
#include <bits/stdc++.h>
using namespace std;
int f[205][10][205], n, k, ans;
int main() {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++) {
f[i][1][i] = 1;
}
for (int i = 1; i <= n; i++) {
for (int j = 2; j <= k; j++) {
for (int q = 1; q <= i; q++) {
for (int p = 1; p <= q; p++) {
f[i][j][q] += f[i - q][j - 1][p];
}
}
}
}
for (int i = 1; i <= n; i++) {
ans += f[n][k][i];
}
printf("%d", ans);
return 0;
}
题目:【例4】
思路:
设f[i][j]表示当前i个元素未出栈,j个未进栈,i >= j
考虑一次操作能对答案产生什么影响
有两种可能
第一,元素进栈,即由f[i][j]变成f[i][j - 1]
第二,元素出栈,即由f[i][j]变成f[i - 1][j]
故\(f(i,j) = f(i,j - 1) + f(i - 1,j)\)
我们相当于从末状态,即f[0][0]推初始状态,即f[n][n]
没想出来,是卡在哪里了:
没考虑到转移(转移式推错了)
提交次数:
2
调题心得:第二次看还是没想出来,咋回事呢
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int f[20][20], n;
int main(){
scanf("%d",&n);
for(int i = 1; i <= n; i ++) f[i][0] = 1;
for(int i = 1; i <= n ; i ++){
for(int j = 1; j <= i ; j ++){
f[i][j] = f[i - 1][j] + f[i][j - 1];
}
}
printf("%d",f[n][n]);
return 0;
}
题目:e1 求f函数
思路:
显然1000以下直接处理出来,直接跑就好了
没想出来,是卡在哪里了:
忘了
提交次数:
2
挂在哪了(代码细节):
f数组只开到1e3,所以对于>1001的数要先判断,否则会访问到不合法的地方
调题心得:
那时候也太菜了,这都要看题解
代码:
Show Code
#include
using namespace std;
int x, ans, f[1050];
int query(int x) {
if (x >= 1001)
return x - 10;
if (f[x])
return f[x];
f[x] = query(query(x + 11));
return f[x];
}
int main() {
x = 1;
while (x != 0) {
scanf("%d", &x);
if (x) {
if (!ans)
ans = query(x);
else
ans ^= query(x);
}
}
printf("%d", ans);
return 0;
}
题目: f2 划分数列
思路:
直接扫,设一个flag代表状态,分别为不增,不降和都行
如果当前数和前一个数之间的关系不符合flag,则新划分一段,或把都行改成不增或不降
没想出来,是卡在哪里了:
忘了
提交次数:
2
挂在哪了(代码细节):
已经看不懂当时的代码了,什么抽象玩意
调题心得:
这道题还是看的题解,我咋这么菜啊
代码:
Show Code
#include
using namespace std;
const int N = 1e5 + 5;
long long a[N], x;
int n, k = 0, cnt = 1, flag = 2;
bool o[N];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%lld", &x);
if (i == 1 || x != a[k])
a[++k] = x;
}
for (int i = 2; i <= k; i++) {
// printf("%d %d\n",i,flag);
if (flag == 2) {
if (a[i] > a[i - 1])
flag = 1;
else if (a[i] < a[i - 1])
flag = 0;
} else {
if ((a[i] > a[i - 1] && flag == 0) || (flag == 1 && a[i] < a[i - 1]))
flag = 2, cnt++;
}
}
printf("%d", cnt);
return 0;
}
题目:g3 无限序列
思路:
重做依然被卡
一上来觉得是很经典的找循环节,找了半天也没找着
然后开始模拟,把变化的过程写出来,然后发现是一个斐波那契
其实推也是可以推出来的
设一个数列长为\(n\),1的个数为\(m\),则有\(<n,m> => <n + m, n> => <n + m + n, n + m>\)也就是说,
所以我们对于一个数a,找最大的不大于它的\(f_i\),这一段1的个数即为\(f_{i-1}\),再让a减去\(f_i\),重复以上过程直到a为零
没想出来,是卡在哪里了:
没看出来斐波那契数列
提交次数:
2
挂在哪了(代码细节):
每次统计完sum1和sum2没清零
没救了
调题心得:
不会的时候,多手玩一会样例或者式子,可能会有发现
代码:
Show Code
#include
using namespace std;
const int N = 1.5e5 + 5;
long long a, b, suma, sumb;
long long f[93];
int q;
int main() {
f[0] = 1, f[1] = 1, f[2] = 1;
for (int i = 3; i <= 92; i++) {
f[i] = f[i - 1] + f[i - 2];
}
scanf("%d", &q);
for (int i = 1; i <= q; i++) {
suma = 0;
sumb = 0;
scanf("%lld", &a);
a -= 1;
for (int i = 92; i >= 1; i--) {
if (a <= 0)
break;
if (a >= f[i]) {
a -= f[i];
suma += f[i - 1];
}
}
//printf("%d ",suma);
scanf("%lld", &b);
for (int i = 92; i >= 1; i--) {
if (b <= 0)
break;
if (b >= f[i]) {
b -= f[i];
sumb += f[i - 1];
//printf("%d %d %d %d\n",i,b,f[i],sumb);
}
}
//printf("%d ",sumb);
printf("%lld\n", sumb - suma);
}
return 0;
}
题目: h4 序列个数
思路:
一道很有意思的题,重做依然花了将近一堂生物晚课
上来经典的设\(g_i\)代表到第\(i\)位时的方案数
然后我们发现没法转移,因为i每加一其实是相当于限制变得更紧了,要除很多东西,这在取模(尤其是模数还是偶数)意义下非常不可做。
于是我们灵机一动
把\(g_i\)拆成两部分,<=i的所有数的方案数,设为\(f_i\),和\(>i\)的其他数的方案数,直接上阶乘
于是我们得出\(g_i=f_i*\prod_{i = 1}^{i - a_i}(n - a_i-i+1)\)
我们要求的答案是\(g_n\),但我们发现\(i = n\)时\(g_i = f_i\),所以只转移\(f_i\)就可以
我们考虑如何转移\(f_i\)
首先注意到一个结论,从i到i+1,\(a_{i+1}\)只可能是\(a_i,a_i+1,a_i+2\)
\(a_{i+1}<a_i\)显然不可能。
若\(a_{i+1}>a_i\),
则考虑排列从1到i内,有\(a_i\)个数\(<=i\),那么最多有\(a_i+1\)个数\(<=i+1\),再算上第\(i+1\)个数,最多也就是\(a_i+2\)
分情况讨论
\(
f_{i+1} =
\begin{cases}
f_i, a_{i+1}=a_i\\
f_i*(i - a_i + i + 1 - a_i), a_{i+1}=a_i+1\\
f_i*(i-a_i)*(i-a_i), a_{i+1}=a_i+2\\
\end{cases}
\)
然后直接递推转移即可
没想出来,是卡在哪里了:
当时大概是直接蒙了吧……
提交次数:
2
挂在哪了(代码细节):
祖宗你好,祖宗再见
调题心得:
遇到不可转移的式子,要么多开几维状态然后考虑优化,要么就分情况把它拆开,最后合并,后者可以用于输出方案数
代码:
Show Code
#include
using namespace std;
const int N = 1e5 + 5;
const int mod = 340610;
long long n, a[N], f[N];
int main() {
scanf("%lld", &n);
for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
f[0] = 1;
for (int i = 1; i <= n; i++) {
if (a[i] - a[i - 1] == 0)
f[i] = f[i - 1];
else if (a[i] - a[i - 1] == 1)
f[i] = f[i - 1] * (2 * i - 1 - 2 * a[i - 1]);
else if (a[i] - a[i - 1] == 2)
f[i] = f[i - 1] * (i - 1 - a[i - 1]) * (i - 1 - a[i - 1]);
else {
printf("%d", 0);
return 0;
}
f[i] %= mod;
}
f[n] %= mod;
printf("%lld", f[n]);
return 0;
}