2019牛客多校训练第二场题解

2019牛客多校训练第二场题解

题目链接

A.Eddy Walker

打表即可发现答案为\(\frac{1}{n-1}\),注意特判。
证明的话\(1\)~\(n-1\)会均分概率,因为对于这些数来说走法都是一样的:假设当前为\(i\),而目前走到了\(i-1\)或者\(i+1\),之后就相当于一条链上的随机游走了。

Code

#include 
using namespace std;
typedef long long ll;
int n, m;
const int MOD = 1e9 + 7;
ll qp(ll a, ll b) {
    ll ans = 1;
    while(b) {
        if(b & 1) ans = ans * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return ans ;
}
int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    int T;
    cin >> T;
    ll ans = 1;
    while(T--) {
        cin >> n >> m;
        if(n == 1) cout << ans << '\n';
        else {
            if(m == 0) ans = 0;
            else ans = ans * qp(n - 1, MOD - 2) % MOD;
            cout << ans << '\n';
        }
    }
    return 0;
}

B.Eddy Walker2

对于\(n>=0\)的时候直接上BM就行了。
对于\(n=-1\)的情况,走一步的期望长度为\(\frac{k+1}{2}\),也就是说.....我也不知道了,打表吧。。不过听有大佬分析说对于无穷远的点所走的步数可以看作相同的,概率均等,所以就是\(\frac{k+1}{2}\)(雾)。

Code

#include
#define rep(i,a,n) for (int i=a;i>1)
#define pb push_back
 
typedef vector VI;
typedef long long ll;
 
ll powMOD(ll a, ll b) {
    ll ans = 1;
    for (; b; b >>= 1, a = a * a%MOD)if (b & 1)ans = ans * a%MOD;
    return ans;
}
ll n;
namespace linear_seq {
    const int N = 10010;
    ll res[N], base[N], _c[N], _md[N];
 
    vector Md;
    void mul(ll *a, ll *b, int k) {
        rep(i, 0, k + k) _c[i] = 0;
        rep(i, 0, k) if (a[i]) rep(j, 0, k) _c[i + j] = (_c[i + j] + a[i] * b[j]) % MOD;
        for (int i = k + k - 1; i >= k; i--) if (_c[i])
            rep(j, 0, SZ(Md)) _c[i - k + Md[j]] = (_c[i - k + Md[j]] - _c[i] * _md[Md[j]]) % MOD;
        rep(i, 0, k) a[i] = _c[i];
    }
    int solve(ll n, VI a, VI b) { // a 系数 b 初值 b[n+1]=a[0]*b[n]+...
        ll ans = 0, pnt = 0;
        int k = SZ(a);
        assert(SZ(a) == SZ(b));
        rep(i, 0, k) _md[k - 1 - i] = -a[i]; _md[k] = 1;
        Md.clear();
        rep(i, 0, k) if (_md[i] != 0) Md.push_back(i);
        rep(i, 0, k) res[i] = base[i] = 0;
        res[0] = 1;
        while ((1ll << pnt) <= n) pnt++;
        for (int p = pnt; p >= 0; p--) {
            mul(res, res, k);
            if ((n >> p) & 1) {
                for (int i = k - 1; i >= 0; i--) res[i + 1] = res[i]; res[0] = 0;
                rep(j, 0, SZ(Md)) res[Md[j]] = (res[Md[j]] - res[k] * _md[Md[j]]) % MOD;
            }
        }
        rep(i, 0, k) ans = (ans + res[i] * b[i]) % MOD;
        if (ans < 0) ans += MOD;
        return ans;
    }
    VI BM(VI s) {
        VI C(1, 1), B(1, 1);
        int L = 0, m = 1, b = 1;
        rep(n, 0, SZ(s)) {
            ll d = 0;
            rep(i, 0, L + 1) d = (d + (ll)C[i] * s[n - i]) % MOD;
            if (d == 0) ++m;
            else if (2 * L <= n) {
                VI T = C;
                ll c = MOD - d * powMOD(b, MOD - 2) % MOD;
                while (SZ(C) < SZ(B) + m) C.pb(0);
                rep(i, 0, SZ(B)) C[i + m] = (C[i + m] + c * B[i]) % MOD;
                L = n + 1 - L; B = T; b = d; m = 1;
            }
            else {
                ll c = MOD - d * powMOD(b, MOD - 2) % MOD;
                while (SZ(C) < SZ(B) + m) C.pb(0);
                rep(i, 0, SZ(B)) C[i + m] = (C[i + m] + c * B[i]) % MOD;
                ++m;
            }
        }
        return C;
    }
    int gao(VI a, ll n) {
        VI c = BM(a);
        c.erase(c.begin());
        rep(i, 0, SZ(c)) c[i] = (MOD - c[i]) % MOD;
        return solve(n, c, VI(a.begin(), a.begin() + SZ(c)));
    }
};
inline void add(int &x, int y) {
    x += y;
    if (x >= MOD)x -= MOD;
}
int t, k, dp[MAXN];
vector v;
int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> t;
    while (t--) {
        v.clear();
        cin >> k >> n;
        if (n == -1) {
            cout << 2 * powMOD(k + 1, MOD - 2) % MOD << '\n';
            continue;
        }
        ll inv = powMOD(k, MOD - 2);
        dp[1] = 1; v.push_back(1);
        for (int i = 2; i <= 2 * k; i++) {
            dp[i] = 0;
            for (int j = max(1, i - k); j < i; j++) {
                add(dp[i], dp[j]);
            }
            dp[i] = dp[i] * inv%MOD;
            v.push_back(dp[i]);
        }
        cout << linear_seq::gao(v, n) << '\n';
    }
    return 0;
}

