首页 > 其他分享 >CSP-S模拟赛7

CSP-S模拟赛7

时间:2022-09-23 21:24:44浏览次数:52  
标签:rt return int while dp CSP 模拟 define

T1.序列问题

盯了T1二十分钟,发现只会\(O(n!)\)的暴力,于是溜了。最后一小时想到了\(O(n^2)\)的dp,拿到了(骗到)50分,而且因为我的dp定义比较原始,所以没有办法优化。
首先定义\(b[i]=i-a[i]\),表示这个数能产生贡献当且仅当前面删除了\(b[i]\)个数。即:定义\(dp[i][j]\)为前i个数,删了j个数的最大值,转移为\(dp[i][j] = max[ dp[i-1][j-1],dp[i-1][j]+(j==b[i]) ]\),就是分为留下和删去两种情况。
那么只能换一种dp定义了,定义\(dp[i]\)为前i个数,以i结尾的最大值,类似于最长上升子序列的方式转移\(当b[i]>=0, dp[i]=max(dp[j]+1),j < i, a[j] < a[i], b[j] <= b[i]\),含义即为前j个数里留下的一定小于前i个数里留下的,删去的一定小于等于。很明显这是一个偏序问题,可以使用CDQ优化,但是效率是\(O(nlog^2n)\)的,但是我们发现通过a与b的不等式关系,能够推导出i与j的关系,也就是说只需要满足后两个偏序关系。我们按a排序,就把问题转化为了求一个数列的最长不下降子序列,排序上有一个细节,当a相等时按b降序,就保证了i不会从\(a[i]=a[j]\)的j转移过来。

代码
#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 = 5e5 + 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 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, ans;
struct seq
{
    int a, b;
    friend bool operator <(seq A, seq B)
    {
        if (A.a == B.a) return A.b > B.b;
        return A.a < B.a;
    }
}; seq s[Z];
int c[Z];
inline int lbt(int x) { return x & (-x); }
inline void add(int x, int y) { while (x <= n) c[x] = max(c[x], y), x += lbt(x); }
inline int ask(int x) { int res = 0; while (x) res = max(res, c[x]), x -= lbt(x); return res; }

sandom main()
{
    fre(sequence, sequence);
    n = read();
    for (re i = 1; i <= n; i++) s[i].a = read(), s[i].b = i - s[i].a + 1;
    sort(s + 1, s + 1 + n);
    for (re i = 1; i <= n; i++)
    {
        if (s[i].b <= 0) continue;
        int dp = ask(s[i].b) + 1;
        add(s[i].b, dp);
        ans = max(ans, dp);
    }
    cout << ans << endl;
    return 0;
}

T2.钱仓

不到半小时就切了……环上问题,考虑转化为链。一定存在某一个位置,钱只从左侧转移向右侧,而不存在钱从右侧经环转移向左侧。为了方便,我们断环成链,找到一个合适的断点,使得左边的钱整体多于右边,这样钱顺时针从左流到右。还发现到一个性质:\((x+y)^2>x^2+y^2\),所以让不要让某短路程过长。这一部分用队列从后往前扫一下就结束了,每次往离自己最远的地方运,这样能保证后面的钱仓的路程小一点(牺牲自我)。问题就只剩下了找到合适的断点。联想到括号序列的思想,我们不妨把c数组减一,合适的一段序列即为前缀和始终非负,这样能保证左边的钱足以运给右边。

代码
#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 + 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 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 c[Z], s[Z];
#define lk (rt << 1)
#define rk (rt << 1 | 1)
#define mid (l + r >> 1)
int Min[Z << 2];
void build(int rt, int l, int r)
{
    if (l == r) { Min[rt] = s[l]; return; }
    build(lk, l, mid), build(rk, mid + 1, r);
    Min[rt] = min(Min[lk], Min[rk]);
}
int query(int rt, int l, int r, int L, int R)
{
    if (L <= l && R >= r) return Min[rt];
    int res = 1e9;
    if (L <= mid) res = min(res, query(lk, l, mid, L, R));
    if (R > mid) res = min(res, query(rk, mid + 1, r, L, R));
    return res;
}
queue <int> q;

sandom main()
{
    fre(barn, barn);
    n = read(); m = n << 1;
    for (re i = 1; i <= n; i++) c[i] = read(), c[i + n] = c[i];
    for (re i = 1; i <= m; i++) s[i] = s[i - 1] + c[i] - 1;
    build(1, 1, m);
    for (re i = 0; i < n; i++)//保证都是从前往后转移
        if (query(1, 1, m, i + 1, i + n) >= s[i])
        {
            for (re j = 1; j <= n; j++) c[j] = c[i + j];
            break;
        }
    for (re i = n; i >= 1; i--)
    {   
        if (c[i])//有的话就往后运
        {
            while (c[i] && !q.empty())
            {
                ans += (q.front() - i) * (q.front() - i);
                c[i]--; q.pop();
            }
        }
        if (!c[i]) q.push(i);
    }
    cout << ans << endl;
    return 0;
}

