比赛链接
我:100+100+8+29=237 / rk22
记 s n = ∑ i = 1 n a i s_n=\sum\limits_{i=1}^n a_i sn=i=1∑nai。
若小 Z 选到 i i i 停止,我们考虑什么情况下这是必胜策略。必然是这样的:存在一个 j ( j ≥ i ) j(j\ge i) j(j≥i),小 Y 选到 i + 1 ∼ j i+1\sim j i+1∼j 都打不过小 Z,而选到 j + 1 ∼ n j+1\sim n j+1∼n 又超过了 X X X。因此,只要知道一个 j j j 满足 s j − s i < s i s_j-s_i
因此,我们找到最大的 k k k 满足 s k − s i < s i s_k-s_i
我在赛时采用 O ( n log n ) O(n\log n) O(nlogn) 的写法,使用线段树求最大值,注意 ST 表会被卡空间。
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int MAXN = 2000005;
const ll INF = LLONG_MAX >> 1;
int N, K, d[MAXN]; ll s[MAXN];
ll tree[MAXN << 2];
#define mid ((l + r) >> 1)
void build(int o, int l, int r) {
if (l == r) tree[o] = s[l];
else build(o << 1, l, mid), build(o << 1 | 1, mid + 1, r), tree[o] = max(tree[o << 1], tree[o << 1 | 1]);
}
ll query(int o, int l, int r, int L, int R) {
if (R > N) return INF;
if (l == L && r == R) return tree[o];
else {
if (R <= mid) return query(o << 1, l, mid, L, R);
else if (L > mid) return query(o << 1 | 1, mid + 1, r, L, R);
else return max(query(o << 1, l, mid, L, mid), query(o << 1 | 1, mid + 1, r, mid + 1, R));
}
}
#undef mid
int main() {
scanf("%d", &N); ll i, j, x;
for (i = 1; i <= N; ++i) scanf("%lld", &x), s[i] = s[i - 1] + x;
scanf("%d", &K);
build(1, 1, N);
for (i = 1; i < N; ++i) {
int ret = i, l = i + 1, r = N;
while (l <= r) {
int mid = (l + r) >> 1;
if (s[mid] - s[i] < s[i]) l = mid + 1, ret = mid;
else r = mid - 1;
}
ll qwq = min(query(1, 1, N, i + 1, ret + 1) - s[i] - 1, (ll)K);
if (qwq < s[i]) continue;
++d[s[i]], --d[qwq + 1];
}
for (i = s[N]; i <= K; ++i) ++d[i], --d[i + 1];
for (i = 1; i <= K; ++i) d[i] += d[i - 1];
int cnt = 0;
for (i = 1; i <= K; ++i) if (d[i]) ++cnt;
printf("%d\n", cnt);
for (i = 1; i <= K; ++i) if (d[i]) printf("%d ", i);
return 0;
}
对于第一个样例,考虑状态 [ 1 , 0 , 1 ] [1,0,1] [1,0,1] 会变成 [ 1 , 1 , 0 ] [1,1,0] [1,1,0],这是因为它进行了如下操作:(3*3 转移矩阵是通过样例输入推出来的)
定义新矩阵乘法的运算是 c[i][j] |= a[i][k] & b[k][j]
,易证满足结合律,然后刻画一个状态的变换:
[ 1 0 1 ] × [ 0 0 0 1 0 1 1 1 0 ] T = [ 1 1 0 ] \begin{bmatrix} 1 & 0 & 1 \end{bmatrix} \times \begin{bmatrix} 0& 0 & 0 \\ 1 & 0 & 1 \\ 1 & 1 & 0 \end{bmatrix}^T=\begin{bmatrix} 1 & 1 & 0 \end{bmatrix} [101]×⎣⎡011001010⎦⎤T=[110]
考虑这个 01 矩阵的特性,显然可以 bitset 优化乘法。
然后考虑第一个人对答案的贡献,它有贡献当且仅当乘法完之后第一位还是 1,也就是转移矩阵的第一列的所有 1 所对应的人 a 1 , a 2 , ⋯ , a k a_1, a_2, \cdots, a_k a1,a2,⋯,ak 中至少有一个是 1,那么我们就能知道贡献是 1 − ∏ ( 1 − p a i ) 1-\prod (1-p_{a_i}) 1−∏(1−pai)。
所以我们就做完了,复杂度 O ( n 3 log T ω ) O(\frac{n^3\log T}{\omega}) O(ωn3logT)。
#include
#include
using namespace std;
typedef long long ll;
const int MAXN = 505, MOD = 998244353;
int N, T; ll p[MAXN], ans, P[MAXN];
struct matrix {
bitset<MAXN> a[MAXN], b[MAXN];
friend matrix operator* (const matrix& A, const matrix& B) {
matrix C; int i, j, k;
for (i = 1; i <= N; ++i) for (j = 1; j <= N; ++j) C.a[i].reset(), C.b[i].reset();
for (i = 1; i <= N; ++i) for (j = 1; j <= N; ++j) C.a[i][j] = (A.a[i] & B.b[j]).count();
for (i = 1; i <= N; ++i) for (j = 1; j <= N; ++j) C.b[i][j] = C.a[j][i];
return C;
}
} RET, A;
int main() {
scanf("%d%d", &N, &T); int i, j, x, y;
for (i = 1; i <= N; ++i) {
scanf("%lld%d", &p[i], &x);
for (j = 1; j <= x; ++j) scanf("%d", &y), A.a[y][i] = 1, A.b[i][y] = 1;
}
for (i = 1; i <= N; ++i) RET.a[i][i] = 1, RET.b[i][i] = 1;
while (T) {
if (T & 1) RET = RET * A;
A = A * A, T >>= 1;
}
for (i = 1; i <= N; ++i) P[i] = 1;
for (i = 1; i <= N; ++i) for (j = 1; j <= N; ++j) if (RET.a[j][i]) P[i] = P[i] * (1 - p[j]) % MOD;
for (i = 1; i <= N; ++i) ans = (ans + (1 - P[i])) % MOD;
printf("%lld\n", (ans + MOD) % MOD);
return 0;
}
咕咕咕
觉得比较可做,过几天来补(