九省联考 2018 简要题解

从这里开始

  • 题目请移至 loj 查看

  每日憨批 ($\infty / 1$)。感觉自己离滚蛋不远了。

Day 1

Problem A 一双木棋

   dp 即可

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

typedef vector<int> vec;

const int S = 1 << 20;
const int inf = (~0u >> 2);

int n, m;

int encode(const vec& a) {
	int p = m, s = 0;
	for (int i = 0; i < n; i++) {
		while (p > a[i])
			s = s << 1, p--;
		s = s << 1 | 1;
	}
	while (p)
		s <<= 1, p--;
	return s;
}
vec decode(int s) {
	vec rt;
	rt.reserve(n);
	int p = m;
	for (int i = n + m - 1; ~i; i--) {
		if ((s >> i) & 1) {
			rt.push_back(p);
		} else {
			p--;
		}
	}
	return rt;
}

int f[S];
bitset<S> vis;
int A[2][12][12];

int dp(int s) {
	if (vis[s])
		return f[s];
	vis[s] = true;
	f[s] = -inf;
	vec v = decode(s);
	int sum = 0;
	for (auto x : v)
		sum += x;
	if (sum == n * m)
		return 0;
	int ls = m;
	for (int i = 0; i < n; i++) {
		if (v[i] ^ ls) {
			vec nv = v;
			nv[i]++;
			ls = v[i];
			f[s] = max(f[s], A[sum & 1][i][v[i]] - dp(encode(nv)));
		}
	}
	return f[s];
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 0; i < 2; i++) {
		for (int x = 0; x < n; x++) {
			for (int y = 0; y < m; y++) {
				scanf("%d", A[i][x] + y);
			}
		}
	}
	int ans = dp(encode(vec(n, 0)));
	printf("%d\n", ans);
	return 0;
}

Problem B IIIDX

   有相等的情形相当于由若干个要在某一个前缀中选出若干个的要求。这个可以用 Hall 定理判。它大概是后缀 min 大于等于 0。这个限制显然能用线段树维护。

Code

#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstdio>
using namespace std;
typedef bool boolean;

const int N = 5e5 + 5;

typedef class SegTreeNode {
    public:
        int mv, tg;
        SegTreeNode *l, *r;

        void pushUp() {
            mv = (l->mv < r->mv) ? (l->mv) : (r->mv);
        }

        void pushDown() {
            l->mv -= tg, l->tg += tg;
            r->mv -= tg, r->tg += tg;
            tg = 0;
        }
}SegTreeNode;

SegTreeNode pool[N << 2];
SegTreeNode *top = pool;

SegTreeNode* newnode(int mv) {
    top->mv = mv, top->tg = 0;
    top->l = top->r = NULL;
    return top++;
}

typedef class SegTree {
    public:
        int n;
        SegTreeNode* rt;

        SegTree() {    }
        SegTree(int n):n(n) {
            build(rt, 1, n);
        }

        void build(SegTreeNode*& p, int l, int r) {
            p = newnode(l);
            if (l == r)
                return ;
            int mid = (l + r) >> 1;
            build(p->l, l, mid);
            build(p->r, mid + 1, r);
        }

        void update(SegTreeNode* p, int l, int r, int ql, int qr, int val) {
            if (l == ql && r == qr) {
                p->mv -= val, p->tg += val;
                return;
            }
            if (p->tg)
                p->pushDown();
            int mid = (l + r) >> 1;
            if (qr <= mid)
                update(p->l, l, mid, ql, qr, val);
            else if (ql > mid)
                update(p->r, mid + 1, r, ql, qr, val);
            else {
                update(p->l, l, mid, ql, mid, val);
                update(p->r, mid + 1, r, mid + 1, qr, val);
            }
            p->pushUp();
        }

        int query(SegTreeNode* p, int l, int r, int len) {
            if (l == r)
                return (p->mv >= len) ? (l) : (l + 1);
            if (p->tg)
                p->pushDown();
            int mid = (l + r) >> 1;
            if (p->r->mv >= len)
                return query(p->l, l, mid, len);
            return query(p->r, mid + 1, r, len);
        }

        int query(int len) {
            return query(rt, 1, n, len);
        }

        void update(int l, int r, int val) {
            update(rt, 1, n, l, r, val);
        }
}SegTree;

int n;
double K;
int fa[N], ds[N];
int siz[N], cnt[N], ans[N];
SegTree st;