T3.自然数

先做的这个题,然后搞了六个容斥数组……还不如打个\(O(n^2)\)暴力呢,s3的部分分因为没开long long挂了。这种区间问题很常见,都是用线段树维护,但是之前的都是往里面插入,而这个不行,插入会改变多个区间的数值,并且变动不一样。所以考虑每次删除一个点。先预处理出每一个\([1, i]\)的mex。从左开始依次删除,容易发现,把删除的这个点的数值记为v,则它到下一个v之间的区间没有v,如果这些区间里的mex大于等于v,则这些区间的mex都会变为v,所以问题转化为,对于每一个删除的v,记下一个v的位置为nxt,每一次删除要把\([v, nxt)\)中所有的\(mex\)与v取\(min\),还要支持区间求和,吉司机线段树的板子。但由于\(mex\)是有单调性的,所以可以在线段树上二分找到需要修改的区间,直接区间覆盖。

代码
#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 + 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 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 pos[Z], nxt[Z], mex[Z], a[Z];
bool vs[Z];
struct tree
{
    int l, r;
    int sum, max, lz;
    #define lk (rt << 1)
    #define rk (rt << 1 | 1)
    #define mid (tr[rt].l + tr[rt].r >> 1)
}; tree tr[Z << 2];
inline void add(int rt, int val)
{
    tr[rt].sum = (tr[rt].r - tr[rt].l + 1) * val;
    tr[rt].max = val;  tr[rt].lz = val;
}
inline void pushup(int rt)
{
    tr[rt].sum = tr[lk].sum + tr[rk].sum;
    tr[rt].max = max(tr[lk].max, tr[rk].max);
}
inline void pushdown(int rt) { if (tr[rt].lz != -1) add(lk, tr[rt].lz), add(rk, tr[rt].lz), tr[rt].lz = -1; }
void build(int rt, int l, int r)
{
    tr[rt].l = l, tr[rt].r = r, tr[rt].lz = -1;
    if (l == r) { tr[rt].sum = tr[rt].max = mex[l]; return; }
    build(lk, l, mid), build(rk, mid + 1, r);
    pushup(rt);
}
void update(int rt, int l, int r, int val)
{
    if (l <= tr[rt].l && r >= tr[rt].r) { add(rt, val); return; }
    pushdown(rt);
    if (l <= mid) update(lk, l, r, val);
    if (r > mid) update(rk, l, r, val);
    pushup(rt);
}
int query(int rt, int l, int r)
{
    if (l <= tr[rt].l && r >= tr[rt].r) return tr[rt].sum;
    pushdown(rt);
    int res = 0;
    if (l <= mid) res += query(lk, l, r);
    if (r > mid) res += query(rk, l, r);
    return res;
}
int find(int rt, int val)
{
    if (tr[rt].l == tr[rt].r) return tr[rt].max > val ? tr[rt].l : n + 1;
    pushdown(rt);
    if (tr[lk].max > val) return find(lk, val);
    else return find(rk, val);
}

sandom main()
{
    fre(mex, mex);
    n = read();
    for (re i = 1; i <= n; i++)
    {
        a[i] = read();
        if (a[i] > n) continue;
        nxt[pos[a[i]]] = i;
        pos[a[i]] = i;
    }
    for (re i = 0; i <= n; i++) nxt[pos[i]] = n + 1;
    int num = 0;
    for (re i = 1; i <= n; i++)
    {
        if (a[i] <= n) vs[a[i]] = 1;
        while (vs[num]) num++;
        mex[i] = num;
    }
    build(1, 1, n);
    for (re i = 1; i <= n; i++)
    {
        ans += query(1, i, n);
        if (a[i] > n) continue;
        int pos = find(1, a[i]);//与区间取min,由于有单调性,可以先二分找到需要进行修改的直接覆盖
        if (pos < nxt[i]) update(1, pos, nxt[i] - 1, a[i]);
    }
    cout << ans << endl;
    return 0;
}

T4.环路

我甚至没有往矩阵乘法上想,一直在想dp,但是发现dp是错的,因为需要不断迭代更新。但是题解让我得到了升华,我看着自己的原始式子\(dp[i][j]+=dp[i][k]*dp[k][j]\),这tm不就是矩阵乘吗,而且\(n<=100\),那么直接把给出的矩阵设为初始矩阵A,表示走1步到达每个点的方案数,\(A[i][i]\)即为环,给A每乘上一个A,就表示多走一步。那么最后的答案矩阵就是\(A+A^2+A^3+…+A^{k-1}\),\(k<=1e6\),显然不能递推,这个类似于等比数列,我们可以递归分治处理,每次/2处理,对于偶数/奇数分别讨论。时间复杂度\(O(n^3logk)\)

