【luogu P8375】游戏(思维)(线段树)

游戏

题目链接:luogu P8375

题目大意

给你 n 个点排成一列,前面 k 个是特殊点,而且每个特殊点会连一条有向边向下一个特殊点(如果下一个不是特殊点就不连)。
然后每次会加一条有向边,然后问你在什么时候开始出现了一个环包含了特殊点。

思路

首先要知道普通图加边动态判环好像是 NP 还是 NP-hard 的,所以我们考虑它有的特殊性质。
也就是前面的一段边。

那我们发现只要前面的那些特殊点能从 \(i\) 走到 \(j\) 而且 \(i\geqslant j\) 即可。
那我们考虑对于每个点维护 \(L_i,R_i\) 分别表示到能到它的编号最大的特殊点,和它能到的编号最小的特殊点
那不难看出如果你在加边 \(x\rightarrow y\) 的时候发现 \(L_x\geqslant R_y\) 那就找到环了。
(只用看这个位置是因为如果别的地方满足了它之前一定没有满足,那它一定是走这里满足的,所以这里一定也是会满足的)

那问题就是你要怎么维护,通过势能分析我们会发现如果硬要维护每个位置的 \(L,R\) 一定是不行的。
那我们考虑怎么维护,其实我们会发现上面那个条件可以看做是自己的区间是否是空的,有这么一个想法就是因为你只需要保证这个区间空或否即可。
那就有一个想法就是你给这个区间弄一棵线段树之类的(不用真的建出来),然后每次只有当更新的值超过了中间线,你才更新左右端点。
那这样每个点的区间弄 \(\log n\) 次,那每次都修改至多 \(n\log n\) 也就可以通过了。

代码

#include<cstdio>
#include<vector>

using namespace std;

const int N = 3e5 + 100;
int n, k, L[N], R[N];
//L[i] 到它的最大   R[i] 能到的最小 
vector <int> G[N], G_[N];
bool fin;

void init(int n_, int k_) {
	n = n_; k = k_; fin = 0;
	for (int i = 1; i <= n; i++) G[i].clear(), G_[i].clear();
	for (int i = 1; i <= n; i++) {
		if (i <= k) L[i] = R[i] = i;
			else L[i] = 0, R[i] = k + 1; 
	}
}

void check(int x, int y);

void update(int x) {
	for (int i = 0; i < G[x].size(); i++) check(x, G[x][i]);
	for (int i = 0; i < G_[x].size(); i++) check(G_[x][i], x);
}

void check(int x, int y) {
	if (L[x] >= R[y]) {fin = 1; return ;}
	int midx = (L[x] + R[x]) >> 1, midy = (L[y] + R[y]) >> 1;
	if (L[x] > midy) L[y] = midy + 1, update(y);
	if (midx >= R[y]) R[x] = midx, update(x);
	return ;
}

int add_teleporter(int u, int v) {
	u++; v++; G[u].push_back(v); G_[v].push_back(u);
	check(u, v); return fin;
}

/*
#include<cstdlib>

int read_int() {
  int x;
  if (scanf("%d", &x) != 1) {
    fprintf(stderr, "Error while reading input\n");
    exit(1);
  }
  return x;
}

int main() {
  int N = read_int();
  int M = read_int();
  int K = read_int();
  std::vector<int> u(M), v(M);
  for (int i = 0; i < M; ++i) {
    u[i] = read_int();
    v[i] = read_int();
  }

  init(N, K);
  int i;
  for (i = 0; i < M; ++i) {
    int answer = add_teleporter(u[i], v[i]);
    if (answer != 0 && answer != 1) {
      i = -1;
      break;
    } else if (answer == 1) {
      break;
    }
  }
  printf("%d\n", i);
  return 0;
}
*/
posted @ 2022-06-14 15:07  あおいSakura  阅读(18)  评论(0编辑  收藏  举报