Loading

3.8 CW 模拟赛 赛时记录

前言

不贪跟策略, 数据检验
冷静, 耐心, 放下

手好冷

看题

\(\rm{T1}\)

马周常数

  • 先从已经确定的部分开始考虑

    • 拆分序列法
      • 一般从可以严格分成两部分来考虑
      • 分成两个同性质的串加上一个构造 \((\)例如 \((X)Y\) 用于构造新的合法括号串\()\)
      • 分成两个同性质的串, 递归分治
  • 先排除无效元素

  • \(\rm{dp}\)

    • 先考虑最终答案的表达式 \((\)合法解的构造方案\()\) , 基础上进行 \(\rm{dp}\)
    • 分段法
    • 拿几个维度表示状态, 逐个转移
    • 子序列类问题, 一维对应原串

\(\rm{T2}\)

妈的, 似有故人来

  • 定义合法情况, 要求方案数
    • 将条件数学化
    • 往往利用 \(\rm{dp}\) , 结合约束处理当前方案数
      • 关注构造方案 / 顺序
      • 关注本质重复的转移是否存在
      • 由合法情况导出的答案
        • 先考虑最终答案的表达式 \((\)合法解的构造方案\()\) , 基础上进行 \(\rm{dp}\)
    • 找到所有情况统一的构造方案
      • 构造: 先推性质, 不行打表
        • 贪心
          • 往往应该把选择权留到后面去
        • 超过一半类
          • 构造一组都满足 \(a\) 条件的, 然后剩下的构造一个满足 \(a\) , 一个满足 \(b\) 的, 这样一定超过一半
    • 逆向思维
    • 列出合法情况需要满足的表达式
      • 在原序列中贪心选择最优情况
      • 然后在基础上进行调整

\(\rm{T3}\)

这下何山真讲过了, 和上次一样忘记了
自己观察了

  • 交换相邻元素性质
    • \(a\)\(b\) , 交换 \(|a - b|\)
    • 对于两个串的定位问题, 每个元素定位的花费就是关于其的逆序对个数
      证明: 从大权值到小权值, 逐个固定位置
    • 往往用固定之前的部分, 移动当前的部分来解决
    • 如果交换有约束
      • 可以认为一个数只能在固定区间内移动
      • 可以看做对相对位置的约束

\(\rm{T4}\)

不会是 \(\rm{DS}\)


做了这么多题, 还是知道自己肯定没时间去想 \(\rm{T3, T4}\) 正解的, 因此先开 \(\textrm{T1, T2}\) , \(\textrm{T3}\) 可以小磕一下拿下高档一点的, 然后顺手把 \(\rm{T4}\) 送的开了就功德圆满了

因此开打


\(\rm{T1}\)

  • 先从已经确定的部分开始考虑

    • 拆分序列法
      • 一般从可以严格分成两部分来考虑
      • 分成两个同性质的串加上一个构造 \((\)例如 \((X)Y\) 用于构造新的合法括号串\()\)
      • 分成两个同性质的串, 递归分治
  • 先排除无效元素

  • \(\rm{dp}\)

    • 先考虑最终答案的表达式 \((\)合法解的构造方案\()\) , 基础上进行 \(\rm{dp}\)

思路

先找对于一个 \(b\), \(\textrm{trans}(b)\) 的性质
大概找到了一个构造方法, 对于 \(1 \leq i \leq |b|\) , \(\textrm{trans}(b)_i\) 就是 \(\geq b_i\) 的数中第一个没有被使用的

因为要维护, 所以形式化一下
最初 \(p_i \gets i\) , 每次对于 \(b_i\) , 放置 \(p_{b_i}\) , \(p_{b_i} \gets p_{b_i} + 1\) 然后对于所有 \(p_k = p_{b_i} - 1, p_k \gets p_k + 1\)

因此知道了 \(\textrm{trans}(b)_1\) , 我们可以推出 \(b_1 = \textrm{trans}(b)_1, p_{b_1} = \textrm{trans}(b)_1\) , 在接下来的放置中, \(p_{b_1} \gets p_{b_1} + 1\) , 对于所有 \(p_k = p_{b_1} - 1, p_k \gets p_k + 1\)

不对不对, 换思路, 这个一点不好维护

发现一个更好的性质, 对于 \(b\) 一个位置的填法, 取决于之前的空窗期位置
等一下等一下, 好像对了

现在问题就是怎么高效维护这个东西, 打算死磕一下
似乎可以二分, 做到 \(\mathcal{O} (n \log n)\) , 做完了做完了

慢, 为什么想歪这么多, 还好拐回来了
为什么我想不到怎么 \(\mathcal{O} (1)\) 求区间和

实现

本来想写线段树上二分的, 但是想起来之前被树状数组双 \(\log\) 薄纱的经历决定写双 \(\log\)
傻逼 \(\textrm{fastio}\) , 能不能注明是 \(\textrm{Lunix}\) 下的