inline void init() {
    scanf("%d%lf", &n, &K);
    for (int i = 1; i <= n; i++)
        scanf("%d", ds + i);
    sort(ds + 1, ds + n + 1, greater<int>());
    for (int i = 1; i <= n; i++)
        fa[i] = i / K;
    for (int i = n; i; i--)
        siz[fa[i]] += (++siz[i]);
    cnt[n] = 1;
    for (int i = n - 1; i; i--)
        cnt[i] = (ds[i] == ds[i + 1]) ? (cnt[i + 1] + 1) : (1);
}

inline void solve() {
    st = SegTree(n);
    for (int i = 1, r; i <= n; i++) {
        if (i > 1 && fa[i] != fa[i - 1])
            st.update(ans[fa[i]], n, -siz[fa[i]] + 1);
        r = st.query(siz[i]);
        ans[i] = r + (--cnt[r]);
        st.update(ans[i], n, siz[i]);
    }
    for (int i = 1; i <= n; i++)
        printf("%d ", ds[ans[i]]);
}

int main() {
    init();
    solve();
    return 0;
}

Problem C 秘密袭击

  常规做法是枚举一个点,算它是第 $K$ 大的方案数。然后这个涉及到每次把一个点的重量从 0 变成 1。换根相当于是要求合并链上所有前缀背包的和或者后缀背包的和。这个可以把生成函数用点值表示,然后全局平衡二叉树瞎维护一下就行了。

  标算做法大概是考虑计算大于等于 $v$ 中的点选了 $j$ 个,然后对于每个点来说是一个区间加物品。现在要求合并子节点信息,以及区间加物品。后者用线段树维护,前者可以线段树合并。实际还有很多细节要处理,此处省略各种细节一万字。

  然而你笑道,正解无用,暴力把分送。

  说了这么多,还是暴力好写。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

const int N = 1670;
const int Mod = 64123;

#define ll long long

void inc(ll& x) {
	if (x >= Mod)
		x -= Mod;
}

int n, K;
ll f[N][N];
int a[N], id[N];
int sz[N], fa[N];
vector<int> G[N];

bitset<N> vis;
void recalc(int p) {
	for (int i = 0; i <= K && i <= sz[p]; i++)
		f[p][i] = 0;
	f[p][0] = 1;
	int sum = 0;
	for (auto e : G[p]) {
		if (e ^ fa[p]) {
			for (int i = min(sum, K); ~i; i--) {
				if (!f[p][i])
					continue;
				for (int j = min(K - i, sz[e]); ~j; j--) {
					f[p][i + j] += f[p][i] * f[e][j];
				}
			}
			sum += sz[e];
			for (int i = 0; i <= K && i <= sum; i++)
				f[p][i] %= Mod;
		}
	}
	if (vis.test(p)) {
		for (int i = min(K, sum + 1); i; i--)
			f[p][i] = f[p][i - 1];
		f[p][0] = 0;
	}
}

void rev(int p) {
	if (!fa[p]) {
		return;
	}
	int q = fa[p];
	rev(q);
	fa[q] = p;
	fa[p] = 0;
	sz[q] -= sz[p];
	sz[p] += sz[q];
	recalc(q);
}

void dfs(int p, int fa) {
	::fa[p] = fa;
	for (auto e : G[p]) {
		if (e ^ fa) {
			dfs(e, p);
		}
	}
	recalc(p);
}

int main() {
	scanf("%d%d%*d", &n, &K);
	for (int i = 1; i <= n; i++) {
		scanf("%d", a + i);
		id[i] = i;
	}
	for (int i = 1, u, v; i < n; i++) {
		scanf("%d%d", &u, &v);
		G[u].push_back(v);
		G[v].push_back(u);
	}
	sort(id + 1, id + n + 1, [&] (int x, int y) {	return a[x] > a[y];	});
	vis.set(id[1]);
	sz[id[1]] = 1;
	dfs(id[1], 0);
	ll ans = f[id[1]][K] * a[id[1]];
	for (int i = 2; i <= n; i++) {
		int p = id[i];
		vis.set(p);
		rev(p);
		sz[p]++;
		recalc(p);
		ans += f[p][K] * a[p];
	}
	printf("%lld\n", ans % Mod);
	return 0;
}

Day 2

Problem A 劈配

  第一问不断加边暴力增广。

  第二问相当于询问第 $i$ 个人只加入志愿在 $[1, s_i]$ 的边,在匈牙利树上到达左侧小于 $i$ 的点中的点权的最大值。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

const int N = 205;

int T, n, m;
int cap[N], ans[N];
vector<int> mat[N];
vector<int> a[N][N];
vector<int> G[N];
boolean visL[N], visR[N];

