2020.02.03【NOIP提高组】模拟A 组 总结

额,感觉不太好。。。

估分:\(100 + 0 + 60 = 160\)
考场:\(100 + 0 + 60 = 160\)

\(T1\)

很实际的一道题。
设第\(m\)条路连接\(u\)\(v\)
很容易想到用最短路求出\(1\)\(u\)\(v\)的距离,以及\(n\)\(u\)\(v\)的距离。
记录一下\(1\)直接到\(n\)的距离。
我们对于每个询问,求个\(min\)即可。

\(T2\)

一开始认为重心一定是那个点,但之后发现好像不太对。
因为有这样一个情况:
1——2
1——3
1——4
2——5
2——6
5——7
然后发现其实只需要4步。。。额好像重心也没错。。。
我晕了——————————————————————————————————————
啊啊啊,现在想来当时是一头撞进死胡同里了。。。。。。
我们设\(f[i]\)表示搞定子树\(i\)所需的最小花费时间。
\(i\)这个子树里面,一定是先走时间花费最多的那个子树,再走次多的。
也就是说\(f[i]=max(f[j]+k)\)\(k\)表示\(j\)子树在\(i\)的儿子中时间花费的排名。
如此我们们可以得到暴力\(O(n^2logn)\)算法,就是对于每个节点都暴力求。

\(solution1\)

然后呢,我们可以实施换根操作,发现换根时只有当前根节点以及将要转到的点的\(f\)值会变。
这样我们可以用线段树来对于每个节点维护一下它的子树所有的\(f\),并顺便求出\(f[j]+k\)的值。
这样我们在换根的时候只需要单点修改顺便得到答案\(ma[rt[i]]\)就可以了。

\(code\)

#include <cstdio>
#include <algorithm>
#define N 200010
#define mem(x, a) memset(x, a, sizeof x)
#define mpy(x, y) memcpy(x, y, sizeof y)
#define fo(x, a, b) for (int x = (a); x <= (b); x++)
#define fd(x, a, b) for (int x = (a); x >= (b); x--)
#define go(x) for (int p = tail[x], v; p; p = e[p].fr)
using namespace std;
struct node{int v, fr;}e[N << 1];
int n, tail[N], cnt = 0, tot = 0, f[N], rt[N];
int t[N * 80], ls[N * 80], rs[N * 80], ma[N * 80];
int ans = N, aw[N];

inline int read()
{
	int x = 0, f = 0; char c = getchar();
	while (c < '0' || c > '9') f = (c == '-') ? 1 : f, c = getchar();
	while (c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
	return f ? -x : x;
}

inline void add(int u, int v) {e[++cnt] = (node){v, tail[u]}; tail[u] = cnt;}

void insert(int &x, int l, int r, int f)
{
	if (! x) x = ++tot; t[x]++;
	if (l == r) {ma[x] = l + t[x]; return;}
	int mid = (l + r) >> 1;
	if (f <= mid) insert(ls[x], l, mid, f);
	else insert(rs[x], mid + 1, r, f);
	ma[x] = max(ma[rs[x]], ma[ls[x]] + t[rs[x]]);
}

void del(int x, int l, int r, int f)
{
	if (! x) return; t[x]--;
	if (l == r) {ma[x] = t[x] ? l + t[x] : 0; return;}
	int mid = (l + r) >> 1;
	if (f <= mid) del(ls[x], l, mid, f);
	else del(rs[x], mid + 1, r, f);
	ma[x] = max(ma[rs[x]], ma[ls[x]] + t[rs[x]]);
}

void dfs(int x, int fa)
{
	go(x)
	{
		v = e[p].v;
		if (v == fa) continue; dfs(v, x);
		insert(rt[x], 0, n, f[v]);
		f[x] = ma[rt[x]];
	}
}

void huangen(int x, int fa)
{
	aw[x] = f[x], ans = min(ans, aw[x]);
	go(x)
	{
		v = e[p].v;
		if (v == fa) continue;
		del(rt[x], 0, n, f[v]);
		f[x] = ma[rt[x]];
		insert(rt[v], 0, n, f[x]);
		insert(rt[x], 0, n, f[v]);
		f[v] = ma[rt[v]];
		huangen(v, x);
	}
}

int main()
{
	freopen("news.in", "r", stdin);
	freopen("news.out", "w", stdout);
	n = read();
	for (int i = 2, x; i <= n; i++)
		x = read(), add(i, x), add(x, i);
	dfs(1, 0);
//	fo(i, 1, n) printf("%d ", f[i]); puts("");
	huangen(1, 0);
	printf("%d\n", ans + 1);
	fo(i, 1, n)
		if (aw[i] == ans) printf("%d ", i);
	return 0;
}

\(T3\)

这道题其实很容易就能转化题意。
我们先暴力求出\(T\)数组。
然后从值为\(1\)\(n*m\)依次搞,判断一下这个点是否可以在前面的点都走到的情况下走到。
我们发现,当一个点必须要走到的时候,那么它左下角和右上角都不能有点。
所以我们可以用个二维树状数组来维护一下。(然鹅发现空间不太够。。。)
然后就开了个\(short\) \(int\)来蒙混过关了。。。(\(TLE60\),其实也没过关)
结果发现好像不需要怎么麻烦。。。。。。
有两种方法都可以搞定。

\(solution1\)

我们对于每一行得到一个区间\([l,r]\),然后我们每次取完一个以后就可以\(O(n)\)修改这个区间。
一共只需要取\(n+m-1\)次,所以总时间复杂度就是\(O((n+m-1)*n)\)不会超!!!

\(solution2\)

我们还可以直接打标记。
选了这个点以后,将其左上角和右下角巧妙地暴力打上标记。
因为我们一打标记都肯定是打到边界的所以我们看到当前打了就可以跳过这一行了。
这样时间应该是可以过的,大概可能是\(O(n^2)\)乘个常数。

总结

这次一开始上来看到\(T1\)就开打了,时间分配勉强过得去。
然鹅好像有很多时间都浪费了(包括和弟弟玩,胡思乱想)
想什么都要想仔细一点,不要只思考于肤浅之间。
要算好一个算法的时间复杂度,不要与正解失之交臂。

posted @ 2020-02-03 17:52  jz929  阅读(98)  评论(0编辑  收藏  举报