「联赛测试」叁拾叁

合并集合

不会的建议直接重学 区间\(dp\)

跟石子合并很像,发现 \(n\) 很小,直接 \(n^3\) 预处理贡献就行了,然后直接 \(dp\),懒得讲了。

小声bb:考试的时候,20分钟打完正解,为了对拍,暴力打了40分钟/kk

代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int maxn = 6e2 + 50, INF = 0x3f3f3f3f;

inline int read () {
	register int x = 0, w = 1;
	register char ch = getchar ();
	for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
	for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
	return x * w;
}

inline void write (register int x) {
	if (x / 10) write (x / 10);
	putchar (x % 10 + '0');
}

int n, ans;
int a[maxn], f[maxn][maxn], num[maxn][maxn];
bool vis[maxn];

int main () {
	freopen ("merge.in", "r", stdin);
	freopen ("merge.out", "w", stdout);
	n = read();
	for (register int i = 1; i <= n; i ++) a[i] = a[i + n] = read();
	for (register int d = 1; d <= n; d ++) {
		for (register int i = 1, j; (j = i + d - 1) <= 2 * n; i ++) {
			memset (vis, 0, sizeof vis);
			for (register int k = i; k <= j; k ++) {
				if (! vis[a[k]]) num[i][j] ++, vis[a[k]] = 1;
			}
		}
	}
	for (register int d = 2; d <= n; d ++) {
		for (register int i = 1, j; (j = i + d - 1) <= 2 * n; i ++) {
			for (register int k = i; k < j; k ++) {
				f[i][j] = max (f[i][j], f[i][k] + f[k + 1][j] + num[i][k] * num[k + 1][j]);
			}
		}
	}
	for (register int i = 1, j; (j = i + n - 1) <= 2 * n; i ++) ans = max (ans, f[i][j]);
	printf ("%d\n", ans);
	return 0;
}

ZYB建围墙

一看数据范围 \(1e9\) 和简单的输入输出,很明显是个数学题,考场上找了 \(1\) 个半小时的规律,没有头绪,想到某略日,果断放弃。

想起来还是比较简单的。

很显然,放的越紧凑,公共边越多,最后的答案是越小的。

所以我们先考虑把大部分放成如下形状:

剩下的单个的再绕着周围放,可以贪心一下放,建议自己手摸。

代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;

const int maxn = 1e5 + 50, INF = 0x3f3f3f3f;

inline int read () {
	register int x = 0, w = 1;
	register char ch = getchar ();
	for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
	for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
	return x * w;
}

inline void write (register int x) {
	if (x / 10) write (x / 10);
	putchar (x % 10 + '0');
}

int n, d, ans;
int f[maxn];

inline void Init () {
	for (register int i = 0; i <= n; i ++) {
		f[i] = 6 * (i + 1) * i / 2 + 1;
		if (f[i] > n) {
			d = i, n -= f[i - 1], ans += f[i] - f[i - 1]; break;
		}
	}	
}

int main () {
	freopen ("wall.in", "r", stdin);
	freopen ("wall.out", "w", stdout);
	n = read(), Init ();
	if (n) ans += (n / d + 1);
	printf ("%d\n", ans);
	return 0;
}

ZYB和售货机

直接贪心可以搞到 \(40\; pts\)

如果我们对于每个 \(i\),向它的 \(f[i]\) 建边,会发现是一个类似基环森林的东西。

我们先利用一下贪心的思想,把能取的都取了,使每个都剩下一个,这样既能做到贡献最大,又可以不会对接下来的选择造成影响,所以这个策略是正确的。

接下来所有的物品就剩下了 \(1\) 个。

考虑上面那个图:

  • 对于链的情况,显然一条链选下去所有的价值都可以选上。

  • 对于一个环(没有链与之相连),一定会有一个点的贡献选不到,求和减去最小的即可。

但是,上面那个图的边太多了,考虑简化:

如果说,我们选一个成本最小的儿子向它建边,一个点的入度和出度都不会超过 \(1\),只会剩下单独的链和环,就好做了。

但是,很容易发现这样做的正确性是不对的,比如下面这个样例:

3
2 2 10 1
3 2 8 1
2 1 9 1

我们按上面的方法建出图来后,只会有 \(2\)\(3\) 的一个环,求出来是 \(7\)

显然我们可以先选 \(2\),再选 \(1\),使我们的答案为 \(13\)

所以,我们对每个环上的点考虑一下它所有儿子中的次小值,即贡献第二小的点,比如:

\(5\)\(4\) 贡献第二大的点,\(3\)\(4\) 贡献第一大的点,由于选完 \(3\) 不能选 \(4\) 的影响,会有两种决策:

  • 选了 \(3\) 而不能选 \(4\)\(5\)

  • 选了 \(5\)\(4\) 而不能选 \(3\)

所以两种情况取个最大值即可。

代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>

typedef long long ll;

using namespace std;

const int maxn = 1e5 + 50, INF = 0x3f3f3f3f;

inline int read () {
	register int x = 0, w = 1;
	register char ch = getchar ();
	for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
	for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
	return x * w;
}

inline void write (register int x) {
	if (x / 10) write (x / 10);
	putchar (x % 10 + '0');
}

int n;
ll ans;

struct Edge {
	int to, next;
} e[maxn << 1];

int tot, head[maxn];

inline void Add (register int u, register int v) {
	e[++ tot].to = v;
	e[tot].next = head[u];
	head[u] = tot;
}

struct Node {
	int id, val;
	Node () {}
	Node (register int a, register int b) { id = a, val = b; }
	inline bool operator < (const Node &x) const { return val < x.val; }
};

int f[maxn], c[maxn], d[maxn], a[maxn], val[maxn], val2[maxn];

vector <Node> vec[maxn];
priority_queue <Node> q;

bool vis[maxn];
int dfn[maxn], low[maxn], size[maxn], belong[maxn], maxx[maxn], st[maxn], deg[maxn], rdeg[maxn];
ll totval[maxn];
int tic, top, sum;

inline void Tarjan (register int u) {
	dfn[u] = low[u] = ++ tic, st[++ top] = u, vis[u] = 1;
	for (register int i = head[u]; i; i = e[i].next) {
		register int v = e[i].to;
		if (! dfn[v]) {
			Tarjan (v);
			low[u] = min (low[u], low[v]);
		} else if (vis[v]) {
			low[u] = min (low[u], dfn[v]);
		}
	}
	if (dfn[u] == low[u]) {
		sum ++;
		while (st[top + 1] != u) {
			register int v = st[top --];
			size[sum] ++, rdeg[sum] += deg[v];
			belong[v] = sum;
			if (vec[v].size ()) maxx[sum] = max (maxx[sum], val2[v] - val[vec[v][vec[v].size () - 1].id]);
			if (deg[v]) totval[sum] += val[v];
			vis[v] = 0;
		}
	}
}

int main () {
	freopen ("goods.in", "r", stdin);
	freopen ("goods.out", "w", stdout);
	n = read(), memset (maxx, - 0x3f, sizeof maxx);;
	for (register int i = 1; i <= n; i ++) f[i] = read(), c[i] = read(), d[i] = read(), a[i] = read();
	for (register int i = 1; i <= n; i ++) val[i] = d[f[i]] - c[i], vec[f[i]].push_back (Node (i, d[f[i]] - c[i])), q.push (Node (f[i], d[f[i]] - c[i]));
	while (! q.empty ()) {
		register Node t = q.top (); q.pop ();
		if (t.val <= 0) break;
		if (a[t.id] > 1) ans += 1ll * (a[t.id] - 1) * t.val, a[t.id] = 1;
	}
	for (register int i = 1; i <= n; i ++) {
		sort (vec[i].begin (), vec[i].end ());
		if (vec[i].empty ()) continue;
		register int end = vec[i].size () - 1;
		if (vec[i][end].val > 0) Add (vec[i][end].id, i), deg[vec[i][end].id] ++;
		if (vec[i].size () >= 2 && val[vec[i][end - 1].id] > 0) val2[i] = val[vec[i][end - 1].id];
	}
	for (register int i = 1; i <= n; i ++) if (! dfn[i]) Tarjan (i);
	for (register int i = 1; i <= sum; i ++) {
		if (size[i] == 1) ans += totval[i];
		else ans += totval[i] + maxx[i];
	}
	printf ("%lld\n", ans);
	return 0;
} 