boolean augment(int p) {
	if (visL[p])
		return false;
	visL[p] = true;
	for (auto e : G[p]) {
		if (visR[e])
			continue;
		visR[e] = true;
		if ((signed) mat[e].size() < cap[e]) {
			mat[e].push_back(p);
			return true;
		}
		for (auto& np : mat[e]) {
			if (augment(np)) {
				np = p;
				return true;
			}
		}
	}
	return false;
}

void solve() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++) {
		scanf("%d", cap + i);
	}
	cap[m + 1] = 233;
	for (int i = 1; i <= n; i++) {
		for (int j = 1, x; j <= m; j++) {
			scanf("%d", &x);
			if (x)
				a[i][x].push_back(j);
		}
		a[i][m + 1].push_back(m + 1);
	}
	
	for (int i = 1; i <= n; i++) {
		fill(visL + 1, visL + n + 1, false);
		fill(visR + 1, visR + m + 2, false);
		for (int j = 1; j <= m + 1; j++) {
			G[i] = a[i][j];
			visL[i] = false;
			if (augment(i)) {
				ans[i] = j;
				break;
			}
		}
	}
	for (int i = 1; i <= n; i++) {
		printf("%d ", ans[i]);
	}
	putchar('\n');
	
	for (int i = 1; i <= n; i++) {
		G[i].clear();
	}
	for (int i = 1; i <= m + 1; i++) {
		mat[i].clear();
	}
	for (int i = 1, s; i <= n; i++) {
		scanf("%d", &s);
		int ans1 = 0;
		if (s >= ans[i]) {
			ans1 = i;
		} else {
			for (int j = 1; j <= s; j++) {
				for (auto e : a[i][j]) {
					G[i].push_back(e);
				}
			}
			fill(visL + 1, visL + n + 1, false);
			fill(visR + 1, visR + m + 1, false);
			augment(i);
			for (int j = 1; j < i; j++) {
				if (visL[j]) {
					ans1 = max(ans1, j);
				}
			}
		}
		printf("%d ", i - ans1);
		fill(visL + 1, visL + n + 1, false);
		fill(visR + 1, visR + m + 1, false);
		G[i] = a[i][ans[i]];
		augment(i);
	}
	putchar('\n');

	for (int i = 1; i <= n; i++) {
		G[i].clear();
	}
	for (int i = 1; i <= m + 1; i++) {
		mat[i].clear();
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m + 1; j++) {
			a[i][j].clear();
		}
	}
}

int main() {
	scanf("%d%*d", &T);
	while (T--) {
		solve();
	}
	return 0;
}

Problem B 林克卡特树

  不难把问题转化成选 $K + 1$ 条点不相交的链,最大化边权和。

  凸优化,dp 即可。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

#define ll long long
#define pii pair<int, int>
#define pli pair<ll, int>
#define fi first
#define sc second

template <typename T>
boolean vmax(T& a, T b) {
	return (a < b) ? (a = b, true) : false;
}
template <typename T>
T smax(T a) {
	return a;
}
template <typename T, typename ...K>
T smax(T a, const K& ...args) {
	return max(a, smax(args...));
}

const int N = 3e5 + 5;
const ll llf = 3e18;

pli operator + (pli a, pli b) {
	return pli(a.first + b.first, a.second + b.second);
}

int n, K;
vector<pii> G[N];

pli f[N][4];
void dfs(int p, int fa, ll mid) {
	static pli h[4];
	pli *g = f[p];
	g[0] = pli(0, 0);
	g[1] = pli(-llf, 0);
	g[2] = pli(-llf, 0);
	g[3] = pli(0, 0);
	for (auto _ : G[p]) {
		int e = _.first;
		int w = _.second;
		if (e == fa)
			continue;
		dfs(e, p, mid);
		pli *t = f[e];
		h[0] = h[1] = h[2] = h[3] = pli(-llf, 0);
		pli v1 = max(smax(t[0], t[1], t[2]) + pli(mid, 1), t[3]);
		pli v2 = max(t[0], t[1]) + pli(w, 0);
		vmax(h[0], g[0] + v1);
		vmax(h[1], g[0] + v2);
		vmax(h[1], g[1] + v1);
		vmax(h[2], g[2] + v1);
		vmax(h[2], g[1] + v2);
		vmax(h[3], g[3] + v1);
		g[0] = h[0], g[1] = h[1];
		g[2] = h[2], g[3] = h[3];
	}
}
	

