2019/10/3 CSP-S 模拟测

T1 Permut

题意:

\(1 - n\)的排列中逆序对数量为\(k\)的排列的个数

SOL:

排除法我们知道一定不是\(O(n!)\)的算法
考虑\(dp\),现在已经有\(n-1\)的答案了,考虑新加入一个数产生多少新的逆序对
\(dp[i][j]\)表示\(1 -i\)的排列有\(j\)个逆序对的数量,考虑新加入的数插在哪里会增加多少逆序对数量
\[dp[i][j] = \sum\limits ^{min(i - 1, j)}_k dp[i - 1][j - k]\]
看起来有点奇怪,变一下形 \[dp[i][j] = \sum\limits^j_{max(0, j - i + 1} dp[i - 1][k]\]
复杂度\(O(n * k ^2)\)\(O(跑不动)\)
考虑一个前缀和优化,类似“我要长高”和“\(Making the Grade\)”里的一样
由于\(k\)的上界随\(j\)的变化而变化,考虑在循环的时候累加\(dp[i - 1][j]\)到一个变量里,然后赋给\(dp[i][j]\)
注意这里\(k\)的下界在\(j - i + 1 \geq 0\)的时候会发生变化,记得把多加的减掉

#include<bits/stdc++.h>
#define N (1000 + 10)
using namespace std;
int T;
int n, k, sum, f[N][N];
const int mod = 10000;
int main() {
    scanf("%d", &T);
    f[1][0] = 1;
    while (T--) {
        scanf("%d%d", &n, &k);
        if (f[n][k]) {printf("%d\n", f[n][k]); continue;}
        for (register int i = 2; i <= n; ++i) {
            sum = 0;
            for (register int j = 0; j <= k; ++j) {
                sum += f[i - 1][j] % mod;
                f[i][j] = sum % mod;
                if (j - i + 1 >= 0) sum -= f[i - 1][j - i + 1] % mod;
            }
        }
        printf("%d\n", f[n][k]);
    }
    return 0;
}

T2 Beautiful

题意:

定义一个数的值\(v_i\)为以它作为中位数的区间的最大长度,\(Q\)次查询询问区间\([l, r]\)内的最大\(v_i\),原数大小比较时按值为第一关键字,下标为第二关键字
\(n \leq 2000, Q \leq 100000\)

SOL:

看到\(n\)很小,想想能不能怎么操作一下呢

考虑\(O(n)\)枚举每一个数,然后分别向左向右扩展并记录一个变量\(S\),遇到比它大的就\(--S\),否则\(++S\),然后对于每个\(S\)的值记录一个最远位置,最后拼在一起取最大长度得到\(v_i\)
然后随便怎么做一下\(RMQ\)即可

#include<bits/stdc++.h>
#define N (100000 + 10)
using namespace std;
inline int read() {
    int cnt = 0, f = 1; char c = getchar();
    while (!isdigit(c)) {if (c == '-') f = -f; c = getchar();}
    while (isdigit(c)) {cnt = (cnt << 3) + (cnt << 1) + (c ^ 48); c = getchar();}
    return cnt * f;
}
int n, Q, l, r;
int S1[N], S2[N];
int a[N], b[N];
int t[N];
int A[N];
struct node {
    int l, r;
    int gmax;
    #define l(p) tree[p].l
    #define r(p) tree[p].r
    #define gmax(p) tree[p].gmax
}tree[N << 2];


void pushup(int p) {
    gmax(p) = max(gmax(p << 1), gmax(p << 1 | 1));
}

void build(int p, int l, int r) {
    l(p) = l, r(p) = r;
    if (l == r) {gmax(p) = A[l]; return;}
    int mid = (l + r) >> 1;
    build (p << 1, l, mid);
    build (p << 1 | 1, mid + 1, r);
    pushup(p);
}

long long query(int p, int l, int r) {
    if (l <= l(p) && r >= r(p)) return gmax(p);
    int mid = (l(p) + r(p)) >> 1;
    long long ans = -1;
    if (l <= mid) ans = max(ans, query(p << 1, l, r));
    if (r > mid) ans = max(ans, query(p << 1 | 1, l, r));
    return ans;
}

int main() {
    n = read(); for (register int i = 1; i <= n; ++i) a[i] = read();
    for (register int i = 1; i <= n; ++i) {
        int tmp = 0;
        memset(S1, 255, sizeof(S1));
        memset(S2, 255, sizeof(S2));
        S1[n] = S2[n] = 0;
        for (register int j = i - 1; j >= 1; --j) {
            if (a[j] > a[i]) ++tmp; if (a[j] <= a[i]) --tmp;
            S1[tmp + n] = i - j;
        }
        tmp = 0;
        for (register int j = i + 1; j <= n; ++j) {
            if (a[j] >= a[i]) ++tmp; if (a[j] < a[i]) --tmp;
            S2[tmp + n] = j - i;
        }
        for (register int j = 1 - i; j <= i - 1; ++j) if (S1[n + j] >= 0 && S2[n - j] >= 0) A[i] = max(A[i], S1[n + j] + 1 + S2[n - j]);
    } Q = read();
//  for (register int i = 1; i <= n; ++i) cout<<A[i]<<" ";return 0;
    build (1, 1, n);
    while (Q--) {
        l = read(), r = read();
        printf("%lld\n", query(1, l, r));
    }
    return 0;
}

T3 Subset

题意:

维护一个集合\(A\),支持插入删除查询\(a_i\&S=a_i\)的个数,\(a_i \in A\),操作\(2e5\),数字大小\(2^{16}\)

SOL:

奇妙的思路
由二进制去考虑,把数拆成两节,记录\(s[a][b]\)表示前八位是\(a\),后八位是\(b\)的子集的个数
对于\(add\)\(del\)\(b\)枚举\(a\)更新
对于\(cnt\)操作定\(a\)枚举\(b\)更新
平衡了查询和修改之间的复杂度,时间复杂度优化为\(O(n * 2 ^ 8)\)

#include<bits/stdc++.h>
#define N ((1 << 8) + 5)
using namespace std;
int Q, x;
char ope[5];
int s[N][N];
inline int read() {
    int cnt = 0, f = 1; char c = getchar();
    while (!isdigit(c)) {if (c == '-') f = -f; c = getchar();}
    while (isdigit(c)) {cnt = (cnt << 3) + (cnt << 1) + (c ^ 48); c = getchar();}
    return cnt * f;
}
void add(int x) {
    int a = x >> 8, b = x - (a << 8);
    for (register int i = 0; i < (1 << 8); ++i) if ((i & a) == a) s[i][b]++;
}
void del(int x) {
    int a = x >> 8, b = x - (a << 8);
    for (register int i = 0; i < (1 << 8); ++i) if ((i & a) == a) s[i][b]--;
}
int query(int x) {
    int ans = 0, a = x >> 8, b = x - (a << 8);
    for (register int i = 0; i < (1 << 8); ++i) if ((i & b) == i) ans += s[a][i];
    return ans;
}
int main() {
    Q = read();
    while (Q--) {
        scanf("%s", ope + 1); x = read();
        if (ope[1] == 'a') add(x);
        if (ope[1] == 'd') del(x);
        if (ope[1] == 'c') printf("%d\n", query(x));
    }
    return 0;
}

转载于:https://www.cnblogs.com/kma093/p/11621761.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CSP-S初赛模拟题是一项计算机编程竞赛中的一道模拟题目。CSP(China's Software Cup)是由中国计算机学会主办的一项年度赛事。CSP-S是该赛事中的初赛阶段,旨在选拔出优秀的选手进入决赛。 这道模拟题是根据实际比赛中的情境和要求设计的。题目通常涉及算法、数据结构、图论等方面的知识,考察选手的编程能力和解决问题的能力。 选手在完成题目时需要先仔细阅读题目描述和要求,理解问题的目标和限制条件。然后根据题目要求,设计合适的算法和数据结构,编写代码进行实现。完成后,选手需要对代码进行试和调试,确保程序能正确输出结果。 在这道模拟题中,选手需要运用所学的编程知识和技巧,解决具体的问题。他们需要采用合适的算法和数据结构,通过编写代码实现,并确保程序的运行效率和正确性。同时,在解决问题的过程中,他们还需要思考如何优化算法,提高程序的效率。 通过参与CSP-S初赛模拟题的练习和比赛,选手能够提高自己的编程能力和解决问题的能力。他们在解决问题的过程中,也可以积累经验,更好地应对实际的编程挑战。 总之,CSP-S初赛模拟题是一个考察编程能力、问题解决能力和算法设计能力的重要环节。参与者通过解决题目,不断提升自己的技能,为进入CSP的决赛阶段打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值