2019 Multi-University Training Contest 2 题解&总结

B

只讨论最小字典序的情况,最大字典序同理。设 f i f_i fi表示从前往后到第 i i i位的最长上升子序列。 g i g_i gi表示从后往前到第 i i i位的最长上升子序列。然后两个拼接一下即可,这里也只讨论 f i f_i fi g i g_i gi同理。我们需要的是更新这两个的最优字典序。可以发现如果不要求字典序,如果我们当前更新第 i i i位,必然是找 { j ∣ a j &lt; a i } \{j|a_j&lt;a_i\} {jaj<ai}中的 f j f_j fj最大值去更新。如果要求字典序最小,会发现,对应的 j j j应该是 a j a_j aj最大,且最靠前的,这个 j j j对应的位置一定是最小字典序。

#include <bits/stdc++.h>

#define F(i, a, b) for (int i = a; i <= b; i ++)
#define G(i, a, b) for (int i = a; i >= b; i --)
#define max(a, b) ((a) > (b) ? (a) : (b))

using namespace std;

const int N = 3 * 1e5 + 10;
const int T = 8 * N;

int n, pos, MX, V, Rt, cnt, x;
int a[N], f[2][N], g[2][N], d[N];
struct Segment_Tree {
    int l, r, v, pos;
} tr[T];

#define get getchar()
void Re(int &x) {
    char c = get; x = 0;
    for (; !isdigit(c); c = get);
    for (; isdigit(c); x = (x << 3) + (x << 1) + c - '0', c = get);
}

#define ls tr[x].l
#define rs tr[x].r
#define M (st + en >> 1)
void Modify(int &x, int st, int en, int p, int t) {
    if (!x) tr[x = ++ cnt] = {0, 0, 0, 0};
    if (st == en) {
        if (!t && f[t][p] <= tr[x].v) return;
        tr[x].v = f[t][p];
        tr[x].pos = p;
        return;
    }
    M >= a[p] ? Modify(ls, st, M, p, t) : Modify(rs, M + 1, en, p, t);
    tr[x].v = max(tr[ls].v, tr[rs].v);
    if (!t)
        tr[x].pos = tr[x].v == tr[rs].v ? tr[rs].pos : tr[ls].pos;
    else
        tr[x].pos = tr[x].v == tr[ls].v ? tr[ls].pos : tr[rs].pos;
}
void Find(int x, int st, int en, int l, int r, int t) {
    if (tr[x].v <= V) return;
    if (l <= st && en <= r) {
        if (tr[x].v > V) {
            V = tr[x].v;
            pos = tr[x].pos;
        }
        return;
    }
    if (!t) {
        if (M < r) Find(rs, M + 1, en, l, r, t);
        if (M >= l) Find(ls, st, M, l, r, t);
    }
    else
    {
        if (M >= l) Find(ls, st, M, l, r, t);
        if (M < r) Find(rs, M + 1, en, l, r, t);    
    }
}

struct node {
    int v, num;
    friend bool operator < (node a, node b) { return a.v < b.v; }
} D[N];

int main() {
    while (~scanf("%d", &n)) {
        MX = 0;
        F(i, 1, n)
            Re(a[i]), D[i] = {a[i], i};
        sort(D + 1, D + n + 1), D[0].v = D[1].v - 1;
        F(i, 1, n) MX += D[i].v != D[i - 1].v, a[D[i].num] = MX;
        
        Rt = 0;
        F(i, 1, n) {
            pos = V = 0;
            Find(1, 1, MX, 1, a[i] - 1, 0);
            f[0][i] = V + 1, g[0][i] = pos;
            Modify(Rt, 1, MX, i, 0);
        }
        while (cnt) tr[cnt --] = {0, 0, 0, 0};

        Rt = 0;
        G(i, n, 1) {
            pos = V = 0;
            Find(1, 1, MX, 1, a[i] - 1, 1);
            f[1][i] = V + 1, g[1][i] = pos;
            Modify(Rt, 1, MX, i, 1);
        }
        while (cnt) tr[cnt --] = {0, 0, 0, 0};
    
        V = pos = 0;
        F(i, 1, n)
            if (f[0][i] + f[1][i] - 1 > V) { V = f[0][i] + f[1][i] - 1, pos = i; } else
            if (f[0][i] + f[1][i] - 1 == V && a[i] > a[pos]) pos = i;
        F(i, 1, n)
            if (f[0][i] + f[1][i] - 1 == V && i < pos)
                pos = i;
        for (d[0] = 0, x = pos; x; x = g[0][x])
            d[++ d[0]] = x;
        G(i, d[0], 2)
            printf("%d ", d[i]);
        printf("%d", d[1]);
        for (d[0] = 0, x = pos; x; x = g[1][x])
            d[++ d[0]] = x;
        F(i, 2, d[0])
            printf(" %d", d[i]);
        puts("");

        Rt = 0;
        F(i, 1, n) {
            pos = V = 0;
            Find(1, 1, MX, 1, a[i] - 1, 1);
            f[1][i] = V + 1, g[1][i] = pos;
            Modify(Rt, 1, MX, i, 1);
        }
        while (cnt) tr[cnt --] = {0, 0, 0, 0};

        Rt = 0;
        G(i, n, 1) {
            pos = V = 0;
            Find(1, 1, MX, 1, a[i] - 1, 0);
            f[0][i] = V + 1, g[0][i] = pos;
            Modify(Rt, 1, MX, i, 0);
        }
        while (cnt) tr[cnt --] = {0, 0, 0, 0};

        V = pos = 0;
        F(i, 1, n)
            if (f[1][i] + f[0][i] - 1 > V) { V = f[1][i] + f[0][i] - 1, pos = i; } else
            if (f[1][i] + f[0][i] - 1 == V && a[i] < a[pos]) pos = i;
        F(i, 1, n)
            if (f[0][i] + f[1][i] - 1 == V && i > pos)
                pos = i;
        for (d[0] = 0, x = pos; x; x = g[1][x])
            d[++ d[0]] = x;
        G(i, d[0], 2)
            printf("%d ", d[i]);
        printf("%d", d[1]);
        for (d[0] = 0, x = pos; x; x = g[0][x])
            d[++ d[0]] = x;
        F(i, 2, d[0])
            printf(" %d", d[i]);
        puts("");
    }
}

