CSP-S模拟9

四道序列题。

T1.最长上升子序列

这题类似于找规律或是推性质,发现在给出的序列的空隙插数,如果插入的数比后面的大,显然字典序不优;如果比它小,那么构成上升序列,而且只有当它小于前面的数时,这个递增才不会传递;据此类推,每一个空隙只能放一个。那么最后还有剩下的数,因为没有空隙了,所以直接在末尾倒序输出。

代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
using namespace std; int wrt[20], TP;
const int Z = 2e5 + 100;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f = c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }
inline void write(int x) { TP = 0; if (x < 0) putchar('-'), x = -x; while (x >= 10) wrt[++TP] = x % 10, x /= 10; wrt[++TP] = x; while (TP) putchar(wrt[TP--] | 48); putchar(' '); }
inline int max(int a, int b) { return a > b ? a : b; } inline int min(int a, int b) { return a < b ? a : b; }

int n, m, k, ans;
int a[Z];
bool in[Z];

sandom main()
{
    n = read(), k = read();
    for (re i = 1; i <= k; i++) a[i] = read(), in[a[i]] = 1;
    int num = 1;
    for (re i = 1; i < k; i++)//从小到大在每个空隙插入不会使递增序列变长的
    {
        write(a[i]);
        while (in[num]) num++;
        if (num > a[i + 1]) continue;
        if (num > a[i] && num < a[i + 1]) continue;
        write(num); in[num] = 1;
    }
    in[a[k]] = 0;
    for (re i = n; i >= 1; i--)//把最后剩下的倒序即可
        if (!in[i]) write(i);
    return 0;
}

T2.独特序列

数据结构优化\(dp\)。有一说一,题面和样例研究了老半天。定义\(dp[i]\)表示前\(i\)个数,以\(a[i]\)结尾的方案数,显然转移是从上一个\(a[i]\)的位置到现在所有的\(dp\)和,因为这样才能保证方案唯一,要么包含所有的\(a[i]\),要么每一个\(a[i]\)之间都有一些特定的数来划分。既然是单点修改、区间查询,直接上树状数组。

代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
#define int long long 
using namespace std;
const int Z = 2e5 + 10; const int mod = 998244353;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f = c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }
inline int max(int a, int b) { return a > b ? a : b; } inline int min(int a, int b) { return a < b ? a : b; }

int n, m, k, ans;
int a[Z], pos[Z];
int dp[Z], c[Z];
inline int lbt(int x) { return x & (-x); }
inline void add(int x, int y) { while (x <= n) (c[x] += y) %= mod, x += lbt(x); }
inline int ask(int x) { int r = 0; while (x) (r += c[x]) %= mod, x -= lbt(x); return r; }

sandom main()
{
    n = read();
    for (re i = 1; i <= n; i++) a[i] = read();
    dp[0] = 1; add(1, 1);
    for (re i = 1; i <= n; i++)//树状数组优化dp
    {
        int j = pos[a[i]];
        dp[i] = (ask(i) - ask(j) + mod) % mod;
        if (j) add(j + 1, -dp[j]), dp[j] = 0;
        add(i + 1, dp[i]);
        pos[a[i]] = i;
    }
    for (re i = 1; i <= n; i++) (ans += dp[i]) %= mod;
    cout << (ans + mod) % mod << endl;
    return 0;
}

T3.最大GCD

我是大废物,这道数学题竟然没有做,甚至连暴力都没有。最暴力的方法是枚举\(gcd\)(Gong Chan Dang bushi),然后\(O(n)\)判断想要达到这个\(gcd\),一共需要加多少,然后判断一下。考虑这个\(O(n)\)的式子,我们是可以化简的\(\sum a_i \% d=\sum \lceil \frac{a_i}{d} \rceil d-a_i=d\sum \lceil \frac{a_i}{d} \rceil - \sum a_i\),后面这个东西很好处理直接预处理出来。对于前面这个东西,考虑枚举\(d\)的倍数,对于所有处于\(((k-1)d, kd)]\)的数,它们向上取整的数都是\(k\),而且根据数学经验(筛素数),枚举倍数的时间效率是\(O(nlogn)\),这个东西显然可以二分找,但由于这道题的\(a\)范围很小,我们可以直接开桶预处理出前缀和。

代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
#define int long long 
using namespace std;
const int Z = 1e6 + 10;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f = c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }

int n, m, k, ans;
int cnt[Z], sum;

sandom main()
{
    n = read(), k = read();
    for (re i = 1; i <= n; i++)
    {
        int a = read(); sum += a;
        cnt[a]++; m = max(m, a);
    }
    if (k >= m * n - sum)//已经可以超过a的极限了
    {
        ans = m + (k - m * n + sum) / n;
        cout << ans; return 0;
    }
    for (re i = 1; i <= m << 1; i++) cnt[i] += cnt[i - 1];
    for (re d = 1; d <= m; d++)//枚举公约数
    {
        int res = 0;
        for (re t = d; t <= m << 1; t += d)//计算所有数到达需要的次数
            res += (cnt[t] - cnt[t - d]) * t / d;
        res = res * d - sum;
        if (res <= k) ans = d;
    }
    cout << ans;
    return 0;
}

T4.连续子段

观察到\(k\)很小,显然状压,但貌似不会转移。在\(Sakura_Lu\)的引领下,我回(会)了,定义\(dp[i][j]\)表示\(i\)个数中选了状态为\(j\)的数,\(f[j]\)表示状态为\(j\)的二进制中有几个1。考虑对一个数的决策:不选——那么它会作为一个空位,对于此,要么是左边所有的1移动到右边,要么反之,这些数经过这个空位的总次数是1的个数(也就是这个空位的贡献),那么两边取一下\(min\);选——首先要满足这种数没有被选过,其次它的交换次数只需要考虑已选数的相对位置,交换次数即为相对逆序对数(这个性质之前也用过),这个东西可以利用一开始预处理出来的\(f\)数组来得到,对于状态\(j\)\(a[i]\)构成的逆序对,就是\(f[j >> a_i]\),因为二进制中的数是有序的,左移\(a\)位后剩下的\(1\)都是比它大的,如果这些数还在它前面,那么显然构成逆序对。

代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
using namespace std;
const int Z = 1 << 16;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f = c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }
inline int min(int a, int b) { return a < b ? a : b; }
inline int lowbit(int x) { return x & (-x); }

int n, m, k, ans = 1e9;
int f[Z], dp[Z];

sandom main()
{
    n = read(), k = read(); m = (1 << k) - 1;
    for (re j = 1; j <= m; j++) f[j] = f[j ^ lowbit(j)] + 1;//每个状态中1的个数
    memset(dp, 63, sizeof(dp)); dp[0] = 0;
    for (re i = 1; i <= n; i++)
    {
        int a = read(), t = 1 << a - 1;
        for (re j = m; j >= 0; j--)
        {
            if (!(j & t)) dp[j | t] = min(dp[j | t], dp[j] + f[j >> a]);
            dp[j] += min(f[j], f[m ^ j]);
        }
        ans = min(ans, dp[m]);
    }
    cout << ans;
    return 0;
}
posted @ 2022-09-28 07:48  sandom  阅读(45)  评论(1编辑  收藏  举报