[IOI2018]狼人

嘟嘟嘟

好题好题。

看到对边权或点权的限制,可以想kruskal重构树(好吧我是为了刷kruskal重构树才写这道题的……)。

给题目变个型:\(Q\)组询问,每组询问为:一匹狼从终点出发,一个人从起点出发,狼只能经过编号为\([1, R]\)的点,人只能经过\([L, n]\)的点,问能否有一个公共点。

那么我们可以给狼和人分别建一棵kruskal重构树,对于人,我们把边权按\(min(E[i].u, E[i].v)\)从大到小排序;对于狼,把边权按\(max(E[i].u, E[i].v)\)从小到大排序。
以人为例,重构树上节点\(u\)的子树就表示经过点权不超过\(u\)的点权能互相到达的点。那么我们从起点\(S\)向上倍增,找到最靠上的一个点\(u\),满足他的点权仍不小于\(L\)。这样这个点子树内的点就是人能活动的范围。
狼也同理,找到最考上的点\(v\)
这样我们只用判断\(u\)\(v\)子树范围内的点有没有交集即可。这是一个二维数点问题。所谓的二位数点,就是横坐标是狼的dfs序,纵坐标是人的dfs序,然后看这个矩形内是否有点。可以在线主席树,也可以离线树状数组。

我写的在线主席树。方法就是我们先按狼的重构树的dfs序建树,每一个位置的权值是这个点在人的重构树上的dfs序。这样对于一个询问,我们只用看第\(dfn[v]\)\(dfn[v] + size[v] - 1\)棵主席树中,区间\([dfn[u], dfn[u] + siz[u] - 1]\)中有没有数,有就返回1,否则返回0.

这题在loj上可以当传统题写,但必须一下子全读完在一个个回答询问,边读边回答询问会交互超时……

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cctype>
#include<vector>
#include<stack>
#include<queue>
#include<assert.h>
using namespace std;
#define enter puts("") 
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const int maxn = 4e6 + 5;
const int N = 19;
In ll read()
{
  ll ans = 0;
  char ch = getchar(), last = ' ';
  while(!isdigit(ch)) last = ch, ch = getchar();
  while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
  if(last == '-') ans = -ans;
  return ans;
}
In void write(ll x)
{
  if(x < 0) x = -x, putchar('-');
  if(x >= 10) write(x / 10);
  putchar(x % 10 + '0');
}
In void MYFILE()
{
#ifndef mrclr
  freopen("ac.in", "r", stdin);
  freopen("ha.out", "w", stdout);
#endif
}

int n, m, Q;
struct edges {int x, y;} E[maxn];
struct Edge {int nxt, to;};

In bool cmp1(edges a, edges b) {return min(a.x, a.y) > min(b.x, b.y);}
In bool cmp2(edges a, edges b) {return max(a.x, a.y) < max(b.x, b.y);}

struct Kruskal
{
  Edge e[maxn];
  int head[maxn], ecnt = -1, a[maxn];
  In void addEdge(int x, int y)
  {
    e[++ecnt] = (Edge){head[x], y};
    head[x] = ecnt;
  }

  In int Find(int x) {return x == p[x] ? x : p[x] = Find(p[x]);}
  int tcnt, p[maxn];
  In void build(bool flg)
  {
    Mem(head, -1); tcnt = n;
    for(int i = 1; i <= n; ++i) p[i] = i, a[i] = 0;
    for(int i = 1; i <= m; ++i)
      {
    int px = Find(E[i].x), py = Find(E[i].y);
    if(px == py) continue;
    a[++tcnt] = flg ? min(E[i].x, E[i].y) : max(E[i].x, E[i].y), p[tcnt] = tcnt;
    addEdge(tcnt, px), addEdge(tcnt, py);
    p[px] = tcnt, p[py] = tcnt;
      }
  }

  int dep[maxn], siz[maxn], fa[N + 2][maxn];
  int dfn[maxn], pos[maxn], cnt;
  In void dfs(int now)
  {
    siz[now] = 1;
    dfn[now] = ++cnt, pos[cnt] = now;
    for(int i = 1; (1 << i) <= dep[now]; ++i)
      fa[i][now] = fa[i - 1][fa[i - 1][now]];
    for(int i = head[now], v; ~i; i = e[i].nxt)
      {
    v = e[i].to;
    dep[v] = dep[now] + 1, fa[0][v] = now;
    dfs(v);
    siz[now] += siz[v];
      }
  }
  In int jump(int x, int K, int flg)
  {
    for(int i = N; i >= 0; --i)
      if((1 << i) <= dep[x] && a[fa[i][x]] * flg >= K * flg) x = fa[i][x];
    return x;
  }
}K1, K2;

struct Tree
{
  int ls, rs, sum;
}t[maxn * 20];
int root[maxn], tcnt = 0;
In void insert(int old, int& now, int l, int r, int id)
{
  t[now = ++tcnt] = t[old];
  ++t[now].sum;
  if(l == r) return;
  int mid = (l + r) >> 1;
  if(id <= mid) insert(t[old].ls, t[now].ls, l, mid, id);
  else insert(t[old].rs, t[now].rs, mid + 1, r, id);
}
int FLG = 0;
In void query(int old, int now, int l, int r, int L, int R)
{
  if(!now) return;
  if(FLG) return;
  if(l == L && r == R) {FLG |= t[now].sum - t[old].sum; return;}
  int mid = (l + r) >> 1;
  if(R <= mid) query(t[old].ls, t[now].ls, l, mid, L, R);
  else if(L > mid) query(t[old].rs, t[now].rs, mid + 1, r, L, R);
  else query(t[old].ls, t[now].ls, l, mid, L, mid), query(t[old].rs, t[now].rs, mid + 1, r, mid + 1, R);
}

int _S[maxn], _T[maxn], _L[maxn], _R[maxn];

int main()
{
  //MYFILE();
  n = read(), m = read(), Q = read();
  for(int i = 1; i <= m; ++i)
    {
      E[i].x = read() + 1, E[i].y = read() + 1;
    }
  for(int i = 1; i <= Q; ++i) _S[i] = read() + 1, _T[i] = read() + 1, _L[i] = read() + 1, _R[i] = read() + 1;
  sort(E + 1, E + m + 1, cmp1), K1.build(1);
  sort(E + 1, E + m + 1, cmp2), K2.build(0);
  K1.dep[K1.tcnt] = 0, K1.cnt = 0, K1.dfs(K1.tcnt);
  K2.dep[K2.tcnt] = 0, K2.cnt = 0, K2.dfs(K2.tcnt);
  for(int i = 1; i <= K2.cnt; ++i)
    if(K2.pos[i] <= n) insert(root[i - 1], root[i], 1, K1.cnt, K1.dfn[K2.pos[i]]);
    else root[i] = root[i - 1];
  for(int i = 1; i <= Q; ++i)
    {
      int x = K1.jump(_S[i], _L[i], 1), y = K2.jump(_T[i], _R[i], -1);
      FLG = 0;
      query(root[K2.dfn[y] - 1], root[K2.dfn[y] + K2.siz[y] - 1], 1, K1.cnt, K1.dfn[x], K1.dfn[x] + K1.siz[x] - 1);
      write(FLG ? 1 : 0), enter;
    }
  return 0;
}

转载于:https://www.cnblogs.com/mrclr/p/10985239.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值