E

f i f_i fi表示长度为 i i i的排列的期望逆序对个数。不难发现,每次取子序列实际上相当于一个新的排列,所有根据期望线性性就有 f i = g i n ! + ∑ j = 1 i ( i j ) 2 i f j f_i=\frac{g_i}{n!}+\sum_{j=1}^i\frac{\binom{i}{j}}{2^i}f_j fi=n!gi+j=1i2i(ji)fj其中 g i g_i gi表示长度为 i i i所有排列的逆序对个数之和。然后解方程即可。还可以通过打表发现答案是 n 2 9 \frac{n^2}{9} 9n2.

#include <bits/stdc++.h>

#define F(i, a, b) for (int i = a; i <= b; i ++)
#define G(i, a, b) for (int i = a; i >= b; i --)

using namespace std;

const int Mo = 998244353;
const int N = 3e3 + 10;

int n, f[N], g[N], jc[N], ny[N];

int ksm(int x, int y) {
    int ans = 1;
    for (; y; y >>= 1, x = (1ll * x * x) % Mo)
        if (y & 1)
            ans = (1ll * ans * x) % Mo;
    return ans;
}

int binom(int x, int y) {
    return 1ll * jc[x] * ny[y] % Mo * ny[x - y] % Mo;
}

int main() {
    n = N - 10;
    jc[0] = ny[0] = 1;
    F(i, 1, n) jc[i] = (1ll * jc[i - 1] * i) % Mo;
    ny[n] = ksm(jc[n], Mo - 2);
    G(i, n - 1, 1) ny[i] = (1ll * ny[i + 1] * (i + 1)) % Mo;
    F(i, 2, n) {
        int s = 0;
        F(j, 1, i - 1)
            s = (s + 1ll * j * jc[j] % Mo * jc[i - j - 1] % Mo * binom(i - 1, j) % Mo) % Mo;
        f[i] = (1ll * i * f[i - 1] % Mo + s) % Mo;
    }
    int w = 2;
    F(i, 2, n) {
        w = w * 2 % Mo;
        int sum = 0;
        F(j, 2, i - 1)
            sum = (sum + 1ll * g[j] * binom(i, j) % Mo) % Mo;
        g[i] = (1ll * (1ll * f[i] * ny[i] % Mo) % Mo * w + sum) % Mo * ksm(w - 1, Mo - 2) % Mo;
    }
    
    while (~scanf("%d", &n)) {
        int Ans = 0;
        F(i, 1, n)
            Ans = (Ans + g[i]) % Mo;
        printf("%d\n", 1ll * Ans * ksm(n, Mo - 2) % Mo);
    }
}

H

一道很简单的最小割题。不妨把最后连向 S S S看做选 A A A,连向 T T T看做选 C C C。然后把两个有关系的点按照经典建图方式建即可。即先平均边权,然后计算中间那条边的边权。

#include <bits/stdc++.h>

#define F(i, a, b) for (int i = a; i <= b; i ++)
#define G(i, a, b) for (int i = a; i >= b; i --)
#define mem(a, b) memset(a, b, sizeof a)
#define mec(a, b, len) memcpy(a, b, len)
#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))
using namespace std;

