闲话 23.2.21

闲话

今天放的歌是 a 老师写的 攀登 - 逍遥散人
词写的挺好的
但是游戏区 up 主跨区做音乐确实比不过职业的(
我就不睿频了(

今天该推啥歌呢?

模拟赛(

好我又回来写模拟赛的题解了(
这套题背景是 nfls 众(?)人(

T1 玩水

你不过 T1 你过啥题啊
你不过题你打锤子啊
.jpg

我们称满足 \(a[n - 1, m] = a[n, m - 1]\) 的位置 \((n, m)\) 为好位置。
一张图有解,当且仅当存在一个好位置在另一个的严格左上方或相邻。然后直接判断即可。



T2 假人

场上看到 \((\max, +)\) 卷积首先想到的就是 minkowski 和,但是冲了个假结论后就弃了。

首先把值域平移到 \([0, 4]\) 上,然后考虑暴力 dp。
\(f(i, j)\) 为在前 \(i\) 组物品中选择 \(j\) 组时的最大权值和。可以证明,设 \(f_{i, k} (x) = f(i, 12x + k)\),则 \(\forall i, k,\ f_{i, k} (x)\) 是上凸的。

《证明》

首先有结论:取一个 \([0, 4]\cap \mathbb N\) 的子集 \(A\),若 \(\sum_{a\in A} a = 24\),则 \(\exists B\subset A, \sum_{b\in B} b = 12\)
总觉得数学那边有一道联赛题和这个很像,但是 oi 这边可以直接搜出正确性。当然也可以分讨(

然后可以考虑直接转移了。考察 \(f_{i, k}\) 代入 \(x - 1, x, x + 1\) 的过程。
考察 \(j : 12x + k - 12 \to 12x + k + 12\) 的过程中的最优方案,设第 \(m\) 组选择数量增加 \(\Delta_m\),这里 \(\Delta_m\in [-4, 4]\),且 \(\sum \Delta = 24\)。我们总能通过贪心的方式将这些值合并,得到一系列 \([0, 4]\) 的值。根据如上的结论,我们能取得两组和为 \(12\) 的选择方法。将权值和最大的一组施在 \(j : 12x + k - 12 \to 12 x + k\) 的过程中,我们能得到

\[f_{i, k}(x) \ge\frac{f_{i, k}(x - 1) + f_{i, k}(x + 1)}{2} \]

这自然引出了凸性。

说的道理

随后我们就可以通过分治求得每组的答案了,过程中通过 minkowski 和合并即可。

code
#include <bits/stdc++.h>
using namespace std; 
using pii = pair<int,int>; using vi = vector<int>; using vp = vector<pii>; using ll = long long; 
using ull = unsigned long long; using db = double; using ld = long double; using lll = __int128_t;
template<typename T1, typename T2> inline T1 max(T1 a, T2 b) { return a > b ? a : b; }
template<typename T1, typename T2> inline T1 min(T1 a, T2 b) { return a < b ? a : b; }
#define multi int T; cin >> T; for (register int CaseNo = 1; CaseNo <= T; ++ CaseNo)
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
#define eb emplace_back
#define pb pop_back
const int N = 1e5 + 10, mod = 1e9 + 7;
int n, t1, sum, k[N];

struct poly : vector<ll> {
    inline void redegree(int new_degree)  {
        resize(new_degree + 1);
    }
    inline int degree() const { 
        return (int)(size()) - 1;
    }
    inline friend poly operator* (poly f, poly g) {
        if (f.empty() or g.empty()) return { };
        poly ret;
        adjacent_difference(f.begin(), f.end(), f.begin());
        adjacent_difference(g.begin(), g.end(), g.begin());
        ret.eb(f.front() + g.front());
        merge(f.begin() + 1, f.end(), g.begin() + 1, g.end(), back_inserter(ret), greater<>());
        partial_sum(ret.begin(), ret.end(), ret.begin());
        return ret;
    }
    inline poly& operator *= (const poly& b) {
        *this = (*this) * b;
        return *this;
    }
} p[N];

inline void update(poly& a, poly b, int offset) {
    if (b.empty()) return;
    if (a.size() < b.size() + offset) 
        a.resize(b.size() + offset);
    for (int i = 0; i < b.size(); i++) 
        a[i + offset] = max(a[i + offset], b[i]);
}

array<poly, 12> solve(int l, int r) {
    array<poly, 12> ans;
    if (l == r) {
        for (int i = 0; i < k[l]; i++) ans[i].push_back(p[l][i]);
        return ans;
    } int mid = (l + r) >> 1;
    auto al = solve(l, mid), ar = solve(mid + 1, r);
    for (int li = 0; li < 12; li++)
        for (int ri = 0; ri < 12; ri++) update(ans[(li + ri) % 12], al[li] * ar[ri], li + ri >= 12);
    return ans;
}

signed main() {
    cin >> n;
    rep(i,1,n) {
        cin >> k[i]; sum += k[i] - 1;
        p[i].redegree(k[i]); 
        rep(j,0,k[i] - 1) cin >> p[i][j];
    } auto ret = solve(1, n);
    rep(t,0,sum) cout << ret[t % 12][t / 12] << ' ';
}



T3 切题

切题排行榜

用户名 \(\qquad\qquad\)格言 \(\qquad\qquad\qquad\qquad\qquad\qquad\qquad\qquad\) 题数

1 superay \(\qquad\) 躺上切题榜 rk1 共计 ??? 天 $\qquad\qquad\quad \ \ \ $ 464
2 wzy \(\qquad\qquad\) <script>alert("1")</script> \(\qquad\qquad\quad\) 408
3 lqs2015 \(\qquad\) 前排观看切题榜 rk1, rk2 神仙打架 /se $\qquad\ $ 392

直接按最大流模型建图后跑,即可获得 10~40pts 不等的好成绩。
要说的结论其实就是 Gale-Ryser 定理,也就是需要满足 \(\forall k, \sum_{i =1}^k a_i \le \sum_{i = 1}^m \min(b_i, k)\),不再赘述了。然后考虑如何维护。
我们设 \(c_k = \sum_{i = 1}^m [b_i \ge k]\),则可以发现 \(\sum_{i = 1}^m \min(b_i, k) = \sum_{i = 1}^k c_k\)。接下来只需要考虑如何维护 \(\sum_{i = 1}^k (c_i - a_i) \ge 0\)
这个可以直接维护了,但是我还是接着转化(
考虑取个反后加 \(\sum a_i\),也就是 \(\sum_{i = 1}^k c_i + \sum_{i = k + 1}^n a_i \ge \sum_{i = 1}^n a_i\)
这个好维护了。我们在线段树上第 \(k\) 个叶子放上式左侧的值,最后只需要看最小值即可。初始化时考虑对每个 \(k\) 计算 \(\sum_{i = 1}^m \min(b_i, k) + \sum_{i = k + 1}^n a_i\)。后面的值可以前缀和优化,前面的值可以整个指针 \(i\) 维护 \(\le k\) 的最大的 \(b_i\),前半部分求和,后半部分贡献都是 \(i\)
\(a, b\) 的加一减一也好维护了。粘个场上写的注释

  1. 找到 a[id] 对应最靠前的 a 的排名 假设是 k
    [1 ~ k - 1] + 1
  2. 找到 a[id] 对应最靠后的 a 的排名 假设是 k
    [1 ~ k - 1] - 1
  3. 找到 b[id] + 1 后的值 c 以及 c 对应的位置
    [k ~ n] + 1
  4. 找到 b[id] - 1 后的值 c 以及 c 对应的位置
    [k ~ n] - 1

然后就是 \(O((n + q)\log n)\) 了。

code
#include <bits/stdc++.h>
using namespace std; 
using pii = pair<int,int>; using vi = vector<int>; using vp = vector<pii>; using ll = long long; 
using ull = unsigned long long; using db = double; using ld = long double; using lll = __int128_t;
template<typename T1, typename T2> T1 max(T1 a, T2 b) { return a > b ? a : b; }
template<typename T1, typename T2> T1 min(T1 a, T2 b) { return a < b ? a : b; }
#define multi int T; cin >> T; for (register int CaseNo = 1; CaseNo <= T; ++ CaseNo)
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
#define eb emplace_back
#define pb pop_back
const int N = 3e5 + 10, mod = 1e9 + 7;
int n, m, a[N], b[N], va[N], vb[N], typ, id;
ll sum, tsum, suma[N], sumb[N], wtf[N];

struct SegmentTree {
    #define ls (p << 1)
    #define rs (p << 1 | 1)
    #define val(p) seg[p].val
    #define lzy(p) seg[p].lzy
    struct node {
        ll val; int lzy;
    } seg[N << 2];

    void ps_d(int p) {
        if (!lzy(p)) return ;
        val(ls) += lzy(p), val(rs) += lzy(p);
        lzy(ls) += lzy(p), lzy(rs) += lzy(p);
        lzy(p) = 0;
    }

    void build(int p, int l, int r) {
        if (l == r) {
            val(p) = wtf[l];
            return;
        } int mid = l + r >> 1;
        build(ls, l, mid);
        build(rs, mid + 1, r);
        val(p) = min(val(ls), val(rs));
    }

    void update(int p, int l, int r, int L, int R, int v) {
        if (L > R) return ;
        if (L <= l and r <= R) {
            val(p) += v, lzy(p) += v;
            return;
        } int mid = l + r >> 1; ps_d(p);
        if (L <= mid) update(ls, l, mid, L, R, v);
        if (mid < R) update(rs, mid + 1, r, L, R, v);
        val(p) = min(val(ls), val(rs));
    }
} Tr;

struct fenwick {
	int Index[N], siz;
	void add(int p, int v) {
		siz += v; ++ p;
		for (; p <= 750005; p += p & -p) 
            Index[p] += v;
	} 
	int query(int p) {
		int ret = 0; ++ p;
		for (; p > 0; p ^= p & -p) 
			ret += Index[p];
		return ret;
	}
} fw;

signed main() {
    cin >> n >> m;
    rep(i,1,n) cin >> a[i], va[i] = a[i], sum += a[i], fw.add(a[i], 1);
    rep(i,1,m) cin >> b[i], vb[i] = b[i];
    sort(va + 1, va + 1 + n, greater<>());
    sort(vb + 1, vb + 1 + m);
    rep(i,1,max(n, m)) suma[i] = suma[i - 1] + va[i], sumb[i] = sumb[i - 1] + vb[i];
    vb[m + 1] = 1e9;
    wtf[0] = sum; 
    for (int i = 1, now = 0; i <= n; ++ i) {
        while (vb[now + 1] <= i) ++ now;
        wtf[i] = suma[n] - suma[i] + sumb[now] + i * (m - now);
    } 
    Tr.build(1, 0, n);
    multi {
        cin >> typ >> id;
        if (typ == 1) {
            ++ sum;
            int id2 = n - fw.query(a[id]) + 1;
            Tr.update(1, 0, n, 0, id2 - 1, 1);
            fw.add(a[id], -1); fw.add(++ a[id], 1);
        } else if (typ == 2) {
            -- sum;
            int id2 = n - fw.query(a[id] - 1);
            Tr.update(1, 0, n, 0, id2 - 1, -1);
            fw.add(a[id], -1); fw.add(-- a[id], 1);
        } else if (typ == 3) Tr.update(1, 0, n, ++ b[id], n, 1);
        else Tr.update(1, 0, n, b[id] --, n, -1);
        cout << (Tr.seg[1].val >= sum) << '\n';
    }
}



T4 天下第一

\(\color{black}{\text{d}}\color{red}{\text{jq_cpp}}\) 是天下第一的。

看到这句话我还以为中国象棋(

考虑一条链的条件:无环、点数 \(- 1 =\) 边数、度数 \(\le 2\)

首先考虑对每个右端点求出满足无环的最左端点。这个可以双指针 + lct 简单(?)维护。
然后考虑对每个右端点求出满足度数 \(\le 2\) 的最左端点。这个可以双指针 + 桶简单维护。
然后我们现在有了一段极长的区间了,我们现在要的就是满足 点数 \(- 1 =\) 边数 的左端点数量,这个套路线段树。
由于无环,点数 \(- 1\ge\) 边数,因此我们需要的就是 点数 \(-\) 边数 的最小值出现次数,并若最小值为 \(1\) 则出现次数可以作为答案。线段树即可。
总时间复杂度 \(O((n + m)\log n)\)
感觉正确性和 GERALD07 的证法差不多,而且做法也差不多。我正好前两天切了那题,然后 lct 是粘的。

代码不难写。

code
#include <bits/stdc++.h>
using namespace std; 
using pii = pair<int,int>; using vi = vector<int>; using vp = vector<pii>; using ll = long long; 
using ull = unsigned long long; using db = double; using ld = long double; using lll = __int128_t;
template<typename T1, typename T2> T1 max(T1 a, T2 b) { return a > b ? a : b; }
template<typename T1, typename T2> T1 min(T1 a, T2 b) { return a < b ? a : b; }
#define multi int T; cin >> T; for (register int CaseNo = 1; CaseNo <= T; ++ CaseNo)
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
#define eb emplace_back
#define pb pop_back
const int N = 2e6 + 10, mod = 1e9 + 7;
int n, m, t1, t2;
ll ans;
vi e[N], f[N];

struct Link_Cut_Tree {
	#define ls(p) spl[p].ch[0]
	#define rs(p) spl[p].ch[1]
	#define sn(p, s) spl[p].ch[s]
	#define fa(p) spl[p].fa
	#define fl(p) spl[p].fl
	#define val(p) spl[p].val
	#define mnv(p) spl[p].mnv
	#define pos(p) spl[p].pos
	#define notrt(p) ((p == ls(fa(p))) or (p == rs(fa(p))))

	struct node {
		int fa, ch[2], val, mnv, pos;
		bool fl;
	} spl[N << 2];

	inline void flip(int p) { swap(ls(p), rs(p)), fl(p) ^= 1; }
	inline void ps_p(int p) {
		if (ls(p) and rs(p)) {
			if (mnv(ls(p)) < mnv(rs(p))) mnv(p) = mnv(ls(p)), pos(p) = pos(ls(p));
			else mnv(p) = mnv(rs(p)), pos(p) = pos(rs(p));
		} else if (!ls(p) and !rs(p)) {
			mnv(p) = 1e9 + 10;
		} else if (!ls(p) and rs(p)) {
			mnv(p) = mnv(rs(p)), pos(p) = pos(rs(p));
		} else {
			mnv(p) = mnv(ls(p)), pos(p) = pos(ls(p));
		} if (val(p) < mnv(p)) mnv(p) = val(p), pos(p) = p;
	}
	inline void ps_d(int p) {
		if (fl(p)) {
			flip(ls(p)), flip(rs(p));
			fl(p) = 0;
		}
	}

	inline void rotate(int x) {
		int y = fa(x), z = fa(y), k = rs(y) == x, son = sn(x, !k);
		if (notrt(y)) sn(z, rs(z) == y) = x;
		sn(x, !k) = y, sn(y, k) = son;
		if (son) fa(son) = y;
		fa(y) = x, fa(x) = z;
		ps_p(y), ps_p(x);
	}
	void splay(int x) {
        static int stk[N];
	    int z = 0, y = x;
		stk[++ z] = x;
		while (notrt(y)) stk[++ z] = y = fa(y);
		while (z) ps_d(stk[z --]);
		while (notrt(x)) {
			y = fa(x), z = fa(y);
			if (notrt(y)) rotate((ls(y) == x) ^ (ls(z) == y) ? x : y);
			rotate(x);
		} ps_p(x);
	}

	inline void access(int x) {
		int y = 0;
		while (x) {
			splay(x); rs(x) = y; ps_p(x);
			x = fa(y = x);
		}
	}

	inline void makert(int x) {
		access(x); splay(x); flip(x);
	}

	int findrt(int x) {
		access(x), splay(x);
		while (ls(x)) x = ls(x);
		return splay(x), x;
	}

	inline void split(int u, int v) {
		makert(u), access(v), splay(v);
	}

	inline void link(int u, int v) {
		makert(u);
		if (findrt(v) != u) fa(u) = v;
	} 
	inline void cut(int u, int v) {
		makert(u);
		if (findrt(v) == u and fa(v) == u and ls(v) == 0) {
			fa(v) = rs(u) = 0;
			ps_p(u);
		}
	}

	#undef ls 
    #undef rs
	#undef sn
	#undef fa
	#undef fl
	#undef val
	#undef mnv
	#undef pos
	#undef notrt
} lct;

struct SegmentBeats {
    #define ls (p << 1)
    #define rs (p << 1 | 1)
    #define mnv(p) seg[p].mnv
    #define cnt(p) seg[p].cnt
    #define lzy(p) seg[p].lzy

    struct node {
        int mnv, cnt, lzy;
        node(int m = 0, int c = 0, int l = 0) { mnv = m, cnt = c, lzy = l; } 
        inline friend node operator+ (const node& a, const node& b) {
            node ret(min(a.mnv, b.mnv));
            if (ret.mnv == a.mnv) ret.cnt += a.cnt;
            if (ret.mnv == b.mnv) ret.cnt += b.cnt;
            return ret;
        }
    } seg[N << 2];

    void build(int p, int l, int r) {
        if (l == r) return void(seg[p].cnt = 1);
        int mid = l + r >> 1;
        build(ls, l, mid), build(rs, mid + 1, r);
        seg[p] = seg[ls] + seg[rs];
    }

    inline void ps_d(int p) {
        if (!lzy(p)) return;
        mnv(ls) += lzy(p), lzy(ls) += lzy(p);
        mnv(rs) += lzy(p), lzy(rs) += lzy(p);
        lzy(p) = 0;
    }

    void update(int p, int l, int r, int L, int R, int v) {
        if (L <= l and r <= R) {
            mnv(p) += v, lzy(p) += v;
            return;
        } int mid = l + r >> 1; ps_d(p);
        if (L <= mid) update(ls, l, mid, L, R, v);
        if (mid < R) update(rs, mid + 1, r, L, R, v);
        seg[p] = seg[ls] + seg[rs];
    }

    node query(int p, int l, int r, int L, int R) {
        if (L <= l and r <= R) return seg[p];
        int mid = l + r >> 1; ps_d(p);
        if (R <= mid) return query(ls, l, mid, L, R);
        else if (mid < L) return query(rs, mid + 1, r, L, R);
        return query(ls, l, mid, L, R) + query(rs, mid + 1, r, L, R);
    }
} Tr;

int buk[N], cnt;
inline void clear(int p) { buk[p] = 0; }
inline bool add(int p) {
    ++ buk[p];
    if (buk[p] == 1) ++ cnt;
    if (buk[p] == 2) -- cnt;
    return buk[p] < 3;
}
inline void del(int p) {
    for (auto v : f[p]) 
        lct.cut(p, v), -- buk[p], -- buk[v];
}

signed main() {
    cin >> n >> m;
    rep(i,1,m) {
        cin >> t1 >> t2;
        if (t1 > t2) swap(t1, t2);
        e[t2].eb(t1);
    }
    rep(i,1,n) sort(e[i].begin(), e[i].end(), greater<>());
    int l = 1, r = 2;
    Tr.update(1, 1, n, 1, 1, 1);
    Tr.build(1, 1, n);
    while (r <= n) {
        Tr.update(1, 1, n, 1, r, 1);
        while (!e[r].empty() && e[r].back() < l) e[r].pop_back();
        while (l <= r && e[r].size() > 2) {
            while (!e[r].empty() && e[r].back() <= l) e[r].pop_back();
            del(l++);
        }
        for (auto v : e[r]) {
            while (buk[v] > 1) del(l++);
            while (lct.findrt(v) == lct.findrt(r)) del(l++);
            if (v >= l) {
                f[v].emplace_back(r);
                ++buk[v], ++buk[r];
                lct.link(v, r);
                Tr.update(1, 1, n, 1, v, -1);
            }
        }
        auto res = Tr.query(1, 1, n, l, r);
        if (res.mnv == 1) ans += res.cnt;
        ++r;
    }
    cout << ans + 1 << "\n";
}
posted @ 2023-02-21 17:58  joke3579  阅读(120)  评论(3编辑  收藏  举报