D.Kth Minimum Clique

看了题解之后感觉做法挺自然的,但考场上还是没做出来= =
做法就是直接贪心去找团就行了,类似于dijkstra,队列附加一个bitset用来维护有哪些团就ok了。

Code

#include 
#define rep(i, a, b) for(int i = a; i < b; i++)
using namespace std;
typedef long long ll;
const int N = 105;
int n, k;
int w[N];
char s[N];
bitset  f[N];
struct node{
    ll ans;
    bitset  S;
    bool operator < (const node &A)const {
        return ans > A.ans;
    }
};
priority_queue  q;
int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n >> k;
    rep(i, 0, n) cin >> w[i];
    rep(i, 0, n) { cin >> s;
        rep(j, 0, n) if(s[j] == '1') f[i][j] = 1;
    }
    bitset  t; t.reset();
    q.push(node{0, t}) ;
    while(!q.empty()) {
        node now = q.top(); q.pop();
        if(--k == 0) {
            cout << now.ans;
            return 0;
        }
        bitset  tmp = now.S;
        int p = 0;
        rep(i, 0, n) if(tmp[i] == 1) p = i + 1;
        rep(i, p, n) {
            if((tmp & f[i]) == tmp) {
                tmp[i] = 1;
                q.push(node{now.ans + w[i], tmp});
                tmp[i] = 0;
            }
        }
    }
    cout << -1;
    return 0;
}

E.MAZE

第一次遇见这种题,原来线段树还可以这样用= =
首先考虑dp,有式子:\(dp[i][j]=\sum dp[i-1][k1]+\sum dp[i-1][k2]\)对于\(1\leq k1\leq j\leq k2\)\(maze[i][k1]=maze[i][k1+1]=\cdots=maze[i][j]=0,maze[i][k2]=\cdots=maze[i][j]=0\),分别对应两个求和式子。含义可以自己脑补。
现在就相当于一层一层进行转移,并且转移时与当前行有关。
那么我们对每一行构造一个表示“列可达”的矩阵,之后所有的乘起来就是方案数了(矩阵乘法常用在计数方面)
因为题目涉及到修改,但每次也只针对一行,然后我们每次询问都是将所有的乘起来,所以直接把矩阵挂树上就行了。
修改时间复杂度\(O(logn*m^3)\),询问可做到\(O(1)\),建树时复杂度为\(O(n*m^3)\)
代码如下:

Code