#define get getchar()
void Re(int &x) {
	char c = get; x = 0; int t = 1;
	for (; !isdigit(c); c = get) t = (c == '-' ? - 1 : t);
	for (; isdigit(c); x = (x << 3) + (x << 1) + c - '0', c = get); x *= t;
}

const int inf = 1e9;
const int N = 500 + 10;
const int M = 2e5 + 10;

long long sum, Ans;
int n, m, S, T;
int u, v, a, b, c, dis[N], cur[N];
int tov[M], nex[M], las[N], tot, len[M];

void link(int x, int y, int z) {
	tov[++ tot] = y, nex[tot] = las[x], las[x] = tot, len[tot] = z;
	tov[++ tot] = x, nex[tot] = las[y], las[y] = tot, len[tot] = 0;
}

bool bfs() {
	deque <int> Q; Q.push_front(S); mem(dis, 0), dis[S] = 1;
	while (Q.size()) {
		int k = Q.front(); Q.pop_front();
		for (int x = las[k]; x ; x = nex[x])
			if (len[x] && !dis[tov[x]]) {
				dis[tov[x]] = dis[k] + 1;
				Q.push_back(tov[x]);
			}
	}
	return dis[T];
}
int dfs(int k, int flow) {
	if (k == T) return flow;
	int have = 0;
	for (int &x = cur[k]; x ; x = nex[x])
		if (len[x] && dis[tov[x]] == dis[k] + 1) {
			int now = dfs(tov[x], min(flow - have, len[x]));
			have += now, len[x] -= now, len[x ^ 1] += now;
			if (flow == have) return flow;
		}
	if (!have) dis[k] = - 1;
	return have;
}

int main() {
	while (~scanf("%d%d", &n, &m)) {
		F(i, 0, T) las[i] = 0;
		tot = 1, S = 0, T = n + 1, Ans = sum = 0;
		F(i, 1, m) {
			Re(u), Re(v), Re(a), Re(b), Re(c);
			sum += 2ll * (a + b + c);
			link(u, T, a / 4 + 4 * c / 3);
			link(v, T, a / 4 + 4 * c / 3);
			link(S, u, 5 * a / 4 + c / 3);
			link(S, v, 5 * a / 4 + c / 3);
			link(u, v, c / 3 + a / 2);
			link(v, u, c / 3 + a / 2);
		}
		while (bfs())
			mec(cur, las, 4 * (T + 1)), Ans += dfs(S, inf);
		cout << ((sum - Ans) / 2) << endl;
	}
}

I

裸PAM,然后hash判断即可。

#include <bits/stdc++.h>

#define F(i, a, b) for (int i = a; i <= b; i ++)
#define G(i, a, b) for (int i = a; i >= b; i --)
#define max(a, b) ((a) > (b) ? (a) : (b))
#define mem(a, b) memset(a, b, sizeof a)

typedef unsigned long long uLL;

using namespace std;

const int N = 3 * 1e5 + 10;
const int M = 26;

const uLL bee1 = 233;
const uLL bee2 = 323;
uLL H1[N], B1[N], G1[N];
uLL H2[N], B2[N], G2[N];

int n, len, nw, ls, num; char s[N];
int a[N], L[N], cnt[N], fail[N], son[N][M], pos[N], Ans[N];

void add(int x) {
    a[++ n] = x;
    for (nw = ls; (L[nw] == n - 1) || (a[n - L[nw] - 1] ^ a[n]); nw = fail[nw]);
    if (!son[nw][x]) {
        cnt[son[nw][x] = ++ num] = 1, pos[num] = n;
        L[num] = L[nw] + 2;
        if (nw == 1) fail[num] = 2; else {
            for (nw = fail[nw]; a[n - L[nw] - 1] ^ a[n]; nw = fail[nw]);
            fail[num] = son[nw][x];
        }
        nw = num;
    } else nw = son[nw][x], cnt[nw] ++;
    ls = nw;
}

int calc1(int l, int r) {
    return H1[r] - H1[l - 1] * B1[r - l + 1];
}
int calc2(int l, int r) {
    return G1[l] - G1[r + 1] * B1[r - l + 1];
}
int calc3(int l, int r) {
    return H2[r] - H2[l - 1] * B2[r - l + 1];
}
int calc4(int l, int r) {
    return G2[l] - G2[r + 1] * B2[r - l + 1];
}

