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

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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值