导入
你,是否在想到贪心策略却无法找到“比x大/小的第一个空位”而烦恼不已?
你,是否因为没有高效的算法而不断TLE?
你需要的,仅仅只是一个4行代码的并查集而已。
当并查集遇上贪心——
一切事情似乎都那么美妙!
好吧以上一段废话只是说明并查集在解决“寻找比x大/小的第一个满足条件的位置,并占用这个位置”的贪心问题上很优秀而已
UVA1623
题目大意
某城市有n个湖,一开始全是满的。接下来有m天,每天在一个湖里会下暴雨,如果下雨的湖是空的就会变满,是满的就会发生水灾。城市里有一条龙,在不下雨的天可以喝干一个满的湖,问龙要怎么喝水才能防止发生水灾(输出不下雨的天里龙喝的湖的编号,不喝输出“0”。)
题目分析
很容易想到一个贪心策略:假如今天在湖x上下雨了,我们找到湖x上一次被填满的时间,然后在这个时间后第一个晴天让龙喝了x的水。
但是此题数据范围很大,需要高效维护。怎么办?上并查集!
f[x]表示:x之后第一个晴天。首先用并查集的路径压缩找到f[x],然后令f[x]=find(x+1)即可(find是一边找根一边路径压缩的函数),复杂度常数级别,比用队列什么维护的不知道高明快到哪里去了。
代码
用时200ms。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int read() {
int q=;char ch=' ';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=q*+ch-'0',ch=getchar();
return q;
}
const int N=+;
int T,n,m;
int f[N],a[N],ans[N],lake[N];
int find(int x) {
if(f[x]==x) return x;
f[x]=find(f[x]);return f[x];
}
int work() {
int i,x;
for(i=;i<=n;++i) lake[i]=;
for(i=;i<=m;++i) {
if(!a[i]) continue;
x=find(lake[a[i]]);
if(x<=i) ans[x]=a[i],f[x]=find(x+);
else return ;
lake[a[i]]=i;
}
puts("YES");
for(i=;i<=m;++i)
if(!a[i])printf("%d ",ans[i]);
puts("");
}
int main()
{
int i,las;T=read();
while(T--) {
n=read(),m=read();
for(i=;i<=m;++i) ans[i]=,a[i]=read();
las=m+,f[m+]=m+;
for(i=m;i>=;--i) {
if(!a[i]&&i!=) las=i;
f[i]=las;
}
if(!work()) puts("NO");
}
return ;
}
UVA11134
题目大意
在n*n的棋盘上放n个车,使任意两车不共行或共列。第i个车必须放在以( x1 , y1 )为左上角( x2 , y2 )为右下角的矩阵里。
题目分析
首先发现某一个车所在位置的行和列可以分开考虑。
好的,那么题目变成了求两次:给定n个区间( [x1,x2] 或 [y1,y2] ),在每个区间里放一个[1,n]的整数且这些整数互不相同。
会发现区间用右端点为第一关键字,左端点为第二关键字,从小到大排序后,一个一个处理,当前区间选择:还没有被用过的,离左端点尽可能近的一个数会比较好(因为以后的区间会在“后面一点”,所以小的数用处会越来越小)。
那么自然是用f[x]表示:大于等于x的没有被用过的数。
那么处理就和上题类似啦。
代码
用时0ms。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int read() {
int q=;char ch=' ';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=q*+ch-'0',ch=getchar();
return q;
}
const int N=;
struct node{int l,r,id;}a[N],b[N];
int n,xx[N],yy[N],f[N];
bool cmp(node x,node y) {
if(x.r==y.r) return x.l<y.l;
return x.r<y.r;
}
int find(int x) {
if(f[x]==x) return x;
f[x]=find(f[x]);return f[x];
}
int work1() {
for(int i=;i<=n+;++i) f[i]=i;
for(int i=;i<=n;++i) {
int kl=find(a[i].l);
if(kl<=a[i].r) xx[a[i].id]=kl,f[kl]=find(kl+);
else return ;
}
return ;
}
int work2() {
for(int i=;i<=n+;++i) f[i]=i;
for(int i=;i<=n;++i) {
int kl=find(b[i].l);
if(kl<=b[i].r) yy[b[i].id]=kl,f[kl]=find(kl+);
else return ;
}
return ;
}
int main()
{
while("I am cute.") {//其实就是while(1)
n=read();if(!n) break;
for(int i=;i<=n;++i) {
a[i].l=read(),b[i].l=read();
a[i].r=read(),b[i].r=read();
a[i].id=b[i].id=i;
}
sort(a+,a++n,cmp),sort(b+,b++n,cmp);
if(!work1()){puts("IMPOSSIBLE");continue;}
if(!work2()){puts("IMPOSSIBLE");continue;}
for(int i=;i<=n;++i) printf("%d %d\n",xx[i],yy[i]);
}
return ;
}
POJ1456
题目大意
超市要销售一些东西,每天只能销售一个。给出每件商品的销售后的价格和销售的期限(即要在期限内销售出去),求销售的最大获利。
题目分析
先把商品按照价格从大到小排序,然后处理,一件商品在其销售期限内的第一个没有销售任务的日子用来销售这件商品。如果找不到空的日子,就不销售这件商品。
用并查集维护“一件商品在其销售期限内的第一个没有销售任务的日子”
代码
时间:63ms(没有加读入优化QAQ)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<climits>
using namespace std;
const int N=;
int n,ans;
struct node{int w,t;}a[N];
int f[N];
bool cmp(node x,node y){return x.w>y.w;}
int find(int x){
if(f[x]==x)return x;
f[x]=find(f[x]);return f[x];
}
int main()
{
int i,j,r1,r2;
while(scanf("%d",&n)!=EOF){
for(i=;i<=n;++i)scanf("%d%d",&a[i].w,&a[i].t);
sort(a+,a++n,cmp);
for(i=;i<=;++i)f[i]=i;
ans=;
for(i=;i<=n;++i){
r1=find(a[i].t);
if(r1){
r2=find(r1-);
f[r1]=r2,ans+=a[i].w;
}
}
printf("%d\n",ans);
}
return ;
}
练习题
–由于某些特殊原因,此题暂时留空–