「LGR-067」洛谷 1 月月赛 Div.1 解题报告

比赛链接

我:100+100+8+29=237 / rk22

游戏

题解

s n = ∑ i = 1 n a i s_n=\sum\limits_{i=1}^n a_i sn=i=1nai

若小 Z 选到 i i i 停止,我们考虑什么情况下这是必胜策略。必然是这样的:存在一个 j ( j ≥ i ) j(j\ge i) j(ji),小 Y 选到 i + 1 ∼ j i+1\sim j i+1j 都打不过小 Z,而选到 j + 1 ∼ n j+1\sim n j+1n 又超过了 X X X。因此,只要知道一个 j j j 满足 s j − s i < s i s_j-s_i<s_i sjsi<si,我们就能断定 [ s i , s j + 1 − s i ) [s_i,s_{j+1}-s_i) [si,sj+1si) 内的 X X X 都是答案。

因此,我们找到最大的 k k k 满足 s k − s i < s i s_k-s_i<s_i sksi<si,然后断定 [ s i , max ⁡ j + 1 ≤ t ≤ k + 1 s t − s i ) [s_i, \max_{j+1\le t\le k+1} s_t - s_i) [si,maxj+1tk+1stsi) 内的 X X X 都是答案。二分、线段树、差分可以在 O ( n log ⁡ n ) O(n\log n) O(nlogn) 的复杂度下通过此题,而关注单调性,递推、单调栈、差分可以在 O ( n ) O(n) O(n) 的复杂度下通过此题。

代码

我在赛时采用 O ( n log ⁡ n ) O(n\log n) O(nlogn) 的写法,使用线段树求最大值,注意 ST 表会被卡空间。

#include <cstdio>
#include <cmath>
#include <climits>
#include <algorithm>
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]×011001010T=[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(1pai)

所以我们就做完了,复杂度 O ( n 3 log ⁡ T ω ) O(\frac{n^3\log T}{\omega}) O(ωn3logT)

代码
#include <cstdio>
#include <bitset>
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;
}

仙人掌

咕咕咕

觉得比较可做,过几天来补(

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值