P4971 断罪者

题目描述

人们的罪恶值\(E\)由人们生前所做过的事和他的死亡方式来决定。他们做过的坏事都会有一个罪恶值,这些坏事有可能会并入同一个集合,一个集合的罪恶值为该集合中罪恶值最大的坏事的罪恶值,而他们一生做过的事会互相影响,我们将他们生前做过的事分为4种,而最后的罪恶值\(E\)由其中所有集合的罪恶值的和决定。

    1.做坏事——有罪恶值,单独为一集合。  

    2.做好事——将一件坏事的罪恶值清零。

    3.忏悔——将指定集合中,最大罪恶值的事罪恶值减少。

    4.认清自己——将两个坏事集合合并。

而死亡方式可分为 自然死亡事故死亡自杀

    1.自然死亡,没什么影响。

    2.事故死亡,可以免除最大罪恶的坏事集合。

    3.自杀,最大的坏事集合罪恶值翻倍。

输入格式

第一行三个输入 \(T\) \(W\) \(K\),代表有 \(T\) 个人等待断罪,\(W\) 为死亡方式,与描述序号对应,\(K\) 含义见输出格式。

接下来的\(T\)组数据, 每组数据第一行两个输入 \(n\) \(m\),代表他做过的坏事数量和其他事的数量。

第二行\(n\)个输入,代表每件坏事的罪恶值。

\(3\)到第\(m+2\)行,每行有三种输入可能。(请联系题目描述进行理解)
\(2\) \(A\) 表示做好事,将坏事 \(A\) 罪恶值清零
\(3\) \(A\) \(B\) 表示忏悔,指定集合为 \(A\) 所在的集合,最大罪恶值的事减少 \(B\),若最大罪恶值比 \(B\) 小,则最大罪恶值的事罪恶值清零对于罪恶值相等的坏事,认为编号更小的更坏。
\(4\) \(A\) \(B\) 表示认清自己,将 \(B\) 所在集合与 \(A\) 所在的集合合并。

输出格式

对于每一个人,一行输出

若他的罪恶值为 \(0\) 则输出 \(Gensokyo\)

若他的罪恶值大于 \(K\) 则输出 \(Hell\)

否则输出 \(Heaven\)

再输出它的罪恶值

样例 #1

样例输入 #1

1 1 10
5 2
1 2 3 4 5
2 3
4 2 4

样例输出 #1

Heaven 10

样例 #2

样例输入 #2

2 2 8
5 4
4 8 7 5 6
4 2 4
2 2
4 2 3
3 3 2
3 2
5 1 2
2 2
3 3 2

样例输出 #2

Hell 9
Gensokyo 0

样例 #3

样例输入 #3

2 1 15
5 4
1 2 3 4 5
4 2 3
3 2 100
4 1 4
4 4 1
5 4
1 2 3 4 5
3 2 15
4 2 3
4 1 4
4 3 4

样例输出 #3

Heaven 11
Heaven 9

提示

样例解释与说明

样例一

一开始有五件坏事,罪恶值分别为 \(1.2.3.4.5\),做好事之后,罪恶值分别为 \(1.2.0.4.5\),认清自我后,只剩下四个集合,罪恶值分别是 \(1.4.0.5\),由于是自然死亡,所以最后的罪恶值 \(E=1+4+5=10 \le K \&\& E!=0\),因此输出 \(Heaven\)

样例二

对于样例2的第一组输入如下图,黑色椭圆代表一个集合,红色为罪恶值,下面为点的编号,由于是事故死亡,可以免去标号5的最大值,故罪恶值为\(E=4+5\)

说明

所有数据均在长整型范围内,对于所有数据,均有\(m\le n\),\(1\le K\),保证输入不存在负数。
由于读入数据可能会很大,建议使用较快的读入。

约定① 对于合并两个集合的操作,至少有一个集合只有一件坏事
约定② 这群人不会做好事

测试点编号 T n 时限 约定
1 \(\le10\) \(\le100\) \(1s\) ①②
2 \(\le10\) \(\le300\) \(1s\)
3 \(\le10\) \(\le500\) \(1s\)
4 \(\le20\) \(\le1000\) \(1s\) ①②
5 \(\le20\) \(\le3000\) \(1s\)
6 \(\le20\) \(\le7000\) \(1s\)
7 \(\le30\) \(\le10000\) \(1s\) ①②
8 \(\le30\) \(\le30000\) \(1s\)
9 \(\le30\) \(\le50000\) \(1s\)
10 \(\le30\) \(\le70000\) \(1s\) ①②
11 \(\le10\) \(\le100000\) \(1s\)
12 \(\le10\) \(\le150000\) \(1s\)
13 \(\le10\) \(\le200000\) \(1s\) ①②
14 \(\le10\) \(\le500000\) \(1s\)
15 \(\le10\) \(\le1000000\) \(2s\)
16 \(\le10\) \(\le1000000\) \(2s\) ①②
17 \(\le10\) \(\le1000000\) \(2s\)
18 \(\le10\) \(\le2000000\) \(2s\)
19 \(\le10\) \(\le2000000\) \(2s\)
20 \(\le10\) \(\le2000000\) \(2s\)
21 \(1\) \(\le2000000\) \(2s\) 路径压缩

