• NOI2018 归程


    传送门
    这道题是一道非常好的Kurskal重构树入门题。

    题目中“两点间存在一条边没有被淹没”等价于“存在一条路径,其最小边权(最低海拔)大于水位线”。有一个很显然的贪心策略,就是每次尝试加入边权大的边,直到两点连通;此时加入的边就是当前两点路径的最小值,也就是“最小边权”的最大值。如果这个最大值都小于等于当前海拔,那么所有两点间的路径一定都会被淹没。

    这个过程等价于构造一棵最大生成树。而第一个使得两点连通的加入边就是我们需要求的“最小边权最大值”。

    如何才能维护这些值呢?事实上,我们可以将这些值都变成一个个节点。当每次往生成树中加入一条边时,我们就将边的端点同时接到一个父节点上,父节点的点权为这条边的边权。

    如果想要维护整棵树的信息,我们就需要在加入一条边的时候,将端点所属的连通块,而不是端点本身,同时接到一个点权为当前边权的父节点上。重复这个过程,我们就可以得到一棵新的树,它叫做“Kruskal重构树”。在这棵树上,两个叶子节点的(LCA)就是两点之间最小边权的最大值。

    Kruskal重构树有些奇妙的性质。由于构造的顺序特殊,这棵树是一个标准的二叉堆。

    回看这道题。我们先以海拔为边权,构造出重构树。在重构树上,如果一个点的点权(w(x)>p)(当前水位线),那么以(x)为根的子树内,任意两点都可以开车来往。这是因为对子树内任意两点的(LCA)一定还是在子树内,而根据二叉堆性质,(w(LCA)>p)显然成立。我们先找到满足(w(x)>p)(w( ext{father}(x)) leq p)(其中初始化(w( ext{叶子})=+infty,w(0)=0),诸如此类),则子树内叶子节点到(1)的最短路径就是答案。可以用树上倍增来找到这个点。

    综上,我们总结一下算法的全过程:

    1. 准备三套储存边的结构体,分别储存路径,海拔和树边。
    2. 用Dijkstra预处理出各点到首都(1)的最短路径dis[i]
    3. 从大到小排序海拔边,构造出Kruskal重构树。
    4. 用DFS处理出以各个点为子树,子树内叶子节点到首都(1)的最短路径subtreeDis[i],同时倍增处理出每个点的(2^k)辈祖先F[i][k]
    5. 每次询问从出发点u开始,倍增地找到最后一个满足w[x]>p的祖先xsubtreeDis[x]即为所求。
    #include <cstdio>
    #include <cctype>
    #include <cstring>
    #include <queue>
    #include <utility>
    #include <algorithm>
    using namespace std;
    #define rg register
    #define fre(z) freopen(z".in", "r", stdin), freopen(z".out", "w", stdout)
    #define customize template<class type> inline
    typedef long long number;
    const number INF = 0x3f3f3f3f3f3f3f3f;
    customize type read(type sample)
    {
    	type ret = 0, sign = 1; char ch = getchar();
    	while(! isdigit(ch))
    		sign = ch == '-' ? -1 : 1, ch = getchar();
    	while(isdigit(ch))
    		ret = ret * 10 + ch - '0', ch = getchar();
    	return sign == -1 ? -ret : ret;
    }
    
    const int MAXN = 400010;
    const int MAXM = 800010;
    
    customize void checkMin(type &var, type value)
    {
    	var = value < var ? value : var;
    }
    
    int N, M;
    int father[MAXN << 1];
    int head[MAXN];
    struct Edge{
    	int next;
    	int front, to;
    	number val;
    }edge[MAXM]; int tot = 0;
    inline void append(int front, int to, number val)
    {
    	++ tot;
    	edge[tot] = (Edge){head[front], front, to, val};
    	head[front] = tot;
    }
    inline void connect(int front, int to, number val)
    {
    	append(front, to, val);
    	append(to, front, val);
    }
    
    number dis[MAXN << 1];
    bool used[MAXN];
    priority_queue< pair<number, int> > heap;
    inline void initDis()
    {
    	memset(dis, 0x3f, sizeof(dis));
    	memset(used, false, sizeof(used));
    	while(! heap.empty())
    		heap.pop();
    	dis[1] = 0;
    	heap.push(make_pair(0, 1));
    	while(! heap.empty())
    	{
    		int cur = heap.top().second; heap.pop();
    		if(used[cur])
    			continue;
    		used[cur] = true;
    		for(rg int e = head[cur]; e; e = edge[e].next)
    		{
    			int to = edge[e].to;
    			if(dis[to] > dis[cur] + edge[e].val)
    			{
    				dis[to] = dis[cur] + edge[e].val;
    				heap.push(make_pair(-dis[to], to));
    			}
    		}
    	}
    }
    
    struct SimpleEdge{
    	int front, to;
    	number val;
    }level[MAXM << 1];
    int totLevel;
    int headLevel[MAXN << 1];
    Edge tree[MAXN << 1];
    int next[MAXN << 1];
    int nodeSize = 0, treeSize = 0;
    number weight[MAXN << 1];
    inline int getNext(int cur)
    {
    	if(next[cur] == cur)
    		return cur;
    	return next[cur] = getNext(next[cur]);
    }
    inline void init()
    {
    	for(rg int i = 1; i <= (N << 1); ++ i)
    		next[i] = i;
    }
    bool cmpByVal(SimpleEdge a, SimpleEdge b)
    {
    	return a.val > b.val;
    }
    inline void appendTree(int front, int to)
    {
    	++ treeSize;
    	tree[treeSize] = (Edge){headLevel[front], front, to, 0};
    	headLevel[front] = treeSize;
    }
    inline void attach(int front, int to)
    {
    	appendTree(front, to);
    	appendTree(to, front);
    }
    
    inline void KruskalReconstruct()
    {
    	memset(weight, 0, sizeof(weight));
    	sort(level + 1, level + totLevel + 1, cmpByVal);
    	nodeSize = N;
    	treeSize = 0;
    	for(rg int i = 1; i <= totLevel; ++ i)
    	{
    		int topNodeLt = getNext(level[i].front);
    		int topNodeRt = getNext(level[i].to);
    		if(topNodeLt != topNodeRt)
    		{
    			attach(++ nodeSize, topNodeLt);
    			attach(nodeSize, topNodeRt);
    			next[topNodeLt] = next[topNodeRt] = nodeSize;
    			weight[nodeSize] = level[i].val;
    			weight[level[i].front] = weight[level[i].to] = INF;
    		}
    	}
    }
    
    number subtreeDis[MAXN];
    int multiFather[MAXN << 1][25];
    
    inline void initSubtreeParameter(int cur, int father)//start from nodeSize
    {
    	subtreeDis[cur] = dis[cur];
    	multiFather[cur][0] = father;
    	for(rg int k = 1; k <= 20; ++ k)
    		multiFather[cur][k] = multiFather[multiFather[cur][k - 1]][k - 1];
    	for(rg int e = headLevel[cur]; e; e = tree[e].next)
    	{
    		int to = tree[e].to;
    		if(to == father)
    			continue;
    		initSubtreeParameter(to, cur);
    		checkMin(subtreeDis[cur], subtreeDis[to]);
    	}
    }
    
    inline number getQuery(int u, int curLevel)
    {
    	int subtreeRoot = u;
    	for(rg int k = 20; k >= 0; -- k)
    	{
    		if(weight[multiFather[subtreeRoot][k]] > curLevel)
    			subtreeRoot = multiFather[subtreeRoot][k];
    	}
    	return subtreeDis[subtreeRoot];
    }
    
    int main()
    {
    	//fre("P4768");
    	int T = read(1);
    	while(T --)
    	{
    		memset(head, 0, sizeof(head));
    		memset(headLevel, 0, sizeof(headLevel));
    		N = read(1); M = read(1);
    		tot = 0; totLevel = 0;
    		nodeSize = 0; treeSize = 0;
    		for(rg int i = 1; i <= M; ++ i)
    		{
    			int u = read(1), v = read(1);
    			number l = read(1ll), a = read(1ll);
    			connect(u, v, l);
    			++ totLevel;
    			level[totLevel].front = u;
    			level[totLevel].to = v;
    			level[totLevel].val = a;
    		}
    		int Q = read(1), K = read(1);
    		number S = read(1), ans = 0;
    		initDis();
    		init();
    		KruskalReconstruct();
    		initSubtreeParameter(nodeSize, 0);
    		for(rg int i = 1; i <= Q; ++ i)
    		{
    			int u = read(1);
    			number p = read(1ll);
    			u = (u + K * ans - 1) % N + 1;
    			p = (p + K * ans) % (S + 1);
    			ans = getQuery(u, p);
    			printf("%lld
    ", ans);
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    1,300萬像素Xperia TX K.O.相機 東方日報
    信息检索Reading List
    雷军:小米二为何不用1300万像素相机_TechWeb
    1300万像素高清双核旗舰 索尼LT30p评测_手机_科技时代_新浪网
    Darts: DoubleARray Trie System海 的 声音我的搜狐
    说说底层架构之实体类的设计
    不忘本~枚举
    两种底层数据层操作时的架构方式,你喜欢那种?
    说说C#中的global
    JS对外部文件的加载及对IFRMAME的加载的实现,当加载完成后,指定指向方法(方法回调)
  • 原文地址:https://www.cnblogs.com/LinearODE/p/11337652.html
Copyright © 2020-2023  润新知