优先队列、并查集2

Task

有n个机器,m个任务。每个机器至多能完成一个任务。对于每个机器,有一个最大运行时间xi和等级yi,对于每个任务,也有一个运行时间xj和等级yj。只有当xi>=xj且yi>=yj的时候,机器i才能完成任务j,并获得500*xj+2*yj金钱。问最多能完成几个任务,当出现多种情况时,输出获得金钱最多的情况。

xi<=1440,yi<=100

1<=N<=100000,1<=M<=100000

我们按x为第一关键字、y为第二关键字从大到小为机器和任务排序。

1、为啥按x为第一关键字、y为第二关键字?

“获得500*xj+2*yj金钱”,以及“xi<=1440,yi<=100”表明,我们应该尽量选x大的来完成(因为即使y差最大值,也大不过x大1)

2、为啥从大到小为机器和任务排序?

举例可以知道,如果大小任务两个只能择其一,那先选大任务明显更优。

登录—专业IT笔试面试备考平台_牛客网

Efficient Solutions

有n个人,每个人有两个属性x、y,如果对于一个人P(x,y),不存在另外一个人(a,b),使得a<x,b<=y或者a<=x,b<y,则这个人是有优势的。

动态插入每个人,要求统计每加入一人后,有优势的人的个数。

先看已经存在的点:有优势的点中,x越小,y必须越大;x越大,y必须越小(保证了不会在另一个点的区间中),而没有优势的点对于有优势的点及之后插入的点不会有任何影响,可以直接去掉;

再看插入的点:若插入一个没有优势的点,可以直接不考虑;若插入一个有优势的点,有可能由于其xy比原来有优势的点都要小,那么这些原来的点失去优势,此时他只需要向右逐一去掉比自己高的点即可。

multiset的写法:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<set>
//scanf("%d",&);
using namespace std;
typedef long long ll;
const int maxn=200005,inf=0x3f3f3f3f;
const double esp=1e-8;
int t,n,x,y;
struct ty{
	int x,y;
	bool operator <(const ty &a)const{
		return (x<a.x||(x==a.x&&y<a.y));//万万不可忽略x==a.x!!!
	}
};
multiset<ty> st;
int main(){
	scanf("%d",&t);
	for(int k=1;k<=t;++k){
		scanf("%d",&n);
		st.clear();
		printf("Case #%d:\n",k);
		for(int i=1;i<=n;++i){
			scanf("%d%d",&x,&y);
			ty p; p.x=x; p.y=y;
			multiset<ty>::iterator it=st.lower_bound(p);//根据ty中定义的来,找到小于等于它的 
			if(it==st.begin()||(--it)->y>p.y){
				st.insert(p);
				it=st.upper_bound(p);
				while(it!=st.end()&&(it)->y>=p.y) st.erase(it++);
			}
			printf("%d\n",st.size()); 
		}
		if(k<t) printf("\n");
	}
	return 0;
}

multimap的写法:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<set>
//scanf("%d",&);
using namespace std;
typedef long long ll;
const int maxn=200005,inf=0x3f3f3f3f;
const double esp=1e-8;
int t,n,x,y;
multimap<int,int> mp;
int main(){
	scanf("%d",&t);
	for(int k=1;k<=t;++k){
		printf("Case #%d:\n",k);
		scanf("%d",&n);
		mp.clear();
		for(int i=1;i<=n;++i){
			scanf("%d%d",&x,&y);
			multimap<int,int>::iterator it=mp.lower_bound(x);
			if(it==mp.begin()||(--it)->second>y){
				mp.insert({x,y});
				it=mp.upper_bound(x);
				while(it!=mp.end()&&(it)->second>=y) mp.erase(it++);
			}
			printf("%d\n",mp.size()); 
		}
		if(k<t) printf("\n");
	}
	return 0;
}

登录—专业IT笔试面试备考平台_牛客网

Defeat the Enemy

