【NOIP2018模拟10.25】 总结

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/er111er/article/details/83382315

做题策略的总结

三道题,T1原本打了个错的DP,后来干脆改成了贪心,只花了30mins就解决了这题,获得了90pts的好成绩。T2想到了正解,写Code+对拍用了2h,最后因为数组开小获得了跟暴力一样的成绩,苦酒入喉心作痛。T3 看都没看瞎jb打了个dp就交了,获得了0pts的好成绩。
事实证明:
数组开大一点会死吗?
数组开大一点会死吗?
数组开大一点会死吗?
重要的事情说三遍,再忘记开大数组就回家种田吧。

T1

T1

Constraints

Constraints
这题的样例是有锅的,没有给数据组数T,导致很多人爆0,然而细心的忘了开大数组的我并没有掉进坑里。

不要小看这个特殊性质:数据保证随机生成。
记住是“水机”生成。
显然的贪心算法:选择 c i c_i 最小的一个,把其他的都改成最小的这个,获得了 90 p t s 90pts 的(nice)成绩,爆踩正解党。
然鹅这个数据就能把它卡掉:
5
4 4 1 4 4
这种情况的话肯定是把唯一的1改成4,但是贪心的做法就挂了。
(不过敢于贪心得到了出题人的极大鼓励)

题解是 O ( n 3 ) O(n^3) 的做法,还有 O ( n 2 ) O(n^2) 的算法,但我并没有理解。
我们首先枚举最终变成的颜色,然后就可以DP。
f i f_i 表示把前 i i 个瓶子都改成最终颜色的代价, m i n i , j min_{i,j} 表示区间 [ i , j ] [i,j] 的最小值, c o s t i , j cost_{i,j} 表示把区间 [ i , j ] [i,j] 都改成 m i n i , j min_{i,j} 的代价,最终颜色为 c o l o r color ,可以得到转移方程:
f i = m i n ( f j + c o s t j + 1 , i )    i f   m i n j + 1 , i = c o l o r f_i=min(f_j+cost_{j+1,i})\ \ if\ min_{j+1,i}=color
f i = m i n ( f j + c o s t j + 1 , i + c o l o r ( i j ) m i n j + 1 , i )    i f   m i n j + 1 , i c o l o r f_i=min(f_j+cost_{j+1,i}+color*(i-j)*min_{j+1,i})\ \ if\ min_{j+1,i}≠color
其中 0 j i 1 0\le j \le i-1
转移方程的由来其实就是选一段区间,先把这段区间改成区间内的最小值,然后再改成目标颜色。
但是这样DP可能会一次性把所有瓶子都改成非目标颜色,因此我们还需要倒着做一次DP,即设 g i g_i 表示后 i i 个瓶子都改成最终颜色的代价。转移类似。
于是全部改成 c o l o r color 的最小花费就是 m i n ( f i 1 + g i + 1 c i = c o l o r ) min(f_{i-1}+g_{i+1}|c_i=color)

跑得很慢的Code:

#include <cstdio>
#include <cstring>
#include <cstdlib>

typedef long long ll;
const int N = 3e2 + 7, MAXC = 1e5 + 7;

ll min(ll a, ll b) { return a < b ? a : b; }

int T;
int n, c[N], buc[MAXC];
ll ans = (1LL << 62), f[N], g[N], mi[N][N], sum[N], cost[N][N];

int main()
{
	freopen("input", "r", stdin);
	//freopen("colour.in", "r", stdin);
	//freopen("colour.out", "w", stdout);
	
	scanf("%d", &T);
	while (T--)
	{
		memset(mi, 0, sizeof(mi));
		memset(sum, 0, sizeof(sum));
		memset(cost, 0, sizeof(cost));
		memset(buc, 0, sizeof(buc));
		ans = (1LL << 62);
		scanf("%d", &n);
		for (int i = 1; i <= n; i++)
		{
			scanf("%d", c + i);
			mi[i][i] = c[i], sum[i] = sum[i - 1] + c[i];
		}
		for (int i = 1; i <= n; i++)
			for (int j = i + 1; j <= n; j++)
				mi[i][j] = min(mi[i][j - 1], c[j]);
		for (int i = 1; i <= n; i++)
			for (int j = i; j <= n; j++)
			{
				ll s = sum[j] - sum[i - 1];
				for (int k = i; k <= j; k++) if (c[k] == mi[i][j]) s -= c[k];
				cost[i][j] = mi[i][j] * s;
			}
		for (int i = 1; i <= n; i++)
			if (!buc[c[i]])
			{
				memset(f, 0x3f, sizeof(f));
				memset(g, 0x3f, sizeof(g));
				f[0] = g[n + 1] = 0;
				for (int j = 1; j <= n; j++)
					for (int k = 0; k <= j - 1; k++)
						f[j] = min(f[j], f[k] + cost[k + 1][j] + (mi[k + 1][j] == c[i] ? 0 : 1ll * c[i] * (j - k) * mi[k + 1][j]));
				for (int j = n; j >= 1; j--)
					for (int k = n + 1; k >= j + 1; k--)
						g[j] = min(g[j], g[k] + cost[j][k - 1] + (mi[j][k - 1] == c[i] ? 0 : 1ll * c[i] * (k - j) * mi[j][k - 1]));
				ll ret = (1LL << 62);
				for (int j = 1; j <= n; j++) if (c[j] == c[i]) ret = min(ret, f[j - 1] + g[j + 1]);
				ans = min(ans, ret);
				buc[c[i]] = 1;
			}
		printf("%lld\n", ans);
	}

	fclose(stdin);
	fclose(stdout);
	return 0;
}

T2

