【luogu P7298】Dance Mooves G(单调队列)(桶)(图论)

Dance Mooves G

题目链接:luogu P7298

题目大意

给你 k 个位置对,在一个时刻,当前的位置对的两个牛会交换位置。
然后当前的位置对是轮流的,不断的从 1~k。
然后问你 m 个时刻之后,每个牛到过多少个不同的位置。

思路

首先我们考虑经过了 k k k 个时间,即经过了一轮,在位置 i i i 的牛到了哪,经过了那些点。
不难想到这个到哪如果连边,就会出现一些环,最简单的环。

那如果无限时间,我们就直接搞个 set,然后在一个环里面的点经过的点并起来的个数就是环里面那些点的答案。

那如果有时间,那我们考虑 t i m e s = m / k times=m/k times=m/k 即经过了 t i m e s times times 轮, e l s e s = m   m o d   k elses=m\bmod k elses=mmodk 即最后还要把前 e l s e s elses elses 个弄了。
那对于一个环,如果它的长度小于等于 t i m e s times times 那还是相当于无限时间,比较已经跑了一圈,所有都跑到了。
那如果没有满,我们就考虑用一个双指针来处理一整轮的,通过 insert 和 clear 来维护一个区间的点出现个数(类似滑动窗口),然后至于剩下的几次你就考虑直接暴力加进去,然后暴力消除影响。

然后不难看出不用 set,而是用一个桶,记录每个点被经过了多少次,然后维护当前答案。
如果放一个点,加了之后是出现一次,就是新出现,答案加一。
如果消一个点,消了之后没有出现,那就是最后一个出现的被消掉了,答案减一。

然后至于怎么暴力加剩下的几次你就弄进去的时候带一个它是第几次的交换,然后由于你按顺序弄进去,所以你只要找到第一个时间在要求时间之后的就退出就可以了。
(记得要把它一开始的位置也要弄进去)

然后你分析一下,每个点走一轮能到的点数的和的级别就是 n n n 的级别,那你滑动窗口就是 O ( n ) O(n) O(n),你暴力就算每个都暴力一次也都是 O ( n ) O(n) O(n),所以是可以过的。
每个点走一轮能到的点数可以用 vector 存。

代码

#include<cstdio>
#include<vector>
#define ll long long

using namespace std;

int n, x, y, pl[100001], fa[100001];
vector <pair<int, int> > go[100001];
int l, r, qu[200001], ans[100001];
ll k, m, times, elses;
bool in[100001];

struct Tong {
	int t[100001], ans;
	void insert(int now, int st) {
		for (int i = 0; i < go[now].size(); i++) {
			if (go[now][i].first > st) return ;//记得是这里判断能到的点是否超过能走的步数
			t[go[now][i].second]++;
			if (t[go[now][i].second] == 1) ans++;
		}
	}
	
	int query() {
		return ans;
	}
	
	void clear(int now, int st) {
		for (int i = 0; i < go[now].size(); i++) {
			if (go[now][i].first > st) return ;
			t[go[now][i].second]--;
			if (!t[go[now][i].second]) ans--;
		}
	}
}tt;

int main() {
	scanf("%d %lld %lld", &n, &k, &m);
	for (int i = 1; i <= n; i++) {
		go[i].push_back(make_pair(0, i));
		pl[i] = i;
	}
	for (int i = 1; i <= k; i++) {
		scanf("%d %d", &x, &y);
		go[pl[x]].push_back(make_pair(i, y));//记录一轮置换这头牛经过了哪些点
		go[pl[y]].push_back(make_pair(i, x));
		swap(pl[x], pl[y]);//交换一轮
	}
	
	times = m / k;
	elses = m - k * times;
	for (int i = 1; i <= n; i++)
		fa[pl[i]] = i;
	
	for (int i = 1; i <= n; i++) {
		if (in[i]) continue;
		bool tob = 0; l = 1; r = 0;
		for (int j = i, num = 1; num <= times; num++) {
			if (j == i && num > 1) {//置换轮数超过环的长度
				tob = 1; break;
			}
			tt.insert(j, k); qu[++r] = j;//插入这一轮能到的点
			j = fa[j];
		}
		if (tob) {//置换都跑过一次,每个点的答案都一样
			ans[i] = tt.query(); in[i] = 1;
			for (int j = fa[i]; j != i; j = fa[j]) {
				in[j] = 1; ans[j] = tt.query();
			}
			for (int j = l; j <= r; j++)//记得这里要清空
				tt.clear(qu[j], k);
		}
		else {
			fa[0] = i;
			for (int j = i, num = 1; num == 1 || j != i; num++) {
				int nxt = fa[qu[r]];
				tt.insert(nxt, elses); ans[j] = tt.query(); tt.clear(nxt, elses);//剩下部分加了再算,算了再减掉
				tt.insert(nxt, k); qu[++r] = nxt;
				tt.clear(j, k); l++;//换块
				in[j] = 1;
				j = fa[j];
			}
			for (int j = l; j <= r; j++)//记得清空
				tt.clear(qu[j], k);
		}
	}
	
	for (int i = 1; i <= n; i++) printf("%d\n", ans[i]);
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值