第一章:递推问题

题目:【例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;
}

posted @ 2024-04-26 20:55  Nihachu  阅读(21)  评论(2编辑  收藏  举报