[题解]血战狗熊岭

\[\color{red}{\text{校长者,真神人也,左马桶,右永神,会执利笔破邪炁,何人当之?}} \\ \begin{array}{|} \hline \color{pink}{\text{The principal is really a god}} \\ \color{pink}{\text{with a closestool on the left and Yongshen on the right}} \\ \color{pink}{\text{holding a sharp pen to pierce the truth}} \\ \color{pink}{\text{Who can resist him? }} \\ \hline \end{array} \\ \begin{array}{|} \hline \color{green}{\text{校長は本当に神であり、左側にトイレ、右側にヨンシェンがあり}} \\ \color{green}{\text{鋭いペンを持って真実を突き刺している。誰が彼に抵抗できるだろうか? }} \\ \hline \end{array} \\ \begin{array}{|} \hline \color{lightblue}{\text{Le principal est vraiment un dieu}} \\ \color{lightblue}{\text{avec des toilettes à gauche et Yongshen à droite}} \\ \color{lightblue}{\text{tenant un stylo pointu pour percer la vérité}} \\ \color{lightblue}{\text{Qui peut lui résister ? }} \\ \hline \end{array} \\ \begin{array}{|} \hline \color{purple}{\text{Der Direktor ist wirklich ein Gott}} \\ \color{purple}{\text{mit einer Toilette links und Yongshen rechts}} \\ \color{purple}{\text{der einen spitzen Stift hält}} \\ \color{purple}{\text{um die Wahrheit zu durchdringen.}} \\ \color{purple}{\text{Wer kann ihm widerstehen? }} \\ \hline \end{array} \\ \begin{array}{|} \hline \color{cyan}{\text{Principalis deus est, Yongshen a dextris cum latrina}} \\ \color{cyan}{\text{acuto stylo ad perforandum veritatem: quis resistet ei? }} \\ \hline \end{array} \\ \color{red}{\text{对曰:“无人,狗欲当之,还请赐教!”}} \\ \newcommand\brak[1]{\left({#1}\right)} \newcommand\Brak[1]{\left\{{#1}\right\}} \newcommand\d[0]{\text{d}} \newcommand\string[2]{\genfrac{\{}{\}}{0pt}{}{#1}{#2}} \newcommand\down[2]{{#1}^{\underline{#2}}} \newcommand\ddiv[2]{\left\lfloor\frac{#1}{#2}\right\rfloor} \newcommand\udiv[2]{\left\lceil\frac{#1}{#2}\right\rceil} \newcommand\lcm[0]{\operatorname{lcm}} \newcommand\set[1]{\left\{{#1}\right\}} \newcommand\ceil[1]{\left\lceil{#1}\right\rceil} \newcommand\floor[1]{\left\lfloor{#1}\right\rfloor} \newcommand\rhs[1]{\;\text{Rhs}\;#1} \newcommand\lhs[1]{\;\text{Lhs}\;#1} \newcommand\Vec[1]{\vec{\mathbf{#1}}} \newcommand\rank[0]{\text{rank}} \]




  \(\mathcal{Back\;To\;The\;Menu}\).

2022-03-14 血战狗熊岭

  要是我做过 T2 就好了,T2 一共就花了我俩小时,T3 点分的部分都打不出来,我枯了。

神必的集合 / Set

  实际上这个东西就是一个关于异或封闭的群,但是这个性质并没有什么用......但是我们显然可以找出这个向量集的线性基。于是我们可以针对这个线性基进行一些统计。

  先考虑 \(m=0\) 怎么做,没有限制,我们可以 DP 它的线性基,为了去重,计数的线性基应当为该线性空间的__最简线性基__,即如果第 \(i\) 位存在一个主元,那么要求线性基中所有元素第 \(i\) 维被消掉,也就是说,该线性基应当为所有向量进行__高斯约旦消元__之后的样子。然后就比较好做了,设 \(f(i,j)\) 表示前 \(i\) 维,有 \(j\) 个主元时的方案数,转移的时候,只需要考虑一下该位是否填入主元即可,这部分的代码实现是这样的:

namespace noConstri {

int f[Maxn + 5][Maxn + 5];
inline void work() {
    f[0][0] = 1;
    rep (i, 1, n) {
        rep (j, 0, i) {
            f[i][j] = f[i - 1][j];
            if (j) chkadd(f[i][j], (1ll << (i - j)) % mod * 1ll * f[i - 1][j - 1] % mod);
        }
    }
    int ans = 0;
    rep (j, 0, n) chkadd(ans, f[n][j]);
    writln(ans);
}

} // namespace noConstri

  接下来考虑 \(m\neq 0\) 时怎么做,限制照题目的翻译 \(x\) 大的数是 \(y\) 似乎在 DP 中难以刻画,因为我们总不可能将当前的序列记录下来吧......因此,将这个限制变个形:小于 \(y_i\) 的数有 \(x_i-1\) 个,并且 \(y\) 存在于这个集合。对于后面那个限制,我们将所有 \(y_i\) 的线性基建立出来,最后就相当于我们可以在这个线性基 \(S\) 中加入一个空的维数,统计合法的线性基的个数。下面称 \(S\) 的第 \(i\) 维为 \(S_i\),当 \(S_i=0\) 时,这一位没有主元。

  先考虑我们手头有一个线性基时,如何统计这个线性基中小于 \(y_i\) 的数个数。类似于数位 DP,从高位往低位走,假设当前所有最高位都已经和 \(y_i\) 匹配(抵上界),当前位置 \(p\) 上,\(y_i(p)\)\(1\),并且当前异或出来的数 \(t\) 的这一位为 \(0\),由于 \(y_i\) 在线性基中,因此第 \(i\) 位一定有主元,但是我们可以选择不异或这个数,让 \(x\) 的这一位为 \(0\),那么 \(x\) 的后面几位都可以任意选,记 \(\displaystyle cnt_p=\sum_{j<p}[S_j\neq 0]\),那么就有 \(2^{cnt_p}\) 种元素;如果当前 \(t\) 的这一位为 \(1\),如果 \(S_i=0\) 就不管他,否则可以选择让 \(t\) 异或上 \(S_i\)\(t\) 这一位变成 \(0\),这样后面又有 \(2^{cnt_p}\) 种比 \(y_i\) 小的元素。而当 \(y_i\) 这一位为 \(0\) 时,无法产生小于的贡献。

  不难发现,最后统计出来的结果一定是一个 \(\displaystyle \sum 2^{k_i}\),而它__等于__ \(x_i-1\),而我们将 \(x-1\) 分解成 \(\displaystyle \sum 2^t\),将会得到__唯一__的 \(\set{k_i}\),对于每一个 \(k_i\),它给出的限制将会是:最终构造的线性基中,从低到高第 \(k_i+1\) 个__非零__维 \(p_i\),应当满足 \(y(p_i)=1\),并且对于所有 \(cnt_j\notin \set{k_i}\)\(S_j\neq 0\) 的位置 \(j\)\(y(j)=0\),否则将会给 \(x\) 加上一些奇怪的位数,让最后的 \(x\) 凑不出来。

  我们可以继续用前面的 DP,定义 \(f(i,j)\) 表示对于线性基的前 \(i\) 位,\(cnt_i=j\) 时的方案数,转移类似即可。

/** @author __Elaina__ */

// #pragma GCC optimize("Ofast")

#include <bits/stdc++.h>
using namespace std;

#define USING_FREAD
// #define NDEBUG
#include <cassert>

namespace Elaina {
/** その可憐な少女は魔女であり、旅人でした。 ―― そう、私です! */

#define rep(i, l, r) for(int i = (l), i##_end_ = (r); i <= i##_end_; ++i)
#define repf(i, l, r) for (int i = (l), i##_end_ = (r); i < i##_end_; ++i)
#define drep(i, l, r) for(int i = (l), i##_end_ = (r); i >= i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
#define whole(v) ((v).begin()), ((v).end())
#define bitcnt(s) (__builtin_popcount(s))
/** @warning no forced type conversion */
#define rqr(x) ((x) * (x))
#define y0 FUCK_UP
#define y1 MOTHER_FUCKER
#define masdf(...) fprintf(stderr, __VA_ARGS__)

typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> pii;
typedef vector<int> vset;

template<class T> inline T fab(T x) { return x < 0 ? -x : x; }
template<class T> inline void chkmin(T& x, const T rhs) { x = std::min(x, rhs); }
template<class T> inline void chkmax(T& x, const T rhs) { x = std::max(x, rhs); }

#ifdef USING_FREAD
inline char qkgetc() {
# define BUFFERSIZE 1 << 20
    static char BUF[BUFFERSIZE], *p1 = BUF, *p2 = BUF;
    return p1 == p2 && (p2 = (p1 = BUF) + fread(BUF, 1, BUFFERSIZE, stdin), p1 == p2) ? EOF : *p1++;
# undef BUFFERSIZE
}
# define CHARRECEI qkgetc()
#else
# define CHARRECEI getchar()
#endif

template<class T> inline T readret(T x) {
    x = 0; int f = 0; char c;
    while (!isdigit(c = CHARRECEI)) if(c == '-') f = 1;
    for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
    return f ? -x : x;
}
template<class T> inline void readin(T& x) {;
    x = 0; int f = 0; char c;
    while (!isdigit(c = CHARRECEI)) if (c == '-') f = 1;
    for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
    if (f) x = -x;
}
template<class T, class... Args> inline void readin(T& x, Args&... args) {
    readin(x), readin(args...);
}
template<class T> inline void writln(T x, char c = '\n') {
    if (x < 0) putchar('-'), x = -x;
    static int __stk[55], __bit = 0;
    do __stk[++__bit] = x % 10, x /= 10; while (x);
    while (__bit) putchar(__stk[__bit--] ^ 48);
    putchar(c);
}

} // namespace Elaina
using namespace Elaina;

const int mod = 998244353;
const int Maxn = 200;
inline void chkadd(int& x, int y) { if ((x += y) >= mod) x -= mod; }

int n, m;
ll rk[Maxn + 5], y[Maxn + 5];
ll acpt[Maxn + 5];
int f[Maxn + 5][Maxn + 5];

ll S[Maxn + 5]; int mx; // at least
inline void insert(ll x) {
    for (int i = n - 1; ~i; --i) if (x >> i & 1) {
        if (!S[i]) return S[i] = x, void();
        x ^= S[i];
    }
}

signed main() {
    freopen("set.in", "r", stdin);
    freopen("set.out", "w", stdout);
    readin(n, m);
    rep (i, 1, m) {
        readin(rk[i], y[i]), --rk[i];
        insert(y[i]);
    }
    ll U = (1ll << n) - 1;
    rep (i, 0, n) acpt[i] = U;
    rep (i, 1, m) {
        if (rk[i]) chkmax(mx, 64 - __builtin_clzll(rk[i]));
        rep (j, 0, n - 1) {
            if (rk[i] >> j & 1) acpt[j + 1] &= y[i];
            else acpt[j + 1] &= (U ^ y[i]);
        }
    }
    if (!S[0]) f[0][0] = 1; // dont forget to statistic the set with only 0
    if (acpt[1] & 1) f[0][1] = 1;
    rep (i, 1, n - 1) {
        if (S[i]) { // force to fill 1
            // this vector is fixed, so no coefficient
            rep (j, 1, i + 1) if (acpt[j] >> i & 1)
                chkadd(f[i][j], f[i - 1][j - 1]);
        }
        else {
            rep (j, 1, i + 1) if (acpt[j] >> i & 1)
                chkadd(f[i][j], (1ll << i + 1 - j) % mod * 1ll * f[i - 1][j - 1] % mod);
            rep (j, 0, i) chkadd(f[i][j], f[i - 1][j]);
        }
    }
    int ans = 0;
    rep (j, mx, n) chkadd(ans, f[n - 1][j]);
    writln(ans);
    return 0;
}

法阵 / Fz

  听说原题是 LOJ 的三连跳?我也没做过,就算了......

  刚开始走偏了一点,考虑的是移动右端点,维护左端点,移动每一个右端点的时候,考虑多出来的合法的三元组,看上去有很多,但是如果用类似单调性的性质来去掉不优状态之后,多出来的三元组十分有限,但是要对于每一个左端点都维护一个这个东西感觉有点过于困难了......但是前面的东西还有一个地方可以用:用单调性的性质去掉不优状态。

  仔细分析一下所谓单调性:一个中间的元素 \(i\),选择的元素在相等的情况下,左边一定是越近越好,右边一定是越远越好,然后你会发现我们只需要搞出两个单增的单调栈,并且选择的其中一个二元组只有可能是这两个栈中相邻的元素,对于所有二元组,维护右端点的最优解即可。

  总时间复杂度 \(\mathcal O(n\log n)\).

/** @author __Elaina__ */

#pragma GCC optimize("Ofast")

#include <bits/stdc++.h>
using namespace std;

#define USING_FREAD
// #define NDEBUG
#include <cassert>

namespace Elaina {
/** その可憐な少女は魔女であり、旅人でした。 ―― そう、私です! */

#define rep(i, l, r) for(int i = (l), i##_end_ = (r); i <= i##_end_; ++i)
#define repf(i, l, r) for (int i = (l), i##_end_ = (r); i < i##_end_; ++i)
#define drep(i, l, r) for(int i = (l), i##_end_ = (r); i >= i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
#define whole(v) ((v).begin()), ((v).end())
#define bitcnt(s) (__builtin_popcount(s))
/** @warning no forced type conversion */
#define rqr(x) ((x) * (x))
#define y0 FUCK_UP
#define y1 MOTHER_FUCKER
#define masdf(...) fprintf(stderr, __VA_ARGS__)

typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> pii;
typedef vector<int> vset;

template<class T> inline T fab(T x) { return x < 0 ? -x : x; }
template<class T> inline void chkmin(T& x, const T rhs) { x = std::min(x, rhs); }
template<class T> inline void chkmax(T& x, const T rhs) { x = std::max(x, rhs); }

#ifdef USING_FREAD
inline char qkgetc() {
# define BUFFERSIZE 1 << 20
    static char BUF[BUFFERSIZE], *p1 = BUF, *p2 = BUF;
    return p1 == p2 && (p2 = (p1 = BUF) + fread(BUF, 1, BUFFERSIZE, stdin), p1 == p2) ? EOF : *p1++;
# undef BUFFERSIZE
}
# define CHARRECEI qkgetc()
#else
# define CHARRECEI getchar()
#endif

template<class T> inline T readret(T x) {
    x = 0; int f = 0; char c;
    while (!isdigit(c = CHARRECEI)) if(c == '-') f = 1;
    for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
    return f ? -x : x;
}
template<class T> inline void readin(T& x) {;
    x = 0; int f = 0; char c;
    while (!isdigit(c = CHARRECEI)) if (c == '-') f = 1;
    for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
    if (f) x = -x;
}
template<class T, class... Args> inline void readin(T& x, Args&... args) {
    readin(x), readin(args...);
}
template<class T> inline void writln(T x, char c = '\n') {
    if (x < 0) putchar('-'), x = -x;
    static int __stk[55], __bit = 0;
    do __stk[++__bit] = x % 10, x /= 10; while (x);
    while (__bit) putchar(__stk[__bit--] ^ 48);
    putchar(c);
}

} // namespace Elaina
using namespace Elaina;

const int Maxn = 5e5;
const ll Infty = 1ll << 60;

int a[Maxn + 5], n, q;
int tol[Maxn + 5], tor[Maxn + 5];

vector<pii> Q[Maxn + 5]; ///< composition: < query_r, id >

inline void input() {
    readin(n);
    rep (i, 1, n) readin(a[i]);
    readin(q); int ql, qr;
    rep (i, 1, q) {
        readin(ql, qr);
        Q[ql].push_back({qr, i});
    }
}

vector<pii> buc[Maxn + 5]; ///< composition: < r_pos, val >

int stk[Maxn + 5], tl;
inline void prelude() {
    tl = 0;
    rep (i, 1, n) {
        for (; tl && a[stk[tl]] < a[i]; --tl);
        if (tl) {
            tol[i] = stk[tl];
            buc[tol[i]].push_back({i, a[tol[i]] + a[i]});
        }
        stk[++tl] = i;
    }
    tl = 0;
    drep (i, n, 1) {
        for (; tl && a[stk[tl]] < a[i]; --tl);
        if (tl) {
            tor[i] = stk[tl];
            buc[i].push_back({tor[i], a[i] + a[tor[i]]});
        }
        stk[++tl] = i;
    }
}

namespace saya {

ll tag[Maxn << 2 | 2];
ll val[Maxn << 2 | 2];
ll ans[Maxn << 2 | 2];

#define ls (i << 1)
#define rs (i << 1 | 1)
#define mid ((l + r) >> 1)
#define _lhs ls, l, mid
#define _rhs rs, mid + 1, r
#define _root int i = 1, int l = 1, int r = n

inline void pushup(int i) { ans[i] = max(ans[ls], ans[rs]); }
inline void work(int i, ll x) {
    if (tag[i] < x) chkmax(ans[i], val[i] + (tag[i] = x));
}

inline void pushdown(int i) {
    if (tag[i] == -Infty) return ;
    work(ls, tag[i]), work(rs, tag[i]), tag[i] = -Infty;
}

void build(_root) {
    tag[i] = -Infty;
    if (l == r) return void(ans[i] = val[i] = a[l]);
    build(_lhs), build(_rhs), pushup(i);
    val[i] = max(val[ls], val[rs]);
}
void modify(int ql, int qr, ll x, _root) {
    if (ql > qr) return ;
    if (ql <= l && r <= qr) return work(i, x);
    pushdown(i);
    if (ql <= mid) modify(ql, qr, x, _lhs);
    if (mid < qr) modify(ql, qr, x, _rhs);
    pushup(i);
}
ll query(int ql, int qr, _root) {
    if (ql > qr) return -Infty;
    if (ql <= l && r <= qr) return ans[i];
    pushdown(i);
    ll ret = -Infty;
    if (ql <= mid) ret = query(ql, qr, _lhs);
    if (mid < qr) chkmax(ret, query(ql, qr, _rhs));
    return ret;
}

#undef ls
#undef rs
#undef mid
#undef _lhs
#undef _rhs
#undef _root

} // namespace saya

ll ans[Maxn + 5];
inline void solve() {
    saya::build();
    drep (l, n, 1) {
        for (const auto& pir: buc[l]) {
            int d = pir.fi - l;
            saya::modify(pir.fi + d, n, pir.se);
        }
        for (const auto& q: Q[l])
            ans[q.se] = saya::query(l, q.fi);
    }
    rep (i, 1, q) writln(ans[i]);
}

signed main() {
    // clock_t _st = clock();
    input();
    prelude();
    solve();
    // masdf("Code %s :> Time Elapsed %dms\n", __FILE__, clock() - _st);
    return 0;
}

旅行 / Travel

  对于 \(m=n-1\) 的部分,实际上和 <2022-02-10 传染> 有异曲同工之妙,都是用点分树优化建图,对于 \(m>n\) 的情况,先随便找le 棵生成树出来,将不在树上的 \(k\) 条边找出来,显然 \(k\le 51\). 如果最短路只经过树边,就直接点分做,否则,一定经过这 \(51\) 条边中的某个端点,每条边找出来一个端点,预处理这些端点到其他所有点之间的距离,这部分的最短路建图就是找 \(dis(i,x)+dis(x,y)\le d_i\)\(y\),可以用类似点分树建图的方法,对于每一个特殊点将所有点按照到它的距离从小往大排序,使用基排可以将这部分做到 \(\mathcal O(nk)\),最终的复杂度就是 \(\mathcal O(n\log n+nk)\). 不想打代码了。

posted @ 2022-03-14 22:17  Arextre  阅读(102)  评论(0编辑  收藏  举报