代码
#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 
#define fir first
#define sec second
using namespace std; 
const int Z = 110; 
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;
char mp[Z][Z];
struct Matrix
{
    int a[Z][Z];
    Matrix () { memset(a, 0, sizeof(a)); }
    friend Matrix operator +(Matrix x, Matrix y)
    {
        Matrix z;
        for (re i = 1; i <= n; i++)
            for (re j = 1; j <= n; j++)
                z.a[i][j] = (x.a[i][j] + y.a[i][j]) % m;
        return z;
    }
    friend Matrix operator *(Matrix x, Matrix y)
    {
        Matrix z;
        for (re i = 1; i <= n; i++)
            for (re k = 1; k <= n; k++)
                for (re j = 1; j <= n; j++)
                    (z.a[i][j] += 1ll * x.a[i][k] * y.a[k][j]) %= m;
        return z;
    }
}; Matrix base, x, y;
pair<Matrix, Matrix> z, res;
pair<Matrix, Matrix> solve(int k)
{
    if (k == 1) return make_pair(base, base);
    z = solve(k >> 1);
    x = z.fir * z.fir, y = z.sec + z.fir * z.sec;
    if (k & 1) x = x * base, y = y + x;
    return make_pair(x, y);
}

sandom main()
{
    fre(tour, tour);
    n = read();
    for (re i = 1; i <= n; i++)
    {
        scanf("%s", mp[i] + 1);
        for (re j = 1; j <= n; j++)
            if (mp[i][j] == 'Y') base.a[i][j] = 1;
    }
    k = read(), m = read();
    res = solve(k - 1);
    for (re i = 1; i <= n; i++) (ans += res.sec.a[i][i]) %= m;
    cout << ans << endl;
    return 0;
}

标签:rt,return,int,while,dp,CSP,模拟,define
From: https://www.cnblogs.com/sandom/p/16724385.html

相关文章

  • 做题记录整理dp8 P5665 [CSP-S2019] 划分(2022/9/23)
    P5665[CSP-S2019]划分这题其实并不是题单的第八题,但我现在一做完题目马上就想来(测出题人的码)整理题目因为这题是真的恶心首先朴素的n三次方dp,枚举上一个端点,以及上上......
  • 25th-27th 2022/7/28,2022/7/29,2022/7/30 模拟赛总结15-17
    首先这次是补,因为有个垃圾将我的总结删了它的名字不配出现在我的总结中这三次其实都不算好主要问题是没睡好,读题不仔细以及并没有拼尽全力去打这几点总结应该注重休......
  • 29th 2022/8/1 模拟赛总结18
    这次还行因为这次认真去打,而且在打T2两个钟时,仍旧能坚持下去,最好迎来了胜利不错的,但仍旧有一些不足在T2打完发现过了时,心花怒放,光顾着打暴力,结果没什么分,T1也没有静心......
  • 30th 2022/8/3 模拟赛总结19
    这次不是很烂,但是问题出现我的思路过于繁复,其实就是对前缀和的概念理解出了问题前缀和不一定是形如\(f_r-f_{l-1}\)只要加减的东西有意义,就可以了如\(f_{i,j}-f_{i-1......
  • 31st 2022/8/4 模拟赛总结20
    这次死在了小错误上虽然并没有考砸,但是本来可以考得更好T1想到了正解但是又是一个小问题断送了前程在求答案时,小数据还好,但是大数据。。。总之,就是在求答案是加上了......
  • 20th 2022/7/18 模拟赛总结12
    这次嗯,题目真是没有半点水分,干巴巴一片T1T3省选模拟,T2NOIP,恐怖的是T4???这次估计上紫赛时T1-T4-T2-T3首先读题很久,30min过,然后着手T1,找规律,没有半分,只用仅有的数论知识......
  • 16th 2022/7/14 模拟赛总结9
    这次哈,没有想打的意思,随便打了两个暴力和一个表就发呆了今天讲一个专题,却听不下去,因为讲题者太帅了,根本听不懂,感觉他就是把课件念了一遍然后回到座位,却还在看讲题,嗯最后......
  • 18th 2022/7/15 模拟赛总结10
    这次哈,依然不大想打随便一打,却发现排名居然没掉,其他人摸鱼吗?其实这次比赛题质量不算很高T1是优化,T2是优化+细节,T3是打过类似的找循环,T4是DP优化嗯,因为要回去了所以没......
  • 19th 2022/7/16 模拟赛总结11
    这次哈,随便一打不理解的一点,就是随便一打,然后排到前10T1是很水的模拟T2是一道数据结构贪心题,此时才发现欠缺的贪心技巧T3是一道数学式子化简,挺简单,但注意计算时爆long......
  • 8th 2022/7/6 模拟赛总结3
    这次打得不算好,因为该拿的分没拿到,如T3题意理解的偏差导致失分,T1找规律欠缺,推导欠缺导致窒息,T2是一道贪心+DP,贪心原来优化最优解的寻找方式T4则是一道字符串题,用最长公......