蓝桥杯十一届校园模拟赛

第九题 序列计数 

题目
【问题描述】
小明想知道,满足以下条件的正整数序列的数量:
1. 第一项为 n;
2. 第二项不超过 n;
3. 从第三项开始,每一项小于前两项的差的绝对值。
请计算,对于给定的 n,有多少种满足条件的序列。
【输入格式】
输入一行包含一个整数 n。
【输出格式】
输出一个整数,表示答案。答案可能很大,请输出答案除以10000的余数。
【样例输入】
4
【样例输出】
7
【样例说明】
以下是满足条件的序列:
4 1
4 1 1
4 1 2
4 2
4 2 1
4 3
4 4
【评测用例规模与约定】
对于 20% 的评测用例,1 <= n <= 5;
对于 50% 的评测用例,1 <= n <= 10;
对于 80% 的评测用例,1 <= n <= 100;
对于所有评测用例,1 <= n <= 1000。

思路分析:

可以按照题目要求设计dfs搜索函数,记录总结点个数减一就是解.但是这样的复杂度太高了,最坏情况下2^1000.

long long t=0;
int a[1005];
void dfs(int cur){
    t++;
    cout<

 可以改变递归函数的设计方式,采用记忆化搜索.设f(i,j)为上一数是i当前数是j的总序列个数,则从第二个数开始f(i,j)=f(i,1)+f(i,2)+...f(i,abs(i-j)-1).最终解f(n)=f(n,1)+f(n,2)+f(n,3)+....f(n,n).解空间是N的平方(详细为N*N)表格,但是因为每次都要循环加总,所以成了N的立方.

#include 
#include 
#include 
#include 

#define _for(i, x, y) for(register int i = x;i <= y;i++)
#define _fordw(i, x, y) for(register int i = x;i >= y;i--)
typedef long long LL;
using namespace std;

int N;
LL ans;
const int MOD = 10000;
int mem[1001][1000];

LL dfs(int pre, int cur) {
    // 询问状态
    if (mem[pre][cur] != 0)
        return mem[pre][cur];
    LL ans = 1;
    _for(j, 1,abs(pre-cur) - 1) {
        ans = (ans + dfs(cur, j)) % MOD;
    }
    //记录状态
    mem[pre][cur] = ans;
    return ans;
}

void work() {
    ans = 0;
    cin >> N;
//    f(pre,cur) = sum(f(cur,_new))|_new from 1 to abs(pre-cur)-1
    _for(x, 1, N) ans = (ans + dfs(N, x)) % MOD;
    cout << ans << endl;
}

在同样的解空间下,避免循环加总,即可优化到N的平方,重新考虑状态的转移:如果我们用f(i,j)表示前一个数是i,当前数[1,j]的合法序列的个数;有f(i,j) = 1 + f(i,j-1) + f(j,abs(i-j)-1)即分为两个部分1)i作为前一个数,从1到j-1为当前数的合法序列的个数已经计算好,2)求以j为尾数,后面选择1到abs(i-j)-1的合法序列的个数。如 f(10,5)=f(10,4)+f(5,4)+1;而不是枚举1到5;这样每次解答树只展开两个节点,相当于减少一层循环,虽然解答树的层次还是很深,但是由于记忆的存在,解空间仍然是N的平方。可在100ms内解决。

#include 
#include 
#include 
#include 

typedef long long LL;
using namespace std;

int N;
const int MOD = 10000;
int mem[1001][1000];

int dfs(int pre, int cur) {
    if (cur <= 0) return 0;
    if (mem[pre][cur] != 0) return mem[pre][cur];
    return mem[pre][cur] = (1 + dfs(pre, cur - 1) + dfs(cur, abs(pre - cur) - 1)) % MOD;
}

void work() {
    cin >> N;
    cout << dfs(N, N) << endl;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int a = clock();
    work();
    int b = clock();
    clog << (b - a) << endl;
    return 0;
}

 

第十题 晚会节目单

【问题描述】
小明要组织一台晚会,总共准备了 n 个节目。然后晚会的时间有限,他只能最终选择其中的 m 个节目。
这 n 个节目是按照小明设想的顺序给定的,顺序不能改变。
小明发现,观众对于晚会的喜欢程度与前几个节目的好看程度有非常大的关系,他希望选出的第一个节目尽可能好看,在此前提下希望第二个节目尽可能好看,依次类推。
小明给每个节目定义了一个好看值,请你帮助小明选择出 m 个节目,满足他的要求。
【输入格式】
输入的第一行包含两个整数 n, m ,表示节目的数量和要选择的数量。
第二行包含 n 个整数,依次为每个节目的好看值。
【输出格式】
输出一行包含 m 个整数,为选出的节目的好看值。
【样例输入】
5 3
3 1 2 5 4
【样例输出】
3 5 4
【样例说明】
选择了第1, 4, 5个节目。
【评测用例规模与约定】
对于 30% 的评测用例,1 <= n <= 20;
对于 60% 的评测用例,1 <= n <= 100;
对于所有评测用例,1 <= n <= 100000,0 <= 节目的好看值 <= 100000。