分析

集合,最大值,合并等关键词,可以想到用左偏树来维护。

对于修改任一节点,将其左右儿子合并,再与原左右儿子所在的堆合并即可。

注意修改时需要自底向上更新 \(dist\),不满足左偏树性质时则交换左右节点。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
int T,n,m,w,k,opt,a,b,sum,maxx;
bool vis[2000005];
struct node{
	int dist,ch[2],val,id,fa;
	bool operator<(node &x)const{return val==x.val?id>=x.id:val<x.val;}//重载运算符 
}t[2000005];
int find(int x){return t[x].fa==x?x:t[x].fa=find(t[x].fa);}//用路径压缩的方法把找到当前堆根的时间复杂度降到 O(nlogn) 
int& rs(int x){return t[x].ch[t[t[x].ch[0]].dist<t[t[x].ch[1]].dist];}//返回右儿子节点
void pushup(int x){
	if(!x)return;
	if(t[x].dist^(t[rs(x)].dist+1))t[x].dist=t[rs(x)].dist+1,pushup(t[x].fa);//自底向上更新dist 
}
int merge(int x,int y){
	if((!x)||(!y))return x|y;
	if(t[x]<t[y])swap(x,y);
	t[rs(x)=merge(rs(x),y)].fa=x;//基础合并操作 
	pushup(x);return x;//更新dist 
}
signed main(){
	cin>>T>>w>>k;
	for(int i=1;i<=T;i++){
		memset(vis,0,sizeof vis);
		cin>>n>>m;sum=maxx=0; //注意初始化!!! 
		t[0].dist=t[0].ch[0]=t[0].ch[1]=t[0].val=t[0].id=t[0].fa=0;
		for(int j=1;j<=n;j++)cin>>t[j].val,t[j].id=t[j].fa=j,t[j].ch[0]=t[j].ch[1]=t[j].dist=0;
		for(int j=1;j<=m;j++){
			cin>>opt>>a;
			if(opt==2){
				int q=0;
				t[a].val=0;
				q=t[t[a].ch[0]].fa=t[t[a].ch[1]].fa=merge(t[a].ch[0],t[a].ch[1]);
				q=find(q);
				t[a].ch[0]=t[a].ch[1]=t[a].dist=0;
				a=find(a);
				t[a].fa=t[q].fa=merge(q,a);
			}
			if(opt==3){
				cin>>b;a=find(a);
				int q=0;
				t[a].val=max(t[a].val-b,0ll);
				q=t[t[a].ch[0]].fa=t[t[a].ch[1]].fa=merge(t[a].ch[0],t[a].ch[1]);
				q=find(q);
				t[a].ch[0]=t[a].ch[1]=t[a].dist=0;
				a=find(a);
				t[a].fa=t[q].fa=merge(q,a);
			}
			if(opt==4){
				cin>>b;a=find(a),b=find(b);
				if(a^b)t[a].fa=t[b].fa=merge(a,b);
			}
		}
		for(int j=1;j<=n;j++){//查询直接模拟即可 
			int x=find(j);
			if(!vis[x]){
				vis[x]=1;
				maxx=max(maxx,t[x].val),sum+=t[x].val;
			}
		}
		if(w==2)sum-=maxx;
		if(w==3)sum+=maxx;
		if(!sum)puts("Gensokyo 0");
		else if(sum<=k)printf("Heaven %lld\n",sum);
		else printf("Hell %lld\n",sum);
	}
	return 0;
}

几个坑点

我全都踩过啊qwq

  1. 多测注意初始化!

  2. 注意修改值之后记得与原左右儿子的堆合并,并不是把它永久删除!

  3. 我的 merge 函数中没有找 \(x,y\) 的堆的根节点,所以注意要保证合并时 \(x,y\) 已经是堆的根节点(即在 merge 之前 find,详情见代码)!

  4. 注意是值相同时,编号越小的越“大”,所以重载运算符时需要搞清楚符号!

posted @ 2023-06-25 13:39  alex_liu09  阅读(28)  评论(0)    收藏  举报