T2
并不能启发人类智慧的Constraints。
Constraints
这题虽然想到了正解,但是因为sb错误没有AC,以后一定要吸取教训。

都说从暴力想,跟着部分分的提示容易想出正解,但是我套路题做得比较多吧,这题就直接想出来了。

首先需要一个性质:
对于任意的 u , v u,v d ( u , v ) d(u,v) 一定是图的最小生成树上的边的边权。

这个比较容易证明,因为要保证图联通首先需要一棵生成树,又为了使最大边最小,就用最小生成树上的边。

然后可以考虑用Kruskal构建最小生成树,新加的每一条边肯定连通了两个连通块。由于边权经过排序,这两个连通块中任意各选两点 u , v u,v d ( u , v ) d(u,v) 一定等于新加这条边的边权,这个很容易想到吧。

Then,如果我们能快速得到两个连通块中满足条件的点对数,这个数*边权就是对答案的贡献。

暴力的思想是用桶维护两个连通块中每个点的 c i c_i ,由于 c i c_i 很大,我们可以考虑用权值线段树维护。依照启发式合并的思想,我们可以枚举较小连通块的每个点(这个用vector<int>记录一下,合并完clear()一下就行了),然后在较大连通块的权值线段树里查询答案。并查集,权值线段树,vector都用启发式合并,复杂度 O ( n l o g n ) O(nlogn)
离散化之后可能常数小一点,我要实锤dh手动吸氧

Code:

#include <vector>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
using namespace std;

typedef long long ll;
const int N = 2e5 + 7, M = 5e5 + 7;
inline int read()
{
	int x = 0, f = 0;
	char c = getchar();
	for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = 1;
	for (; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ '0');
	return f ? -x : x;
}

int n, m, L, c[N], pl[N], rl[N];
int len, arr[N * 3];
struct edge { int from, to, len; } E[M];
ll ans;

int fa[N], size[N];
int getfa(int x) { return fa[x] == x ? x : getfa(fa[x]); }

vector<int> s[N];
int tot, root[N], sum[N * 50], lson[N * 50], rson[N * 50];
void merge(int x, int y)
{
	sum[y] += sum[x];
	if (lson[x] && lson[y]) merge(lson[x], lson[y]);
	else if (lson[x]) lson[y] = lson[x];
	else if (lson[y]) lson[x] = lson[y];
	if (rson[x] && rson[y]) merge(rson[x], rson[y]);
	else if (rson[x]) rson[y] = rson[x];
	else if (rson[y]) rson[x] = rson[y];
}
void insert(int &rt, int l, int r, int po)
{
	if (!rt) rt = ++tot;
	sum[rt]++;
	if (l == r) return;
	int mid = l + r >> 1;
	if (po <= mid) insert(lson[rt], l, mid, po);
	else insert(rson[rt], mid + 1, r, po);
}
int qrysum(int rt, int l, int r, int ql, int qr)
{
	if (ql > qr) return 0;
	if (!rt) return 0;
	if (ql <= l && r <= qr) return sum[rt];
	int mid = l + r >> 1, ret = 0;
	if (ql <= mid) ret += qrysum(lson[rt], l, mid, ql, qr);
	if (mid + 1 <= qr) ret += qrysum(rson[rt], mid + 1, r, ql, qr);
	return ret;
}

int cmp(edge x, edge y) { return x.len < y.len; }

void init()
{
	n = read(), m = read(), L = read();
	for (int i = 1; i <= n; i++) c[i] = read(), arr[++len] = c[i], arr[++len] = c[i] - L, arr[++len] = c[i] + L;
	sort(arr + 1, arr + len + 1);
	len = unique(arr + 1, arr + len + 1) - arr - 1;
	for (int i = 1; i <= n; i++)
	{
		pl[i] = lower_bound(arr + 1, arr + len + 1, c[i] + L) - arr;
		rl[i] = lower_bound(arr + 1, arr + len + 1, c[i] - L) - arr;
		c[i] = lower_bound(arr + 1, arr + len + 1, c[i]) - arr;
	}
	for (int i = 1; i <= m; i++) E[i].from = read(), E[i].to = read(), E[i].len = read();
	for (int i = 1; i <= n; i++) fa[i] = i, size[i] = 1, insert(root[i], 1, len, c[i]), s[i].push_back(i);
	sort(E + 1, E + m + 1, cmp);
}

void solve()
{
	for (int i = 1, cnt = 0; i <= m; i++)
	{
		int u = E[i].from, v = E[i].to;
		u = getfa(u), v = getfa(v);
		if (u == v) continue;
		if (size[u] > size[v]) swap(u, v);
		int siz = s[u].size();
		for (int j = 0; j < siz; j++)
		{
			if (L == 0) ans += sum[root[v]] * 1ll * E[i].len;
			else ans += (qrysum(root[v], 1, len, 1, rl[s[u][j]]) + qrysum(root[v], 1, len, pl[s[u][j]], len)) * 1ll * E[i].len;
			s[v].push_back(s[u][j]);
		}
		s[u].clear();
		fa[u] = v, size[v] += size[u], merge(root[u], root[v]);
		cnt++;
		if (cnt == n - 1) break;
	}
	printf("%lld\n", ans);
}

int main()
{
	//freopen("input", "r", stdin);
	//freopen("output", "w", stdout);
	//freopen("graph.in", "r", stdin);
	//freopen("graph.out", "w", stdout);

	init();
	solve();
	
	fclose(stdin);
	fclose(stdout);
	return 0;
}

T3

这么难不改了。

又是颓废的一天。。。

猜你喜欢

转载自blog.csdn.net/er111er/article/details/83382315
今日推荐