[USACO21JAN] Dance Mooves G(图论建模+双指针)

本文探讨了通过暴力模拟和环结构分析来解决牛群交换问题的高效算法。首先,暴力模拟方法的时间复杂度为O(NM),然后提出将M视为无限,利用环的性质减少计算。最后,针对有限次数置换的限制,文章介绍了滑动窗口优化策略,使得问题能在O(n)时间内解决。
摘要由CSDN通过智能技术生成

Solution 1

N<=100 K<=200
暴力模拟,记录每个点走过的位置
复杂度 O ( N M ) O(NM) O(NM)

Solution 2

M=1e18
我们知道总共最多经过N个点,所以M可以看成无限走
如果把每 K K K轮交换称作一次置换,从每个点,向一次置换后它的位置连一条边,则会发现形成的这个图是由若干环组成的,也有一些是自环,环上的边可以看成一次置换,也就是说每条边上实际是由 K K K次小交换组成的
比如样例的图是这样的
盗一下大佬的图
在这里插入图片描述
经过 K K K轮之后,1号奶牛就到了5号位置
因为M是无限大,所以最终环上的点是可以互相到达的,也就是它们的答案是相等的
如果用 S i S_i Si表示 i i i这个点经过一次置换能到达的点的集合,则一个环的答案就是环上所有点 S S S集合的并集,因为一次置换最多有 2 ∗ K 2*K 2K次换位,所以 ∑ S i ≤ 2 ∗ K \sum S_i\leq 2*K Si2K,可以对每个点开vector存起来,统计并集就用一个桶即可

Solution 3

有了 M M M的限制后,从每个点出发不一定能遍历到环上所有点,所以每个点的答案可能不同,我们对环分两类讨论
设在 M M M次内,能进行的置换的个数为 T T T,余下不足一次置换的个数为 R R R
也就是 T = M / K , R = M − K ∗ T T=M/K,R=M-K*T T=M/K,R=MKT

1:环的大小小于等于T

此时环依然能遍历完,处理和上面是一样的

2:环的大小大于T

也就是说从每个点出发不能遍历环上的所有点,如图
在这里插入图片描述
举个例子,从一号点出发,绿色的线就是T次置换经过的点,蓝色的线就是剩下的不足一次置换的R次交换经过的点,那么1号点的答案就是被覆盖部分的并集,形式化地说
设点 i i i经过 T T T次置换到达的点为 j j j
答案就是 S i ∪ S i + 1 ∪ … … S j ∪ L j S_i \cup S_{i+1} \cup ……S_j \cup L_j SiSi+1SjLj
其中 L i L_i Li表示从 i i i号点出发进行 R R R次交换能到达的点的集合
这个式子可以用一个桶处理
但是暴力这样统计答案是 O ( N K ) O(NK) O(NK)的,依然会T,而我们浪费的地方就是没有利用已经统计过的信息
考虑到从每个点出发的距离都相等,所以可以看成是若干长度相等的区间
这就是经典的滑动窗口问题,可以 O ( n ) O(n) O(n)做,设 l , r l,r l,r表示当前区间,当 r r r加一的时候, l l l也跟着加一,同时删掉 l l l的贡献,加上 r r r的贡献,因为是环所以破环成链倍长即可
如果不懂看下边的图
l l l从1转移到5时,删掉 1 1 1 ~ 5 5 5之间的绿色边,删掉 4 4 4开始的蓝色边,加上 4 4 4 ~ 3 3 3的绿色边,最后加上从 3 3 3开始的蓝色边
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5+7;
struct Swap
{
	int x,y;
}seq[N];
vector<int> res[N],pos[N];
int a[N];
int Next[N];
int n;
LL k,m,R,T;
bool vis[N];
vector<int> cir[N];
int siz[N];
int cnt=0;
void Find(int x)
{
	int y=x;
	++cnt;
	do
	{
		vis[y]=1;
		cir[cnt].push_back(y);
		y=Next[y];
	}while(x!=y);
	siz[cnt]=cir[cnt].size();
}
int Ans[N];
int t[N];
int ans=0;
void add(int x,int opt)
{
	if(opt==1)
	{
		for(int i=0;i<pos[x].size();i++)
		{
			int y=pos[x][i];
			if(t[y]==0) ans++;
			t[y]++;
		}		
	}
	if(opt==2)
	{
		for(int i=0;i<res[x].size();i++)
		{
			int y=res[x][i];
			if(t[y]==0) ans++;
			t[y]++;
		}		
	}
}
void del(int x,int opt)
{
	if(opt==1)
	{
		for(int i=0;i<pos[x].size();i++)
		{
			int y=pos[x][i];
			if(t[y]==1) ans--;
			t[y]--;
		}		
	}
	if(opt==2)
	{
		for(int i=0;i<res[x].size();i++)
		{
			int y=res[x][i];
			if(t[y]==1) ans--;
			t[y]--;
		}		
	}
}
void calc(int id)
{
	if(siz[id]<=T)
	{
		for(int i=0;i<siz[id];i++)
		{
			int x=cir[id][i];
			add(x,1);
		}
		for(int i=0;i<siz[id];i++)
		{
			int x=cir[id][i];
			Ans[x]=ans;
		}
		for(int i=0;i<siz[id];i++)
		{
			int x=cir[id][i];
			del(x,1);
		}
		return;
	}
	else
	{
		for(int i=0;i<siz[id];i++)
		{
			int x=cir[id][i];
			cir[id].push_back(x);
		}
		int l=0,r=T-1;
		for(int i=l;i<=r;i++)
		{
			int x=cir[id][i];
			add(x,1);
		}
		add(cir[id][T],2);
		Ans[cir[id][l]]=ans;
		while(l<siz[id]-1)
		{
			if(l<=r) del(cir[id][l],1);
			del(cir[id][r+1],2);
			l++;r++;
			if(l<=r) add(cir[id][r],1);
			add(cir[id][r+1],2);
			Ans[cir[id][l]]=ans;
		}
		for(int i=l;i<=r;i++)
		del(cir[id][i],1);
		del(cir[id][r+1],2);
	}
}
void init()
{
	for(int i=1;i<=k;i++)
	{
		int x=seq[i].x,y=seq[i].y;
		pos[a[x]].push_back(y);
		pos[a[y]].push_back(x);
		if(i<=R)
		{
			res[a[x]].push_back(y);
			res[a[y]].push_back(x);
		}
		swap(a[x],a[y]);
	}
	for(int i=1;i<=n;i++) Next[a[i]]=i;
	for(int i=1;i<=n;i++) 
	if(!vis[i]) 
	{
		Find(i);
		calc(cnt);
	}
}
int main()
{
	scanf("%d %lld %lld",&n,&k,&m);
	T=m/k;
	R=m-k*T;
	for(int i=1;i<=k;i++)
	scanf("%d %d",&seq[i].x,&seq[i].y);
	for(int i=1;i<=n;i++)
	{
		pos[i].push_back(i);
		res[i].push_back(i);
		a[i]=i;
	}
	init(); 
	for(int i=1;i<=n;i++)
	printf("%d\n",Ans[i]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值