[题解]血战狗熊岭
\(\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)\). 不想打代码了。