zoukankan      html  css  js  c++  java
  • 【洛谷P5064】等这场战争结束之后

    题目

    题目链接:https://www.luogu.com.cn/problem/P5064
    给你一个图,每个点有点权,最开始没有边。
    有一些操作:

    1. 添加一条 (x)(y) 之间的双向边。
    2. 回到第 (x) 次操作后的状态(注意这里的 (x) 可以是 (0),即回到初始状态)。
    3. 查询 (x) 所在联通块能到的点中点权第 (y) 小的值,如果不存在,那么输出 (-1)

    (n,mleq 10^5;a_ileq 10^9)

    思路

    什么叫做极限卡空间啊(战术后仰
    首先这个历史版本很烦,考虑怎么搞掉这个玩意。我们可以建出操作树,那么对于任意一个操作树上的节点 (x),从根节点到 (x) 的所有操作就是它要依次进行的操作。
    考虑按照 dfs 序处理询问,那么我们就需要支持加边删边,然后询问连通块 (k) 小值。
    加边删边肯定采用按秩合并的并查集,那么询问 (k) 小值一般只有二分和值域分块两种做法,显然前者是不行的。
    先把点权离散化,考虑值域分块,其中每一个块的大小为 (T)。对于原图中的任意一个点,我们找到它在并查集中的根节点 (x),记 ( ext{cnt}[x][i]) 表示这个连通块,值域在 ([iT,(i+1)T)) 的点的数量。
    操作一就直接把两个连通块按秩合并,操作三的话就先枚举答案在哪一个块,确定块之后再依次枚举块中的所有数字暴力判断。
    不算并查集的复杂度的话,那么时间复杂度 (O(mT)),空间复杂度 (O(frac{n^2}{T}))。考虑到空间只有 (20 ext{MB}),所以我们 (T) 必须取大一些。
    发现提示中 lxl 的神仙做法复杂度是 (O(frac{nm}{omega})) 的,所以其实 (T)(64) 左右就是可以接受的。所以直接不管时间,卡到 (T=40) 的时候空间就可以接受了。虽然按秩合并并查集理论上是 (O(log n)) 的,但是跑起来还挺快的(

    代码

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N=100010,M=40;
    int n,m,T,tot,head[N],ans[N],siz[N],father[N],opt[N],X[N],Y[N];
    short cnt[M][N];
    
    struct edge
    {
    	int next,to;
    }e[N];
    
    struct node
    {
    	int v,id;
    }a[N];
    
    bool cmp(node x,node y)
    {
    	return x.v<y.v;
    }
    
    void add(int from,int to)
    {
    	e[++tot]=(edge){head[from],to};
    	head[from]=tot;
    }
    
    inline int read()
    {
    	int d=0; char ch=getchar();
    	while (!isdigit(ch)) ch=getchar();
    	while (isdigit(ch)) d=(d<<3)+(d<<1)+ch-48,ch=getchar();
    	return d;
    }
    
    int find(int x)
    {
    	return x==father[x]?x:find(father[x]);
    }
    
    inline void merge(int &x,int &y)
    {
    	if (siz[x]<siz[y]) swap(x,y);
    	father[y]=x; siz[x]+=siz[y];
    	for (int i=0;i<M;i++) cnt[i][x]+=cnt[i][y];
    }
    
    inline void divide(int x,int y)
    {
    	father[y]=y; siz[x]-=siz[y];
    	for (int i=0;i<M;i++) cnt[i][x]-=cnt[i][y];
    }
    
    void dfs(int x)
    {
    	bool flag=0;
    	if (opt[x]==1)
    	{
    		X[x]=find(X[x]),Y[x]=find(Y[x]);
    		if (X[x]!=Y[x]) merge(X[x],Y[x]),flag=1;
    	}
    	if (opt[x]==3)
    	{
    		int p=find(X[x]);
    		if (siz[p]<Y[x]) ans[x]=-1;
    		else
    		{
    			int i=0;
    			while (Y[x]>cnt[i][p])
    				Y[x]-=cnt[i][p],i++;
    			i*=T; 
    			do {
    				if (find(a[i].id)==p) Y[x]--;
    				if (!Y[x]) ans[x]=i;
    				i++;
    			} while (Y[x]);
    		}
    	}
    	for (int i=head[x];~i;i=e[i].next)
    		dfs(e[i].to);
    	if (flag) divide(X[x],Y[x]);
    }
    
    int main()
    {
    	memset(head,-1,sizeof(head));
    	n=read(); m=read(); T=(n-1)/M+1;
    	for (int i=1;i<=n;i++) a[i]=(node){read(),i};
    	sort(a+1,a+1+n,cmp);
    	for (int i=1;i<=n;i++)
    		father[i]=i,siz[i]=1,cnt[i/T][a[i].id]=1;
    	tot=0;
    	for (int i=1;i<=m;i++)
    	{
    		opt[i]=read(); X[i]=read();
    		if (opt[i]==2) add(X[i],i);
    		if (opt[i]!=2) Y[i]=read(),add(i-1,i);
    	}
    	dfs(0);
    	for (int i=1;i<=m;i++)
    		if (opt[i]==3)
    			printf("%d
    ",ans[i]==-1 ? -1 : a[ans[i]].v);
    	return 0;
    }
    
  • 相关阅读:
    (转 )Unity对Lua的编辑器拓展
    unity timeline
    unity拖尾粒子问题
    unity shader 波动圈
    linux教程
    Unity Shader 基础
    ugui拖拽
    unity shader 热扭曲 (屏幕后处理)
    英文取名神器
    lua正则表达式替换字符串
  • 原文地址:https://www.cnblogs.com/stoorz/p/14773933.html
Copyright © 2011-2022 走看看