每个部队还拥有攻击力攻击力,以及防御力Defensei.我们最多可以用一支部队攻击一个敌人村庄和一支部队只能用来攻击一个敌方村庄。即使一支部队幸存下来攻击,它不能在另一次攻击中再次使用。2个部队之间的战斗非常简单。部队利用他们的攻击力攻击另一支部队同时。如果一个部队的防御力小于或等于另一个部队的防御力攻击力,它会被摧毁。两支部队都有可能幸存或摧毁。我们部落的主要目标是消灭所有敌军。此外,我们的部落希望拥有
大多数部队在这场战争中幸存下来。

肯定是要将敌人按防御力从大到小、攻击力做为第二关键字同样从大到小排序(先消灭敌人,后尽量求存活多的),我方则是以攻击力为第一单位,防御力为第二单位从大到小排序。在寻找过程中,当有多个攻击力大于或等于敌方防御力的时候,若有足够防御力抵挡敌人攻击且防御力与敌人攻击力相差最小就上,否则给敌人一个最脆皮的。

细节问题:可以发现,只要知道我方的攻击力足够击垮敌方,剩下的就是防御力的问题了,也就是说只需要维护一个防御力从小到大的multiset,再用upper_bound找到大于的第一个进行擦除,若迭代器指向尾部说明需要同归于尽(这时直接擦除队首后ans--即可)。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<set>
//scanf("%d",&);
using namespace std;
typedef long long ll;
const int maxn=100005,inf=0x3f3f3f3f;
const double esp=1e-8;
int t,n,m,a,d,ans;
struct enm{
	int a,d;
	bool operator <(const enm &x)const{
		return d<x.d;
	}
}e[maxn];
struct us{
	int a,d;
	bool operator<(const us &x)const{
		return a<x.a;
	}
}u[maxn];
multiset<int>s;
int main(){
	scanf("%d",&t);
	for(int k=1;k<=t;++k){
		printf("Case #%d:\n",k);
		scanf("%d%d",&n,&m);
		s.clear();
		ans=n;
		for(int i=1;i<=n;++i)
			scanf("%d%d",&u[i].a,&u[i].d);
		for(int i=1;i<=m;++i)
			scanf("%d%d",&e[i].a,&e[i].d);
		sort(u+1,u+1+n);
		sort(e+1,e+2+m);
		int j=1;
		for(int i=1;i<=m;++i){
			while(j<=n&&e[i].d<=u[j].a){
				s.insert(u[j].d);
				j++;
			}
			if(s.empty()){
				ans=-1; break;
			}
			multiset<int>::iterator it=s.upper_bound(e[i].a);
			if(it==s.end()) {
				it=s.begin();//所有人防御都比不过对方,此时用一个防御最小的人上场
				ans--;//同归于尽
			}
			s.erase(it);
		}
		printf("%d",ans);
		if(k<t) printf("\n");
	}
	return 0;
}

登录—专业IT笔试面试备考平台_牛客网

Let's Play Curling

数轴上有n个红色石子和m个蓝色石子,对于数轴上任意一个位置c,存在一个红色石子与c的距离比所有的蓝色石子都近则红队多得一分,现在已知所有石子的位置,请找出一个位置c使红队得分最多。

n,m<=10^5; 1<=ai,bi<=10^9

如果有一堆红色石子聚众(其中不含蓝石子),那么最优点肯定是在他们之间且答案为最多的那一堆。值得注意的是,需要考虑当红蓝石子重叠的情况和一堆红色石子重叠的情况。对于前一种情况,我们可以通过遇到蓝色石子则直接清零,对于由此,我们可以先用map记录坐标及对应颜色,然后通过遍历找到最大的红石子堆,他们的数量即是最终得分。