#include 
using namespace std;
typedef long long ll;
const int N = 50005, M = 12, MOD = 1e9 + 7;
int n, m, q;
char mp[N][M];
int maz[N][M];
struct Matrix{
    int n;
    ll a[M][M];
    Matrix() {
        n = m; memset(a, 0, sizeof(a));
    }
    Matrix operator * (const Matrix &A) {
        Matrix ans;
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= n; j++)
                for(int k = 1; k <= n; k++)
                    ans.a[i][j] = (ans.a[i][j] + 1ll * a[i][k] * A.a[k][j] % MOD) % MOD;
        return ans;
    }
}sum[N << 2];
Matrix Get(int k) {
    Matrix ans;
    for(int i = 1; i <= m; i++) {
        for(int j = i; j <= m; j++) {
            if(maz[k][j]) break ;
            ans.a[i][j] = ans.a[j][i] = 1;
        }
    }
    return ans;
}
void push_up(int o) {
    sum[o] = sum[o << 1] * sum[o << 1|1];
}
void build(int o, int l, int r) {
    if(l == r) {
        sum[o] = Get(l);
        return ;
    }
    int mid = (l + r) >> 1;
    build(o << 1, l, mid); build(o << 1|1, mid + 1, r);
    push_up(o);
}
void update(int o, int l, int r, int v) {
    if(l == r) {
        sum[o] = Get(l);
        return;
    }
    int mid = (l + r) >> 1;
    if(v <= mid) update(o << 1, l, mid, v);
    else update(o << 1|1, mid + 1, r, v);
    push_up(o);
}
int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n >> m >> q;
    for(int i = 1; i <= n; i++) {
        cin >> mp[i] + 1;
        for(int j = 1; j <= m; j++)
            maz[i][j] = mp[i][j] - '0';
    }
    build(1, 1, n);
    while(q--) {
        int op, x, y;
        cin >> op >> x >> y;
        if(op == 1) {
            maz[x][y] ^= 1;
            update(1, 1, n, x);
        } else {
            cout << sum[1].a[x][y] << '\n';
        }
    }
    return 0;
}

F.Partition problem

直接爆搜就行了,然后在中间统计答案可以优化一层\(n\),做到\(C(2*N,N)*N\)的复杂度。
可以这样想,搜索树是一颗满二叉树,最后一层为叶子,我们将所有的\(n\)分散到前面的层数上去,因为前面的结点个数加起来还比最后一层结点个数少一个,所以复杂度就降下来了比起直接在最后进行统计。

Code

#include 
using namespace std;
typedef long long ll;
const int N = 30;
ll v[N][N];
int a[N], b[N], c[N];
int n;
ll Ans;
void dfs(int k, int tot, int cnt, ll ans) {
    if(tot > n || cnt > n) return ;
    if(tot == n && cnt == n) {
        Ans = max(ans, Ans);
        return ;
    }
    ll now = 0;
    a[tot + 1] = k + 1;
    for(int i = 1; i <= cnt; i++) now += v[k + 1][b[i]];
    dfs(k + 1, tot + 1, cnt, ans + now);
    now = 0;
    b[cnt + 1] = k + 1;
    for(int i = 1; i <= tot; i++) now += v[k + 1][a[i]];
    dfs(k + 1, tot, cnt + 1, now + ans);
}
int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n;
    for(int i = 1; i <= 2 * n; i++)
        for(int j = 1; j <= 2 * n; j++)
            cin >> v[i][j];
    dfs(0, 0, 0, 0);
    cout << Ans;
    return 0;
}

H.Second Large Rectangle

做法比较多,悬线法、并查集、单调栈这些都可以搞,注意一点就是题目要求次大,所以我们统计答案的时候直接用set来存要被卡,只需要保留最大的两个就行了。
见代码吧:

Code

