zoukankan      html  css  js  c++  java
  • @uoj


    @description@

    小M在玩一个即时战略(Real Time Strategy)游戏。不同于大多数同类游戏,这个游戏的地图是树形的。也就是说,地图可以用一个由 n 个结点,n−1 条边构成的连通图来表示。这些结点被编号为 1 ~ n。

    每个结点有两种可能的状态:“已知的”或“未知的”。游戏开始时,只有 1 号结点是已知的。在游戏的过程中,小M可以尝试探索更多的结点。具体来说,小M每次操作时需要选择一个已知的结点 x,和一个不同于 x 的任意结点 y(结点 y 可以是未知的)。然后游戏的自动寻路系统会给出 x 到 y 的最短路径上的第二个结点 z,也就是从 x 走到 y 的最短路径上与 x 相邻的结点。此时,如果结点 z 是未知的,小M会将它标记为已知的。

    这个游戏的目标是:利用至多 T 次探索操作,让所有结点的状态都成为已知的。然而小M还是这个游戏的新手,她希望得到你的帮助。

    为了让游戏过程更加容易,小M给你提供了这个游戏的交互库,具体见【任务描述】和【实现细节】。

    另外,小M也提供了一些游戏的提示,具体见题目的最后一节【提示】。

    任务介绍
    你需要实现一个函数 play,以帮助小M完成游戏的目标。

    play(n, T, dataType)
    n 为树的结点个数;
    T 为探索操作的次数限制;
    dataType 为该测试点的数据类型,具体见【数据规模和约定】。

    在每个测试点中,交互库都会调用恰好一次 play 函数。该函数被调用之前,游戏处于刚开始的状态。

    你可以调用函数 explore 来帮助你在游戏中探索更多结点,但是这个函数的调用次数不能超过 T 次。

    explore(x, y)
    x 为一个已知的结点;
    y 为一个不同于 x 的任意结点(可以不是已知的结点);
    这个函数会返回结点 x 到 y 的最短路径上的第二个结点的编号。

    在函数 play 返回之后,交互库会检查游戏的状态:只有当每个结点都是已知的,才算游戏的目标完成。

    提示
    这里是小M给你的一些贴心的提示:

    (1)图(无向图)由结点和边构成,边是结点的无序对,用来描述结点之间的相互关系
    (2)路径是一个结点的非空序列,使得序列中相邻两个结点之间都有边相连
    (3)两个结点是连通的,当且仅当存在一条以其中一个结点开始、另一个结点结束的路径
    (4)一个图是连通的,当且仅当这个图上的每对结点都是连通的
    (5)一棵 n 个结点的树,是一个由 n 个结点,n−1 条边构成的连通图
    (6)两个结点的最短路径,是指连接两个结点的所有可能的路径中,序列长度最小的
    (7)在一棵树中,连接任意两个结点的最短路径,都是唯一的
    (8)通过访问输入输出文件、攻击评测系统或攻击评测库等方式得分属于作弊行为,所得分数无效。

    限制与约定
    一共有 20 个测试点,每个测试点 5 分。

    对于所有测试点,以及对于所有样例,2≤n≤3×10^5,1≤T≤5×10^6,1≤dataType≤3。 不同 dataType 对应的数据类型如下:

    对于 dataType=1 的测试点,没有特殊限制。
    对于 dataType=2 的测试点,游戏的地图是一棵以结点 1 为根的完全二叉树, 即,存在一个 1 ~ n 的排列 a,满足 a1=1,且结点 ai (1<i≤n) 与结点 a⌊i/2⌋ 之间有一条边相连。
    对于 dataType=3 的测试点,游戏的地图是一条链, 即,存在一个 1 ~ n 的排列 a,满足结点 ai (1<i≤n) 与结点 ai−1 之间有一条边相连。

    对于每个测试点,n,T,dataType 的取值如下表:

    测试点编号nTdataType
    12100001
    2310000
    31010000
    410010000
    51000100002
    620000300000
    72500005000000
    81000200003
    9500015500
    103000063000
    11150000165000
    12250000250100
    13300000300020
    141000500001
    155000200000
    1630000900000
    171500003750000
    182000004400000
    192500005000000
    203000005000000

    @solution@

    第一道自己切出来的 WC 题,开心~
    (然而还是调试了非常久)

    首先明显发现数据大概分为四个板块:

    第一板块:n^2 <= T。每次 O(n) 暴力地找出一个新点,就可以 O(n^2) 找出所有点。

    第二板块:dataType = 2,T 大概是 O(nlogn) 的范围。
    采用分治,每次把点集分到当前块的左右子树。递归即可。
    根据完全二叉树性质,深度 <= log,直接搞即可。

    第三板块:dataType = 3,T 大概是 n + logn 的范围。
    如果是正常的做法,我们对于每个点,需要判断是在当前已探明的这条链的左边还是右边。
    也就是说最坏情况我们需要 2*n 次操作才能探明整条链。

    (但是经过今年 NOI 的交互题,我学聪明了!不会做就随机化!)
    假如当前已探明的这条链为 [l, r],不妨设 y 在 l 的左边,则我们可以直接探清 y ~ l 的路径上的所有点,而不需要错误试探。
    假如我们的 y 是随机选取的,那么 y ~ l 这条路径上的点数期望最坏为 n/4(n 为当前未探明的点数)。
    于是我们期望试探 log 次就可以探明整条链。

    第四板块:dataType = 1,T 大概是 O(nlogn) 的范围。
    怎么利用这 log 呢?我们不妨猜测是找到一个点需要 log 次。于是联想到分治。
    假如只插一个点,我们可以使用点分治。每次 explore 要么告诉新插入的点在哪一颗子树,要么告诉新插入的点连向重心。
    那么现在插入多个点怎么办?于是就可以动态维护点分树。用替罪羊的思想,设置一个平衡因子 α。如果一棵子树的占比 > α 不平衡了就暴力拍扁重构。

    插入一个点,就直接接到它相邻的点的下面。
    然后从这个点开始往上爬,找到最高的那个不平衡的结点 x,重构 x 所在的子树。

    实现的时候有一个小细节:点分树的结构适合自下而上爬,而不适合自上而下找。
    即每一次 explore 找到的是与重心相邻的点,而不是它的下一层重心。
    这个时候怎么办呢?由于点分树高度不是很高,于是你暴力在点分树上爬,爬到那一层就好了。

    @accepted code@

    #include "rts.h"
    #include <vector>
    #include <cstdlib>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const double B = 0.78;
    const int MAXN = 300000;
    int vis[MAXN + 5];
    void solve1(int n) {
    	vis[1] = 1;
    	for(int i=1;i<=n;i++) {
    		int f;
    		for(int j=1;j<=n;j++)
    			if( vis[j] == 1 ) f = j;
    		vis[f] = -1;
    		for(int j=1;j<=n;j++)
    			if( vis[j] == 0 ) {
    				int p = explore(f, j);
    				if( vis[p] == 0 ) vis[p] = 1;
    			}
    	}
    }
    void solve(const vector<int>&vec, int x) {
    	if( vec.empty() ) return ;
    	vector<int>l, r;
    	int a = -1, b = -1;
    	for(int i=0;i<vec.size();i++) {
    		if( vec[i] != x ) {
    			int p = explore(x, vec[i]);
    			if( a == -1 ) a = p;
    			else if( a != p && b == -1 ) b = p;
    			if( p == a ) l.push_back(vec[i]);
    			else r.push_back(vec[i]);
    		}
    	}
    	solve(l, a), solve(r, b);
    }
    void solve2(int n) {
    	vector<int>vec;
    	for(int i=1;i<=n;i++)
    		vec.push_back(i);
    	solve(vec, 1);
    }
    int a[MAXN + 5];
    void solve3(int n) {
    	for(int i=1;i<=n;i++)
    		a[i] = i;
    	random_shuffle(a + 1, a + n + 1);
    	int l = 1, r = 1;
    	vis[1] = true;
    	for(int i=1;i<=n;i++) {
    		if( vis[a[i]] ) continue;
    		int p = explore(l, a[i]);
    		if( vis[p] ) {
    			p = explore(r, a[i]);
    			while( true ) {
    				vis[p] = true;
    				if( p == a[i] ) break;
    				p = explore(p, a[i]);
    			}
    			r = a[i];
    		}
    		else {
    			while( true ) {
    				vis[p] = true;
    				if( p == a[i] ) break;
    				p = explore(p, a[i]);
    			}
    			l = a[i];
    		}
    	}
    }
    struct edge{
    	int to; edge *nxt;
    }edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt=&edges[0];
    int dep[MAXN + 5], fa[MAXN + 5];
    void addedge(int u, int v) {
    	edge *p = (++ecnt);
    	p->to = v, p->nxt = adj[u], adj[u] = p;
    	p = (++ecnt);
    	p->to = u, p->nxt = adj[v], adj[v] = p;
    }
    bool tag[MAXN + 5];
    void get_tag(int x, int d) {
    	tag[x] = true;
    	for(edge *p=adj[x];p;p=p->nxt) {
    		if( dep[p->to] < d || tag[p->to] ) continue;
    		get_tag(p->to, d);
    	}
    }
    int siz[MAXN + 5];
    int get_siz(int x, int f) {
    	siz[x] = 1;
    	for(edge *p=adj[x];p;p=p->nxt) {
    		if( !tag[p->to] || p->to == f ) continue;
    		siz[x] += get_siz(p->to, x);
    	}
    	return siz[x];
    }
    int hvy[MAXN + 5];
    int get_G(int x, int tot, int f) {
    	int ret = -1; hvy[x] = (tot - siz[x]);
    	for(edge *p=adj[x];p;p=p->nxt) {
    		if( !tag[p->to] || p->to == f ) continue;
    		int k = get_G(p->to, tot, x);
    		if( ret == -1 || hvy[k] < hvy[ret] )
    			ret = k;
    		hvy[x] = max(hvy[x], siz[p->to]);
    	}
    	if( ret == -1 || hvy[x] < hvy[ret] )
    		ret = x;
    	return ret;
    }
    void push(const int &x, int y, int f) {
    	for(edge *p=adj[y];p;p=p->nxt) {
    		if( !tag[p->to] || p->to == f ) continue;
    		push(x, p->to, y);
    	}
    }
    int divide(int t, int x, int d) {
    	tag[x] = false, dep[x] = d, siz[x] = t;
    	push(x, x, 0);
    	for(edge *p=adj[x];p;p=p->nxt) {
    		if( !tag[p->to] ) continue;
    		int tot = get_siz(p->to, x);
    		fa[divide(tot, get_G(p->to, tot, x), d + 1)] = x;
    	}
    	return x;
    }
    int rebuild(int x, int d) {
    	get_tag(x, d);
    	int tot = get_siz(x, 0);
    	return divide(tot, get_G(x, tot, 0), d);
    }
    int G;
    void add_edge(int f, int x) {
    	dep[x] = dep[f] + 1, fa[x] = f, addedge(f, x), vis[x] = true;
    	for(int p=x;p;p=fa[p]) siz[p]++;
    	int p = -1, q = x;
    	while( q != G ) {
    		if( siz[q] >= B*siz[fa[q]] )
    			p = fa[q];
    		q = fa[q];
    	}
    	if( p != -1 ) {
    		if( p == G ) fa[G = rebuild(p, 0)] = 0;
    		else {
    			int x = fa[p];
    			fa[rebuild(p, dep[p])] = x;
    		}
    	}
    }
    void insert(int x, int p, int y) {
    	int f = x;
    	while( true ) {
    		add_edge(f, p);
    		if( p == y ) break;
    		f = p, p = explore(p, y);
    	}
    }
    void get(int x) {
    	int nw = G;
    	while( true ) {
    		int p = explore(nw, x);
    		if( vis[p] ) {
    			for(p;fa[p]!=nw;p=fa[p]); nw = p;
    		}
    		else {
    			insert(nw, p, x);
    			break;
    		}
    	}
    }
    void solve4(int n) {
    	for(int i=1;i<=n;i++)
    		a[i] = i;
    	random_shuffle(a + 1, a + n + 1);
    	G = 1, siz[G] = 1, vis[G] = true;
    	for(int i=1;i<=n;i++)
    		if( !vis[a[i]] ) get(a[i]);
    }
    void play(int n, int T, int dataType) {
    	if( n <= 100 ) solve1(n);
    	else if( dataType == 2 ) solve2(n);
    	else if( dataType == 3 ) solve3(n);
    	else solve4(n);
    }
    

    @details@

    调试得好痛苦 QAQ。
    你可以看到在 uoj 上我整整一页的提交 QAQ。
    以后调不过还是直接重构代码,好烦啊 QAQ。

    首先要弄清楚要维护哪些东西。。。比如我就用了最简单的维护:点分树的父亲 fa,管辖的结点数 siz,在点分树中的深度 dep。
    然后,少用 stl(vector 啊,set 啊之类的乱七八糟的东西)!本身数据结构就是卡时间的。
    假如 x 在它父亲 f 中占比太大,应该重构 f 而不是 x!!!
    平衡因子 α 取 0.75 果然还是最好的。当然可以上下微调(不过调到 0.7 就 TLE 了是什么鬼)。

    还有各种各样的错误 QAQ。我感觉自己的代码能力好像不太行了啊 QAQ。

  • 相关阅读:
    (树的直径)第九届湘潭市大学生程序设计比赛 H-Highway
    (记忆化DFS)Codeforces Round #413 D-Field expansion
    (树状数组)Codeforces Round #413 C-Fountains
    (几何)LeetCode Weekly Contest 32 D-Erect the Fence
    LeetCode Weekly Contest 32 解题报告
    (贪心)华师大程序设计竞赛 F-丽娃河的狼人传说
    (最短路)AtCoder Beginner Contest 061 D
    Tinkoff Challenge
    Codeforces Round #410 (Div. 2) 解题报告
    (二叉树)UVA
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11656915.html
Copyright © 2011-2022 走看看