[题解]Viva la vida!
\(\mathcal{Back\;To\;The\;Menu}\).
2022-03-11 Viva la vida!
T1 拿来搞笑的吧?T2 知道 \(\mathcal O(n^2)\) 还转不到正解上面去......T3 就没怎么想了。
王 / King
I used to roll the dice
过去我常常孤注一掷
Feel the fear in my enemy's eyes
尽情品味惊恐在死敌瞳孔绽开
Listen as the crowd would sing:
欣然倾听百姓高歌喝彩
"Now the old king is dead! Long live the king!"
“先王亡矣!新王万代!”
从 \(0\) 开始倒着考虑,那么原题就相当于分别用长度为 \(1,2,3,\cdots\) 的全 \(1\) 串去异或当前的串,也可以不异或。
做一遍 dijkstra 就行了,同时不难发现一定有解且小于等于 \(16\),因为当长度为 \(16\) 时线性基满秩。
/** @author __Elaina__ */
#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 curse(...) 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 = 16;
const int n = 16;
const int Maxs = 1 << Maxn;
int dis[Maxs + 5];
priority_queue<pii, vector<pii>, greater<pii>> Q;
inline void dijkstra() {
memset(dis, 0x3f, sizeof dis);
Q.push({dis[0] = 0, 0});
while (!Q.empty()) {
int s = Q.top().se, d = Q.top().fi; Q.pop();
if (d > dis[s]) continue;
repf (i, 0, n) {
rep (len, 0, i) {
if (len < i && dis[s] > len) continue;
int to = ((2 << len) - 1) << (i - len), w = max(dis[s], len) + 1;
to ^= s;
if (dis[to] > w) Q.push({dis[to] = w, to});
}
}
}
}
signed main() {
freopen("roll.in", "r", stdin);
freopen("roll.out", "w", stdout);
cin.tie(NULL)->sync_with_stdio(false);
dijkstra();
int s, t; char reci[Maxn + 5];
cin >> t; while (t--) {
cin >> reci;
repf (i, s = 0, strlen(reci)) s = s << 1 | (reci[i] ^ 48);
writln(dis[s]);
}
return 0;
}
落幕 / Fading
One minute I held the key
曾经我手握权位经脉
Next the walls were closed on me
如今才知宫墙深似海
And I discovered that my castles stand
恍然发现我的城池
Upon pillars of salt, pillars of sand
基底散如盐沙乱似尘埃
可以发现一些性质,比如说有解的充要条件是 \(k\mid \sum a_i\),比如说如果 \([a,b]\) 有解,\([b+1,c]\) 有解,那么 \([l_1,r_2]\) 一定有解,并且若设 \(g(l,r)\) 表示区间 \([l,r]\) 的答案,那么 \(g(a,c)=g(a,b)+g(b+1,c)\),这告诉我们,对于一个右端点,我们实际上只需要计算与它最近的那个 \(l\) 且保证 \([l,r]\) 有解的 \(l\),而这样的区间是线性的,如果每一对我们可以快速地计算,那么最后的处理就非常容易了。
分析到这里就有一个 \(\mathcal O(n^2)\) 的算法,每次枚举一个左端点,然后计算所有右端点的花费。
不难发现上面的暴力的瓶颈在于,我们知道 \([l,r]\) 有解,但是计算 \(g(l,r)\) 花费了我们大量的时间,考虑快速地计算它:显然我们应当给每个需要的数定向,是向上还是向下凑成 \(k\) 的倍数,然后每个数就有 冗余 和 售罄 两种情况,花费就是将 冗余 部分转移到 售罄 地方的总花费。
十分容易可以证明,用 冗余 去补充 售罄,最小花费一定是先贪心地将最近的两个进行补充,然后再次近的......但是这样做显然不是特别好,问题有两个:一,如何将每个点定成 冗余 还是 售罄,二,即使可以确定,计算花费也不是特别方便。
考虑从另外的方面进行计算,如果我们将前缀和看成条状,那么每一次修改就是将某个条拔高或者降低,并且其他的条不受到影响,而目标状态是让每个条的高度变成 \(k\) 的倍数,那么这个花费就很好计算了,记这个数列的前缀和对 \(k\) 取模之后为 \(pre_i\),那么总花费就是
记原序列的前缀和对 \(k\) 取模为 \(s_i\),那么,对于每一个右端点,找到最近的 \(l\) 使得 \(s_{l-1}=s_r\),计算 \([l,r]\) 的贡献,和上面相似,就是
注意 \(\min\) 里面的运算都要对 \(k\) 取模。这个式子的计算,实际上可以分成两个部分,记 \(x_i=s_i-s_{l-1}\),那么我们找到 \([l,r]\) 中有多少 \(x_i\in[0,\frac{k}{2}]\),这些直接取值,另外的就用 \(k\) 乘以个数减去和即可。实现可以使用主席树。
代码细节有亿点多,但是应该是我写丑了。
/** @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 = 1e6;
const int Maxk = 1e8;
const int logk = 30;
const int mod = 998244353;
inline void chkadd(int& x, int y) { if ((x += y) >= mod) x -= mod; }
int n, k;
int a[Maxn + 5];
inline void input() {
readin(n, k);
rep (i, 1, n) readin(a[i]);
}
namespace saya {
const int Maxn = ::Maxn * logk;
int ncnt;
int cnt[Maxn + 5], sum[Maxn + 5];
int ls[Maxn + 5], rs[Maxn + 5];
#define mid ((l + r) >> 1)
#define _lhs ls[i], l, mid
#define _rhs rs[i], mid + 1, r
inline int copy(int x) {
int i = ++ncnt;
tie(cnt[i], sum[i], ls[i], rs[i]) = tie(cnt[x], sum[x], ls[x], rs[x]);
return i;
}
void modify(int p, int& i, int l = 0, int r = Maxk - 1) {
i = copy(i), ++cnt[i], chkadd(sum[i], p);
if (l == r) return ;
if (p <= mid) modify(p, _lhs);
else modify(p, _rhs);
}
inline void add(pii& x, pii y) { x.fi += y.fi, chkadd(x.se, y.se); }
pii query(int ql, int qr, int x, int y, int l = 0, int r = Maxk - 1) {
if (ql > qr) return {0, 0};
if (ql <= l && r <= qr) return {cnt[y] - cnt[x], (sum[y] + mod - sum[x]) % mod};
pii ret = {0, 0};
if (ql <= mid) ret = query(ql, qr, ls[x], ls[y], l, mid);
if (mid < qr) add(ret, query(ql, qr, rs[x], rs[y], mid + 1, r));
return ret;
}
#undef mid
#undef _lhs
#undef _rhs
};
int rt[Maxn + 5], s[Maxn + 5];
int reals[Maxn + 5];
inline void buildTree() {
rep (i, 1, n) {
s[i] = (s[i - 1] + a[i]) % k;
reals[i] = (reals[i - 1] + s[i]) % mod;
saya::modify(s[i], rt[i] = rt[i - 1]);
}
}
int val[Maxn + 5];
inline int solve(int l, int r) {
int mov = s[l - 1], les_cnt, gre_cnt, les_sum, gre_sum;
if (mov + (k >> 1) < k) {
int ql = mov, qr = mov + (k >> 1);
auto ret = saya::query(ql, qr, rt[l - 1], rt[r]);
tie(les_cnt, les_sum) = ret;
gre_cnt = (r - l + 1) - les_cnt;
gre_sum = ((0ll + reals[r] - reals[l - 1] - les_sum) % mod + mod) % mod;
int number = saya::query(0, ql - 1, rt[l - 1], rt[r]).fi;
les_sum = ((0ll + les_sum - 1ll * les_cnt * mov) % mod + mod) % mod;
gre_sum = ((0ll + gre_sum + 1ll * k * number - 1ll * gre_cnt * mov) % mod + mod) % mod;
}
else {
int ql = ((k >> 1) + 1 + mov) % k, qr = (k - 1 + mov) % k;
auto ret = saya::query(ql, qr, rt[l - 1], rt[r]);
tie(gre_cnt, gre_sum) = ret;
les_cnt = (r - l + 1) - gre_cnt;
les_sum = ((0ll + reals[r] - reals[l - 1] - gre_sum) % mod + mod) % mod;
int number = saya::query(0, ql - 1, rt[l - 1], rt[r]).fi;
les_sum = ((0ll + les_sum + 1ll * k * number - 1ll * les_cnt * mov) % mod + mod) % mod;
gre_sum = ((0ll + gre_sum + 1ll * k * gre_cnt - 1ll * gre_cnt * mov) % mod + mod) % mod;
}
chkadd(les_sum, ((1ll * k * gre_cnt - gre_sum) % mod + mod) % mod);
return les_sum;
}
map<int, int> pre;
int tol[Maxn + 5];
inline void prelude() {
pre[0] = 0;
rep (i, 1, n) {
if (pre.count(s[i])) val[i] = solve(pre[s[i]] + 1, i), tol[i] = pre[s[i]];
else val[i] = tol[i] = -1;
pre[s[i]] = i;
}
}
int ans = 0;
int f[Maxn + 5];
inline void getAns() {
pre.clear(), pre[0] = 1;
rep (i, 1, n) {
chkadd(ans, 1ll * (mod - 1) * (i - pre[s[i]]) % mod);
if (~tol[i]) {
f[i] = 1ll * val[i] * pre[s[i]] % mod;
chkadd(f[i], f[tol[i]]);
}
++pre[s[i]];
chkadd(ans, f[i]);
}
writln(ans);
}
signed main() {
freopen("win.in", "r", stdin);
freopen("win.out", "w", stdout);
input();
buildTree();
prelude();
getAns();
return 0;
}
终焉 / Ending
Shattered windows and the sound of drums
断壁残垣礼崩乐坏
People couldn't believe what I'd become
世人不敢相信我已当年不再
Revolutionaries Wait
起义大军翘首期待
For my head on a silver plate
有朝一日我站上断头台
转化之后将会变成一个很简单的问题,可是我并没有想到去转化......概率期望题还是多想想能不能转化再说。
判断最优策略:在当前的手牌可以弑君再出牌。
然后将赢的局数的期望转变一下:\(1+\mathsf{第一回合无法获胜的概率}+\mathsf{第二回合无法获胜的概率}+\mathsf{第三回合无法获胜的概率}\cdots\)
注意到
于是我们可以只关注抽排序列有哪些牌。
- 只有毒药:在 \(n\) 张牌以内都无法获胜,\(\displaystyle P_1=\sum_{i=1}^n\frac{1}{3^i}=\frac{1-\frac{1}{3^n}}{2}\);
- 只有火球,在 \(\displaystyle m=\ddiv{n-1}{2}\) 张牌以内无法获胜,\(\displaystyle P_2=\sum_{i=1}^m \frac{1}{3^i}=\frac{1-\frac{1}{3^m}}{2}\);
- 只有复读,无法获胜,\(\displaystyle P_3=\sum_{i=1}^{+\infty}\frac{1}{3^i}=\frac{1}{2}\);
- 毒药加火球,比较复杂,定义 \(f(0/1,0/1,s)\) 表示当前是否使用毒药,是否使用火球,造成 \(s\) 伤害的概率,最后的概率就是 \(\displaystyle P_4=\sum_{i=1}^{n-1}f(1,1,i)\);
- 毒药加复读,相当于毒药伤害为 \(1\),复读伤害为 \(2\),与上面的 DP 类似;
- 复读加上火球,其实和全是火球一样,但是要注意排除全是火球或者复读的情况,因此这一部分的概率为 \(\displaystyle P_6=\sum_{i=1}^m \frac{2^i-2}{3^i}=2\brak{1-\brak{\frac{2}{3}}^m}-\brak{1-\frac{1}{3^m}}\);
- 毒药加火球加复读,类似地,毒药为 \(1\),火球为 \(3\),复读为 \(4\) 就行了;
注意到 \(4,5,7\) 的前缀和都是线性递推的形式,因此可以使用矩阵加速进行优化,可以把他们仨放到一个大矩阵中进行递推,这样比较方便,矩阵大小不超过 \(20\) 吧,因此复杂度大概就是 \(\mathcal O(T\times 20^3\log n)\).
但是无码。