int main() {
	scanf("%d%d", &n, &K);
	++K;
	for (int i = 1, u, v, w; i < n; i++) {
		scanf("%d%d%d", &u, &v, &w);
		G[u].emplace_back(v, w);
		G[v].emplace_back(u, w);
	}
	ll l = -3e11, r = 3e11, mid;
	while (l <= r) {
		mid = (l + r) >> 1;
		dfs(1, 0, mid);
		pli* t = f[1];
		pli res = max(smax(t[0], t[1], t[2]) + pli(mid, 1), t[3]);
		if (res.sc < K) {
			l = mid + 1;
		} else {
			r = mid - 1;
		}
	}
	dfs(1, 0, l);
	pli* t = f[1];
	pli res = max(smax(t[0], t[1], t[2]) + pli(l, 1), t[3]);
	ll ans = res.fi - K * l;
	printf("%lld\n", ans);
	return 0;
}

Problem C 制胡窜

  发现很久没写线段树和后缀自动机,于是抱着写写板子的心态来写写这个题,于是又荒废了一个晚上。

  一个没什么意思的讨论题。

  假设你处理出了 right 集合。我们设三段字符串分别为 $s_1, s_2, s_3$

  • $s_1$ 中包含 $s$
  • $s_3$ 中包含 $s$
  • $s_1, s_3$ 中同时包含 $s$
  • $s_2$ 中包含 $s$,但 $s_1, s_3$ 都不包含
    • $s$ 第一次出现没有被截断,最后一次出现也没有被截断
    • $s$ 第一次出现被截断,最后一次出现没有被截断
    • $s$ 第一次没有出现被截断,最后一次出现被截断
    • $s$ 第一次出现和最后一次出现都被截断

  最后一项要用 border 长度形成 log 个等差数列的性质吗?不不不,冷静一下,它只用维护相邻两项的差乘后一项的乘积,这个线段树瞎维护一下就行了。

  实际还有很多细节要处理,此处省略各种细节一万字。

Code

/**
 * Copy the templates and you'll get AC.
 * Please stop writing problems!
 */
#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

const int N = 1e5 + 5;
const int bzmax = 18;

#define ll long long

int n, m;

typedef class Data {
	public:
		int mi, mx;
		ll sum;

		Data() {	}
		Data(int x) : mi(x), mx(x), sum(0) {	} 
		Data(int mi, int mx, ll sum) : mi(mi), mx(mx), sum(sum) {	}

		boolean empty() {
			return mi > mx;
		}
		Data operator + (Data b) {
			if (empty())
				return b;
			if (b.empty())
				return *this;
			return Data(mi, b.mx, sum + b.sum + (b.mi - mx) * 1ll * b.mi);
		}
} Data;

Data dnul (N, -1, 0);

typedef class SegTreeNode {
	public:
		Data d;
		SegTreeNode *l, *r;
		
		SegTreeNode() : d(N, -1, 0) {	}
		SegTreeNode(SegTreeNode* slf) : l(slf), r(slf) {	}
		
		void push_up() {
			d = dnul;
			if (l) d = l->d;
			if (r) d = d + r->d;
		}
} SegTreeNode;

SegTreeNode pool[N * 40];
SegTreeNode *_top1 = pool;

void insert(SegTreeNode* &p, int l, int r, int idx, const Data& v) {
	p = _top1++;
	if (l == r) {
		p->d = v;		
		return;
	}
	int mid = (l + r) >> 1;
	if (idx <= mid) {
		insert(p->l, l, mid, idx, v);
	} else {
		insert(p->r, mid + 1, r, idx, v);
	}
	p->push_up();
}
void insert(SegTreeNode*& p, int idx, const Data& v) {
	insert(p, 1, n, idx, v);
}

Data query(SegTreeNode* p, int l, int r, int ql, int qr) {
	if (!p)
		return dnul;
	if (l == ql && r == qr)
		return p->d;
	int mid = (l + r) >> 1;
	if (qr <= mid) {
		return query(p->l, l, mid, ql, qr);
	} else if (ql > mid) {
		return query(p->r, mid + 1, r, ql, qr);
	}
	return query(p->l, l, mid, ql, mid) + query(p->r, mid + 1, r, mid + 1, qr);
}
Data query(SegTreeNode* p, int l, int r) {
	return query(p, 1, n, l, r);
}

void merge(SegTreeNode* a, SegTreeNode* b, SegTreeNode* &p) {
	if (!a) {
		p = b;
		return;
	}
	if (!b) {
		p = a;
		return;
	}
	p = _top1++;
	merge(a->l, b->l, p->l);
	merge(a->r, b->r, p->r);
	p->push_up();
}

inline int cti(char x) {
	return x - '0';
}