错误思路
如果用两次排序求解,那就错了。因为并不是要选出的方案的好看值总和最大,而是要从前往后尽量好看。

思路 O(N^2)
此题关键在于“第一个节目尽可能好看”并希望“第二个节目尽可能好看”……那么我们选择的第一节目就是max(g[0]~g[n-m])闭区间,要选择的第二个节目是max(g[lastMax+1],g[n-m+1])及从上一个节目往下到n-m+1这个区间里面选最好看的,直到剩下的必须全部选择。

算法用尺取法,双指针移动。理论上的复杂度是O(M*(N-M)),极端情况是M=N/2,整体达到(N^2)/4。如果输入数据为

int N, M;
void work() {
    cin >> N >> M;
    vector games(N);
    for (int i = 0; i < N; i++) {
        cin >> games[i];
    }
    int pos_max = 0, pos_1 = 0, pos_2 = N - M;
    while (pos_1 < pos_2 && pos_2 < N) {
        while (pos_1 < pos_2)
            if (games[++pos_1] > games[pos_max])pos_max = pos_1;
        cout << games[pos_max] << " ";
        pos_1 = pos_max + 1;
        pos_2++;
        pos_max = pos_1;
    }
    while (pos_2 != N) {
        cout << games[pos_2++] << " ";
    }
    cout << endl;
}

优化:区间最值查询 O(NlogN)

while (pos_1 < pos_2)
    if (games[++pos_1] > games[pos_max])pos_max = pos_1;

这一段代码是区间内查询最大值,反复多次,且数据是静态的,所以选择ST做RMQ。f[i][j]表示以 i 为起点,连续 2^j 个数中的最大值(的下标);转移方程就是:f[i][j] = max(data[f[i][j-1]] ,data[f[i+pow_2(j-1)][j-1]])注:比较原始数据,记录下标由于预处理是O(nlogn),M次查询是O(M),每次查询是O(1),所以整体复杂度为O(nlogn)。下列代码实测运行时间100ms以内

#include 
#include 

using namespace std;
int N, M;
vector data;
/*===st rmq的数据 begin===*/
const int MAX_N = 100005;
const int MAX_POW = 20;
int f[MAX_N][MAX_POW], Log[MAX_N];
/*===st rmq的数据 end===*/

/*===st rmq的函数 begin===*/
void initLog() {
    Log[1] = 0;//2的0次方为1,log2_1 = 0;
    for (int i = 2; i <= N; ++i) {
        Log[i] = Log[i / 2] +1;
    }
}
//2的p次方
int pow_2(int p) {
    return 1 << p;
}

void initSt() {
    int i, j;
    for (i = 0; i < N; ++i) {
        f[i][0] = i;//注意这里存的是下标,而不是值
    }

    for (j = 1; pow_2(j) < N; ++j) {
        for (i = 0; i + pow_2(j-1) < N; ++i) {
            int index1 = f[i][j - 1];
            int index2 = f[i + pow_2(j - 1)][j - 1];
            f[i][j] = data[index1] > data[index2] ? index1 : index2;
        }
    }
}

int query(int l,int r){
    int len = r-l+1;
    int k = Log[len];
    int index1 = f[l][k];
    int index2 = f[r - pow_2(k) + 1][k];
    return data[index1] > data[index2] ? index1 : index2;
}
/*===st rmq的函数 end===*/

void work() {
    std::ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> N >> M;
    data = vector(N);
    for (int i = 0; i < N; i++) {
        cin >> data[i];
    }
    /*===初始化st rmq相关数据===*/
    initLog();
    initSt();

    int pos_max = 0, pos_1 = 0, pos_2 = N - M;
    while (pos_1 < pos_2 && pos_2 < N) {
/*        while (pos_1 < pos_2)
            if (data[++pos_1] > data[pos_max])pos_max = pos_1;*/
        pos_max = query(pos_1,pos_2);
        cout << data[pos_max] << " ";
        pos_1 = pos_max + 1;
        pos_2++;
        //pos_max = pos_1;
    }
    while (pos_2 != N) {
        cout << data[pos_2++] << " ";
    }
    cout << endl;
}

int main() {
/* 造数据
    freopen("E:\\data\\my10_1.in", "w", stdout);
    cout<<100000<<" "<<50000<

 

你可能感兴趣的:(算法学习)