细节问题:如果使用计数排序而数组下标又非常之大或是数据与数据之间的差非常之大,则考虑使用map进行计数(cnt的作用);另外求求了,别忘了重新开始样例的时候容器重新清0啊啊啊。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#include<set>
//scanf("%d",&);
using namespace std;
typedef long long ll;
const int maxn=100005,inf=0x3f3f3f3f;
const double esp=1e-8;
int t,n,m,a,b;
ll ans,sum;
map<int,int>mp,cnt;//
int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d%d",&n,&m);
		ans=sum=0;
		cnt.clear();//
        mp.clear();//
		for(int i=1;i<=n;++i){
			scanf("%d",&a);
			mp[a]=1;
			cnt[a]++;
		}
		for(int i=1;i<=m;++i){
			scanf("%d",&b);
			mp[b]=2;
			cnt[b]=0;
		}
		map<int,int>::iterator it;
		for(it=mp.begin();it!=mp.end();it++){
			if(it->second==2){
				sum=0;
			}else{
				sum+=cnt[it->first];
				ans=max(ans,sum);
			}
		}
		if(ans) printf("%d\n",ans);
		else printf("Impossible\n");
	}
}

并查集

按秩合并:让矮的树向高的树合并,可以保证树的高度不会很高。

路径压缩:让所有点的父亲成为最早的祖先,大大减小树的高度。

以上两个操作的合并可以将并查集操作看做常数级别。

1988 -- Cube Stacking

Farmer John 和 Betsy 正在用 N (1 <= N <= 30,000) 个标为 1 到 N 的相同立方体玩游戏。他们从 N 叠开始,每个叠包含一个立方体。Farmer John 要求 Betsy 执行 P (1<= P <= 100,000) 操作。有两种类型的操作:移动和计数。* 在移动操作中,Farmer John 要求 Bessie 将包含立方体 X 的堆栈移动到包含立方体 Y 的堆栈顶部。 * 在计数操作中,Farmer John 要求 Bessie 计算堆栈中包含立方体 X 的立方体的数量在立方体 X 下并报告该值。编写一个可以验证游戏结果的程序。

 通过令最下面的方块成为祖先节点存放整个栈的深度(这个是为了以后若有另一个栈整体移动到其上的时候,上面栈的祖先节点的深度可以直接等于这个深度),其他节点再用一个数组存放下面的节点数,通过递归将祖先的下方节点数相加即可得到自己下方的节点数,最后再将自己的祖先变为最原始的祖先(即路径压缩)。

细节问题:在求下方节点数时,可能会出现还没来得及合并到最原始祖先而导致少算的情况,所以在求时务必再进行一次getfather。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=30005,inf=0x3f3f3f3f;
const double esp=1e-8;
int t,n,m,a,b,fa[maxn],u[maxn],deep[maxn];
//ll ans,sum;
string s,ss;
inline string rs(){
	string s;
	char c=getchar();
	while(c==' '||c=='\n') c=getchar();
	while(c!=' '&&c!='\n'){
		s+=c; c=getchar();
	}
	return s;
}
int gf(int x){
	if(x!=fa[x]){
		int t=fa[x];//先记录爸爸是谁 
		fa[x]=gf(fa[x]);//让它的爸爸成为最早的祖先 (路径压缩) 
		u[x]+=u[t];//加上原来爸爸的下面方块数 
	}
	return fa[x];
}
void merg(int x,int y){
	int xx=gf(x),yy=gf(y);
	if(xx!=yy){//若没被合并过 
		fa[xx]=yy;//x认y为爸爸 
		u[xx]=deep[yy];//x下面方块数为y的深度 
		deep[yy]+=deep[xx];//y的深度加上x的深度 
		deep[xx]=0;//x变为儿子,不要它的深度了 
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=30001;++i)
		fa[i]=i,u[i]=0,deep[i]=1;//深度为1 //初始每个下面没有方块 
	for(int i=0;i<n;++i){
		s=rs(); scanf("%d",&a);
		if(s=="M"){
			scanf("%d",&b);
			merg(a,b);
		}else{
			int k=gf(a);//这里 是防止a没有合并完全 
			printf("%d\n",u[a]);
		}
	}
	return 0;
}

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
 

食物链

动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B,B吃C,C吃A。现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。有人用两种说法对这N个动物所构成的食物链关系进行描述:

第一种说法是“1 X Y”,表示X和Y是同类。

第二种说法是“2 X Y”,表示X吃Y。

此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。