typedef class TrieNode {
	public:
		int len;
		TrieNode* par;
		TrieNode* ch[10];
		TrieNode* bz[bzmax];
		SegTreeNode* rt;

		void init_bz() {
			if (!par) {
				bz[0] = this;
			} else {
				bz[0] = par;
			}
			for (int i = 1; i < bzmax; i++)
				bz[i] = bz[i - 1]->bz[i - 1];
		}
		void insert(int x) {
			::insert(rt, x, Data(x));
		}
		TrieNode* jump(int nlen) {
			TrieNode* p = this;
			for (int i = bzmax - 1; ~i; i--) {
				if (p->bz[i]->len >= nlen) {
					p = p->bz[i];
				}
			}
			return p;
		}
} TrieNode;

TrieNode pool1[N << 1];
TrieNode* _top = pool1;

TrieNode* newnode(int len) {
	_top->len = len;
	return _top++;
}

typedef class SuffixAutomaton {
	public:
		TrieNode *rt, *lst;

		SuffixAutomaton() : rt(newnode(0)), lst(rt) {	}

		TrieNode* extend(char _) {
			int c = cti(_);
			TrieNode* p = newnode(lst->len + 1);
			TrieNode* f = lst;
			while (f && !f->ch[c])
				f->ch[c] = p, f = f->par;
			if (!f) {
				p->par = rt;
			} else {
				TrieNode* q = f->ch[c];
				if (q->len == f->len + 1) {
					p->par = q;
				} else {
					TrieNode* nq = newnode(f->len + 1);
					memcpy(nq->ch, q->ch, sizeof(nq->ch));
					nq->par = q->par, q->par = p->par = nq;
					while (f && f->ch[c] == q)
						f->ch[c] = nq, f = f->par;
				}
			}
			return lst = p;
		}

		vector<TrieNode*> order;
		void topusort() {
			vector<int> cnt (lst->len + 1, 0);
			for (TrieNode* p = pool1; p != _top; p++)
				cnt[p->len]++;
			for (int i = 1; i < (signed) cnt.size(); i++)
				cnt[i] += cnt[i - 1];
			order.resize(_top - pool1);
			for (TrieNode* p = pool1; p != _top; p++)
				order[--cnt[p->len]] = p;
		}
} SuffixAutomaton;

char s[N];
TrieNode* nodes[N];
SuffixAutomaton sam;

ll Cn2(int n) {
	if (n < 3)
		return 0;
	return 1ll * n * (n - 1) / 2 - (n - 1);
}

ll solve(SegTreeNode* st, int len) {
	assert(st);
	Data dall = st->d;
	int L = dall.mi, R = dall.mx;
	ll ansL = Cn2(n - (L + len - 1) + 1);
	ll ansR = Cn2(R);
	ll ansM00 = 1ll * (L - 1) * (n - (R + len - 1));
	if (L == R)
	   return ansL + ansR + ansM00;
	ll ansLR = Cn2(R - L - len + 2);
	ll ansM01 = 1ll * (L - 1) * (min(len - 1, R - L));
	ll ansM10 = 1ll * (min(len - 1, R - L)) * (n - (R + len - 1));
	ll ansM11 = 0;
	Data d = query(st, max(1, R - len + 1), n);
	int M = d.mi, Led = L + len - 1;
	if (M == L) {
		ansM11 = 1ll * (R - L) * R - d.sum;
	} else {
		int pr = query(st, L, M - 1).mx;
		if (pr < Led) {
			ansM11 = 1ll * (pr - L) * (len - 1) + 1ll * (Led - pr) * R;
			d = query(st, pr, Led - 1);
			ansM11 -= d.sum + 1ll * (Led - d.mx) * query(st, Led, R).mi;
		} else {
			ansM11 = 1ll * (len - 1) * (len - 1);
		}
	}
	return ansL + ansR - ansLR + ansM00 + ansM01 + ansM10 + ansM11;
}
ll solve(int l, int r) {
	TrieNode* p = nodes[l]->jump(r - l + 1);
	return solve(p->rt, r - l + 1);
}

int main() {
	scanf("%d%d", &n, &m);
	scanf("%s", s + 1);
	for (int i = n; i; i--)
		nodes[i] = sam.extend(s[i]);
	sam.topusort();
	auto& V = sam.order;
	for (auto p : V)
		p->init_bz();
	reverse(V.begin(), V.end());
	for (int i = 1; i <= n; i++)
		nodes[i]->insert(i);
	for (auto p : V) {
		if (p->par) {
			merge(p->par->rt, p->rt, p->par->rt);
		}
	}
	while (m--) {
		int l, r;
		scanf("%d%d", &l, &r);
		printf("%lld\n", solve(l, r));
	}
	return 0;
}

posted @ 2020-03-11 10:03  阿波罗2003  阅读(303)  评论(0编辑  收藏  举报