#include 
using namespace std;
typedef long long ll;
const int N = 1005;
char s[N];
int a[N][N];
int n, m;
#define mp make_pair
#define se second
#define fi first
namespace max_matrix{
    int vis[N], f[N], sz[N];
    int t[N][N];
    int mx = 0, mx2 = 0;
    pair h[N];
    int find(int x) {
        return f[x] == x ? x : f[x] = find(f[x]) ;
    }
    void Union(int x, int y) {
        int fx = find(x), fy = find(y);
        f[fx] = fy;
        sz[fy] += sz[fx];
    }
    void solve(int a, int b) {
        int area = a * b;
        if(area > mx) mx2 = mx, mx = area;
        else if(area > mx2) mx2 = area;
    }
    int work(int n, int m, int a[][N]) {
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= m; j++) {
                t[i][j] = (a[i][j] == 1) ? t[i - 1][j] + 1 : 0;
            }
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= m; j++) {
                f[j] = j, vis[j] = 0, h[j] = mp(t[i][j], j), sz[j] = 1;
            }
            sort(h + 1, h + m + 1);
            for(int j = m; j >= 1; j--) {
                int k = h[j].se; vis[k] = 1;
                if(vis[k - 1]) Union(k - 1, k);
                if(vis[k + 1]) Union(k + 1, k);
                int len = sz[find(k)];
                solve(len, h[j].fi);
                solve(len - 1, h[j].fi);
                solve(len, h[j].fi - 1);
            }
        }
        return mx2;
    }
};
int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n >> m;
    for(int i = 1; i <= n; i++) {
        cin >> s + 1;
        for(int j = 1; j <= m; j++) a[i][j] = s[j] - '0';
    }
    cout << max_matrix::work(n, m, a);
    return 0;
}

J.Subarray

因为给出的区间长度最多为\(1e7\),所以我们可以把有用的位置给拿出来,很容易知道最多\(3e7\)个位置有用。
之后将多个可以连在一起的段合并,然后对于每一段,求出前缀和,对于每一个位置,之前比它小的数都会产生贡献。因为前缀和中任意相邻两项相差为1,所以可以开个桶来搞,边维护边统计答案。
思路差不多就是这样,代码还有一些细节。求连续段的话可以类似于dp的方式来搞,通过记录向右、向左最大延伸。
细节见代码:

Code

#include 
using namespace std;
typedef long long ll;
typedef pair pii;
const int N = 1e6 + 5, MAX = 3e7 + 5, base = 1e7 + 10, MAXX = 1e9;
struct seg{
    ll l, r;
}a[N], b[N];
int n;
int v[base << 1];
ll work(int l, int r, int L, int R) {
    ll ans = 0, tmp = 0;
    v[base]++;
    for(int i = L, id = l, now = base; i <= R; i++) {
        while(id <= r && i > a[id].r) id++;
        if(i >= a[id].l && i <= a[id].r) {
            tmp += v[now];
            v[++now]++;
        } else {
            now--;
            tmp -= v[now];
            v[now]++;
        }
        ans += tmp;
    }
    v[base]--;
    for(int i = L, id = l, now = base; i <= R; i++) {
        while(id <= r && i > a[id].r) id++;
        if(i >= a[id].l && i <= a[id].r) {
            v[++now]--;
        } else {
            v[--now]--;
        }
    }
    return ans;
}
int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i].l >> a[i].r;
    b[0].r = -1;
    for(int i = 1; i <= n; i++) {
        b[i].r = max(b[i - 1].r - a[i].l + 1, 0ll) + 2 * a[i].r - a[i].l + 1;
        if(b[i].r >= MAXX) b[i].r = MAXX - 1;
    }
    b[n + 1].l = MAXX;
    for(int i = n; i >= 1; i--) {
        b[i].l = a[i].l - max(a[i].r - b[i + 1].l + 1, 0ll) - (a[i].r - a[i].l + 1);
        if(b[i].l < 0) b[i].l = 0;
    }
    ll ans = 0;
    for(int i = 1, j; i <= n; i = j + 1) {
        j = i;
        int L = b[i].l, R = b[i].r;
        while(j <= n && R >= b[j + 1].l) R = b[++j].r;
        ans += work(i, j, L, R);
    }
    cout << ans;
    return 0;
}

转载于:https://www.cnblogs.com/heyuhhh/p/11228785.html

你可能感兴趣的:(2019牛客多校训练第二场题解)