代码
#include <bits/stdc++.h>
struct fio{
	#define isdigit(x) (x >= '0' && x <= '9')
	char buf[1 << 20], *p1, pbuf[1 << 20], *p2, *pp;
	fio() : p1(buf), p2(buf), pp(pbuf){}
	~fio(){fwrite(pbuf, 1, pp - pbuf, stdout);}
	inline char gc(){return getchar(); }
	inline void pc(const char &c){if (pp - pbuf == 1 << 20) fwrite(pbuf, 1, 1 << 20, stdout), pp = pbuf;*pp ++ = c;}
	inline bool blank(char ch){return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';}
	template <class T> inline void read(T &x){
		long double tmp = 1;bool sign = 0;char ch = gc();x = 0;
		for (;!isdigit(ch);ch = gc()) if (ch == '-') sign = 1;
		for (;isdigit(ch);ch = gc()) x = x * 10 + (ch - '0');
		if (ch == '.') for (ch = gc();isdigit(ch);ch = gc()) tmp /= 10.0, x += tmp * (ch - '0');
		if (sign) x = -x;
	}
	inline void read(char *s){
    	char ch = gc();for (;blank(ch);ch = gc());
    	for (;!blank(ch);ch = gc()) *s ++ = ch;*s = 0;
    }
    inline void read(char &c){for (c = gc();blank(c);c = gc());}
	template <class T> inline void write(T x){
		if (x < 0) x = -x, putchar('-');int sta[30];int top = 0;
		do sta[top ++] = x % 10, x /= 10;while (x);
		while (top) putchar(sta[-- top] + '0');
	}
}io;
#define int long long
#define lowbit(x) (x & -(x))
const int MAXN = 2e6 + 20;
const int MOD = 1e9 + 7;
namespace calc {
    int add(int a, int b) { return a + b >= MOD ? a + b - MOD : a + b; }
    int mul(int a, int b) { return (a * b) % MOD; }
    int mus(int a, int b) { return a - b < 0 ? a - b + MOD : a - b; }
    void addon(int &a, int b) { a = add(a, b); }
    void mulon(int &a, int b) { a = mul(a, b); }
} using namespace calc;

int n, m = 0;
int a[MAXN];

struct BIT {
    int val[MAXN << 1];
    BIT() { memset(val, 0, sizeof val); }
    void modify(int p, int d) { while (p <= m) val[p] += d, p += lowbit(p); }
    int query(int p) { int ans = 0; while (p > 0) ans += val[p], p -= lowbit(p); return ans; }
} bit;

class bsearcher {
private:

public:
    bool check(int x, int p) {  return bit.query(p) - bit.query(x - 1) == p - x + 1; }
    int solve(int p) {
        int left = 1, right = p + 1, ans = 0;
        while (left < right) {
            int mid = (left + right) >> 1;
            if (check(mid, p)) ans = mid, right = mid;
            else left = mid + 1;
        }
        return ans;
    }
} src;

signed main() {
    // freopen("ex_trans4.in", "r", stdin);
    io.read(n);
    for (int i = 1; i <= n; i++) io.read(a[i]), m = std::max(m, a[i]);

    int ans = 1;
    for (int i = 1; i <= n; i++) {
        bit.modify(a[i], 1);
        int p = src.solve(a[i]);
        mulon(ans, a[i] - p + 1);
    }
    io.write(ans);

    return 0;
}

\(\rm{T2}\)

发现以后实现还是单开, 不然容易挂
框架确实还是需要的

神经, 吃个送分题这么费劲
剩下的应该就是能拿多少拿多少了, 按照时间分配来, 不贪

  • 定义合法情况, 要求方案数
    • 将条件数学化
    • 往往利用 \(\rm{dp}\) , 结合约束处理当前方案数
      • 关注构造方案 / 顺序
      • 由合法情况导出的答案
        • 先考虑最终答案的表达式 \((\)合法解的构造方案\()\) , 基础上进行 \(\rm{dp}\)
        • 关注本质重复的转移是否存在
    • 找到所有情况统一的构造方案
      • 构造: 先推性质, 不行打表
        • 贪心
          • 往往应该把选择权留到后面去
  • 先从已经确定的部分开始考虑
    • 拆分序列法
      • 一般从可以严格分成两部分来考虑
      • 分成两个同性质的串加上一个构造 \((\)例如 \((X)Y\) 用于构造新的合法括号串\()\)
      • 分成两个同性质的串, 递归分治
  • 先排除无效元素

思路

题意

一个合法序列 aa 定义为

  • liairil_i \leq a_i \leq r_i
  • 不存在和相等的前后缀

给定 l,rl, r , 求有多少种合法序列

发现 \(n\) 很小
转化到值域上面去

我们把 \(\sum a_i\) 拆成一个个 \(1\) , 转化成划线的问题, 好像也不行
时间到了, 只能打 \(10\)

\(\rm{T3}\)

那我不爆了, 真服了, 啥都不会
新增贪心公式, 然后跟着策略走

思路

何山大抵是讲过这个的, 我大概也是忘完了的

哎哎哎哥们好像送了 \(40\)
谢谢谢谢

没时间了, 打 \(40\)

posted @ 2025-03-08 11:57  Yorg  阅读(26)  评论(0)    收藏  举报