int main() {
    while (~scanf("%s", s + 1)) {
    len = strlen(s + 1), L[1] = - 1, L[2] = 0, fail[2] = 1, ls = 1, num = 2, B1[0] = B2[0] = 1, n = 0;
    F(i, 1, len) {
        add(s[i] - 'a');
        H1[i] = (H1[i - 1] * bee1 + a[n]), B1[i] = B1[i - 1] * bee1;
        H2[i] = (H2[i - 1] * bee2 + a[n]), B2[i] = B2[i - 1] * bee2;
    }
    G1[len+1]=G2[len+1]=0;
    G(i, len, 1)
        G1[i] = (G1[i + 1] * bee1 + a[i]), G2[i] = (G2[i + 1] * bee2 + a[i]);
    G(i, num, 4) cnt[fail[i]] += cnt[i];
    F(i, 3, num) {
        int l = pos[i] - L[i] + 1, r = pos[i], m = (l + r) >> 1;
        int T = m - l + 1 >> 1;
        if (l == m || (calc1(l, l + T - 1) == calc2(m - T + 1, m)) && (calc3(l, l+T-1) == calc4(m - T + 1, m)))
            Ans[L[i]] += cnt[i];
    }
    F(i, 1, len)
        printf("%d%c", Ans[i], i == len ? '\n' : ' '), Ans[i]=0;
    while (num) mem(son[num], 0), num --;
    }
}

K

斐波那契 + Segment_Tree。

#include <bits/stdc++.h>

#define F(i, a, b) for (LL i = a; i <= b; i ++)
#define G(i, a, b) for (LL i = a; i >= b; i --)
#define mem(a, b) memset(a, b, sizeof a)
#define mec(a, b) memcpy(a, b, sizeof a)
#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))
using namespace std;


typedef long long LL;

#define get getchar()
void Re(LL &x) {
    char c = get; x = 0; LL t = 1;
    for (; !isdigit(c); c = get) t = (c == '-' ? - 1 : t);
    for (; isdigit(c); x = (x << 3) + (x << 1) + c - '0', c = get); x *= t;
}

const LL N = 1e5 + 10;
const LL T = 4 * N;

LL n,q, a[N], l, r;
LL V, Pos, d[N];
struct SegmentTree {
    LL v, pos;
} tr[T];

#define ls (x << 1)
#define rs (ls | 1)
#define M (st + en >> 1)
void Modify(LL x, LL st, LL en, LL p, LL q) {
    if (st == en) { tr[x].v = q, tr[x].pos = st; return; }
    M >= p ? Modify(ls, st, M, p, q) : Modify(rs, M + 1, en, p, q);
    tr[x].v = max(tr[ls].v, tr[rs].v);
    tr[x].pos = tr[x].v == tr[ls].v ?  tr[ls].pos : tr[rs].pos;
}
void Go(LL x, LL st, LL en, LL l, LL r) {
    if (l <= st && en <= r) {
        if (tr[x].v > V) {
            V = tr[x].v;
            Pos = tr[x].pos;
        }
        return;
    }
    if (M >= l) Go(ls, st, M, l, r);
    if (M < r) Go(rs, M + 1, en, l, r);
}
LL Find(LL x, LL st, LL en, LL l, LL r) {
    V = 0, Pos = 0;
    Go(x, st, en, l, r);
    return Pos;
}

void Wr(LL x) {
    if (x > 9) Wr(x / 10);
    putchar(x % 10 + '0');
}
void ToDo() {
    LL A = Find(1, 1, n, l, r);
    d[++ d[0]] = A; Modify(1, 1, n, A, 0);
    LL B = Find(1, 1, n, l, r);
    d[++ d[0]] = B; Modify(1, 1, n, B, 0);
    LL C = Find(1, 1, n, l, r);
    d[++ d[0]] = C; Modify(1, 1, n, C, 0);
//    printf("%d %d %d\n", A, B, C);
    if (a[B] + a[C] > a[A]) {
        Wr(a[A]+a[B]+a[C]); puts("");
        return;
    }
    F(i, 1, min(50, r - l + 1 - 3)) {
        A = B, B = C, C = Find(1, 1, n, l, r);
        d[++ d[0]] = C, Modify(1, 1, n, C, 0);
        if (a[B] + a[C] > a[A]) {
            Wr(a[A]+a[B]+a[C]); puts("");
            return;
        }
    }
    puts("-1");
}

int main() {
//int st = clock();
    while (~scanf("%d%d", &n,&q)) {
        F(i, 1, n) Re(a[i]), Modify(1, 1, n, i, a[i]);
        F(i, 1, q) {
            Re(l), Re(r);
            if (r - l + 1 < 3) { puts("-1"); continue; }
            ToDo();
            while (d[0]) Modify(1, 1, n, d[d[0]], a[d[d[0]]]), d[0] --;
        }
        F(i, 1, n) Modify(1, 1, n, i, 0);
    }
//printf("%lf", (double)(clock()-st)/CLOCKS_PER_SEC);
}

总结

这一场比赛实际上难度还是比较低的,除了几道题比较毒以外。事实上有五道很简单的题,然后还有两题需要一点点省选级别的知识就可以做出来,然后貌似这个F也是可以做的,所以这次可以说是一般般,至少也要7道啊,最后我们才做了5道,继续加油吧。虽然是noip临近,但一些省选难度的裸题还是要会做啊。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值