2017年10月23日提高组T1 询问
Description
Input
Output
分析:首先二分答案,转化为前 mid 个询问是否矛盾。 把所有询问按照答案从大到小排序,把答案相同的那些询问放在一起,求出区 间交及区间并,如果区间交为空说明序列中有重复数字,直接无解;否则我们看 看区间交是否全被覆盖,如果全被覆盖显然之前的区间答案错误产生矛盾,如 果没全被覆盖我们就把区间并(由于区间交不为空区间并显然是一段连续区间) 进行覆盖。 正确性在于如果有 l1 ≤ l2 ≤ r2 ≤ l1,那么显然有 ans2 ≥ ans1,而如果答案小 的区间全被覆盖就相当于 ans2 < ans1。这就产生了矛盾。 考虑怎么维护,可以使用线段树进行维护,但是更简单的是利用并查集。 暴力覆盖区间中的位置,当覆盖了 i 之后就把 i 合并到 i + 1 的集合下面。 于是 i 在并查集中的根就是 i 所在的最长连续覆盖区间的右端点 +1,也就是说 后面第一个没被覆盖的位置。 覆盖 [l, r] 时就从 l 开始,每次找到下一个没被覆盖的位置,如果还没超过 r, 就把他覆盖然后合并。 查询 [l, r] 是否完全被覆盖只需比较 l 后面第一个没被覆盖位置是否 > r。 这样的话每次覆盖时间复杂度与实际覆盖的位置数有关,那么总复杂度不超过 O(nα(n))。 于是总时间复杂度 O(log Q(Q log Q + nα(n)))。
代码
#include <cstdio>
#include <algorithm>
#define maxn 2000000
using namespace std;
struct edge
{
int x,y,w;
};
struct arr
{
int x,y,w;
};
arr a[maxn];
edge b[maxn];
int n,m,f[maxn];
int so(edge p,edge q)
{
return p.w>q.w;
}
int find1(int x)
{
if (f[x]==x) return x;
return f[x]=find1(f[x]);
}
int find(int a)
{
int r, j, k ;
r = a;
while(r != f[r])
r = f[r];
j = a;
while(j != r)
{
k = f[j];
f[j] = r;
j = k;
}
return r;
}
int max(int x,int y)
{
return x>y?x:y;
}
int min(int x,int y)
{
return x<y?x:y;
}
bool check(int x)
{
for (int i=1;i<=x;i++)
{
b[i].x=a[i].x;
b[i].y=a[i].y;
b[i].w=a[i].w;
}
for (int i=1;i<=n+1;i++)
f[i]=i;
sort(b+1,b+x+1,so);
int fx=b[1].x,fy=b[1].y;
int gx=fx,gy=fy;
b[x+1].w=-1;
for (int i=2;i<=x+1;i++)
if (b[i].w==b[i-1].w)
{
if (b[i].x>fy||b[i].y<fx) return false;
fx=max(b[i].x,fx);
fy=min(b[i].y,fy);
gx=min(b[i].x,gx);
gy=max(b[i].y,gy);
}
else
{
int u=find(fx);
int v=find(fy+1);
if (u==v) return false;
u=find(gx);
v=find(gy+1);
while (u!=v)
{
f[u]=u+1;
u=find(u+1);
}
fx=b[i].x;
fy=b[i].y;
gx=fx;gy=fy;
}
return true;
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++)
scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].w);
int l=1,r=m;
while (l<r)
{
int mid=(l+r)/2;
if (check(mid)) l=mid+1;
else r=mid;
}
if (l==m&&check(l)) printf("%d",0);else printf("%d",l);
}