ZYB玩字符串

暴力可以考虑枚举每个子串去在原串中以此消掉

这种带有一点贪心的思想很容易被hack掉

ababaa

很显然答案是 \(aba\),但是如果从前往后消的话,这种方案被算到不合法的情况里了


正解是思路很奇怪的 \(dp\),但是很好理解

先枚举一个要检验的长度为 \(d\) 的子串 \(res\),然后

定义 \(f[i][j]\) 表示原串中从 \(i\)\(j\) 能否被全部消掉

转移有两种:

  • \(f[i][j] \; |= f[i][j - 1] \& (str[j] = res[(j - i) \% d + 1])\)

  • \(f[i][j] \; |= f[i][j - k\times d] \& f[j - k\times d + 1][j]\)

第一个很好理解,看一下第 \(j\) 位的字符能否被匹配,相当于是类似于贪心的一位一位的匹配

第二个转移保证了我们整体转移的正确性,其实就是往后几个几个串地匹配,这样上面那个样例自然就合法了

直接记忆化搜索,不过转移二必须要写到转移一上面,这样才能保证正确性

代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int maxn = 666 + 50, INF = 0x3f3f3f3f;

inline int read () {
	register int x = 0, w = 1;
	register char ch = getchar ();
	for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
	for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
	return x * w;
}

inline void write (register int x) {
	if (x / 10) write (x / 10);
	putchar (x % 10 + '0');
}

int T, n, d, f[maxn][maxn];
char str[maxn], ans[maxn];

inline bool cmp (register char * a, register char * b) {
	register int len = strlen (a + 1);
	for (register int i = 1; i <= len; i ++) if (a[i] < b[i]) return 1;
	return 0;
}

inline bool same (register char * a, register char * b) {
	register int len = strlen (a + 1);
	for (register int i = 1; i <= len; i ++) if (a[i] != b[i]) return 0;
	return 1;	
}

inline int DFS (register char * res, register int l, register int r) {
	if (f[l][r] != -1) return f[l][r];
	if (l > r) return 1;
	for (register int k = r - d; k >= l; k -= d) if (DFS (res, l, k) && DFS (res, k + 1, r)) return f[l][r] = 1;
	if (str[r] == res[(r - l) % d + 1]) return f[l][r] = DFS (res, l, r - 1);
	return f[l][r] = 0;
}

int main () {
	freopen ("string.in", "r", stdin);
	freopen ("string.out", "w", stdout);
	T = read();
	while (T --) {
		scanf ("%s", str + 1), n = strlen (str + 1), memset (ans, 0, sizeof ans);
		register bool flag = 0;
		for (d = 1; d <= n; d ++) {
			if (n % d) continue;
			for (register int i = 1, j; (j = i + d - 1) <= n; i ++) {
				register char res[maxn] = {};
				memcpy (res + 1, str + i, d), memset (f, -1, sizeof f);	
				if (DFS (res, 1, n)) {
					if (cmp (res, ans) || ans[1] == 0) memcpy (ans + 1, res + 1, d);
					flag = 1;
				}
			}
			if (flag) break;
		}
		printf ("%s\n", ans + 1);
	}
	return 0;
}
posted @ 2020-11-19 18:32  Rubyonlу  阅读(78)  评论(1编辑  收藏  举报