首先,1和2是同类相当于1为A则2也为A等等,若2吃3则说明2为A时3为B、2为B时3为C、2为C时3为A,以此类推找出不符合逻辑的。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#include<set>
//scanf("%d",&);
using namespace std;
typedef long long ll;
const int maxn=30005,inf=0x3f3f3f3f;
const double esp=1e-8;
//int t,n,m,a,b,fa[maxn],u[maxn],deep[maxn];
int n,k,cnt;
int fa[200010];
int find(int x){
	while(x!=fa[x]) x=fa[x]=fa[fa[x]];
	return x;
}
void merge(int x,int y){
	x=find(x); y=find(y);
	fa[x]=fa[y];
}
int main() {
	cin>>n>>k;
	for(int i=1; i<=3*n; ++i)
		fa[i]=i;
	/*
	fa[i]    i->a
	fa[i+n]  i->b
	fa[i+2n] i->c
	*/
	for(int i=0; i<k; ++i) {
		int op,x,y;
		scanf("%d%d%d",&op,&x,&y);
		if(y>n||x>n) {
			cnt++;
			continue;
		}
		if(op==1) {
			if(find(x)==find(y+n)||find(x)==find(y+2*n)) //若x为a则y为b或c就是假话 
				cnt++;
			else{
				merge(x,y);
				merge(x+n,y+n);
				merge(x+2*n,y+2*n);
			}
		}else{
			if(find(x)==find(y)||find(x)==find(y+2*n)) //若x为a则y为a或c就是假话 
				cnt++;
			else{
				merge(x,y+n);
				merge(x+n,y+2*n);
				merge(x+2*n,y);
			}
		}
	}
	cout<<cnt;
    return 0;
}

给出一些点和边,每个点上都有一个权值,后给出一些操作,分为2种

Q a询问与a直接或者间接相连的点中最大权值的是哪个点,输出这个点

D a b 删除ab的边

Q操作即是通过边维护并查集,只要找到集合里权值最大的边即可;但D操作却是一个拆分并查集的操作,这可咋办?其实可以先读入操作,然后倒着求答案,若某Q操作在D中的边删除之前进行,则先连上D中的边再求Q。例:

Q 3

D 4 5

Q 6        这个例子就是先读样例,去掉4 5连边,在Q 6执行后将4 5边连上,再执行Q 3。

给定由0/1组成的数串长度n,m个描述,每个描述给出a,b,s表示区间[a,b]内1的数量的奇偶性,找出第一个与之前回答矛盾的位置。

由于区间会出现如相交、互不相关等复杂关系,所以我们尽量将区间信息转化为单点的信息。如果[a,b]1为奇数个,则[0,a-1]与[0,b]中1的奇偶性不同;否则相同。然后华丽滴变成食物链的变形。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#include<set>
//scanf("%d",&);
using namespace std;
typedef long long ll;
const int maxn=30005,inf=0x3f3f3f3f;
const double esp=1e-8;
int len,q,a,b,ans;
string s;
map<int,int>fa;
inline string rs(){
	string s;
	char c=getchar();
	while(c==' '||c=='\n') c=getchar();
	while(c!=' '&&c!='\n'){
		s+=c; c=getchar(); 
	}
	return s;
}
int find(int x){
	while(x!=fa[x]) x=fa[x]=fa[fa[x]];
	return x;
}
void merge(int x,int y){
	fa[find(x)]=find(y);
}
int main(){
	cin>>len>>q; len++; ans=q;
	for(int i=0;i<q;++i){
		cin>>a>>b;
		if(fa[b]==0) fa[b]=b,fa[b+len]=b+len;
		if(fa[a-1]==0) fa[a-1]=a-1,fa[a-1+len]=a-1+len;
		s=rs();
		if(s=="even"){
			if(find(a-1)==find(b+len)||find(a-1+len)==find(b)){
				ans=i;
				break;
			}else{
				merge(a-1,b);
				merge(a-1+len,b+len);
			}
		}else{
			if(find(a-1)==find(b)||find(a-1+len)==find(b+len)){
				ans=i;
				break;
			}else{
				merge(a-1,b+len);
				merge(a-1+len,b);
			}
		}
	}
	cout<<ans;
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值