[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
    评论
以下是P4087 [USACO17DEC]Milk Measurement的c++代码: ```c++ #include<bits/stdc++.h> using namespace std; int n,d,i,x,minn=1e9,maxn=-1e9,sum=7;//注意sum要初始化为7,因为一开始有三个人挤奶! map<int,int> mp; struct node{ int day,milk,id;//day表示某一天,milk表示这一天的产奶量,id表示这头牛的编号 }a[100010]; bool cmp(node x,node y){ return x.day<y.day; } int main(){ scanf("%d%d",&n,&d); for(i=1;i<=n;i++){ scanf("%d%d%d",&a[i].day,&a[i].id,&a[i].milk); minn=min(minn,a[i].id);//记录最小的牛的编号 maxn=max(maxn,a[i].id);//记录最大的牛的编号 } sort(a+1,a+n+1,cmp);//排序 for(i=1;i<=n;i++){ int p=a[i].id; mp[p]+=a[i].milk;//记录每头牛产奶总量 if(mp[p]-a[i].milk>=mp[minn]&&mp[p]>=mp[minn]){//如果这头牛的产奶总量减去这一天的产奶量后等于最小产奶量且这头牛的产奶总量大于等于最小产奶量 sum--; } if(mp[p]>=mp[maxn]&&mp[p]-a[i].milk<mp[maxn]){//如果这头牛的产奶总量大于等于最大产奶量且这头牛的产奶总量减去这一天的产奶量小于最大产奶量 sum++; } if(mp[p]-a[i].milk<mp[maxn]&&mp[p]>=mp[maxn]){//如果这头牛的产奶总量减去这一天的产奶量小于最大产奶量且这头牛的产奶总量大于等于最大产奶量 if(mp[maxn]-mp[p]+a[i].milk>0)sum++; } mp[p]-=a[i].milk;//减去这一天的产奶量 if(i==n||a[i].day!=a[i+1].day){//如果到了新的一天或者到了最后一天 if(mp[maxn]!=mp[a[i].id]&&mp[a[i].id]>=mp[maxn])sum++;//如果这头牛的产奶总量不等于最大产奶量且这头牛的产奶总量大于等于最大产奶量 if(mp[maxn]==mp[a[i].id]){//如果这头牛的产奶总量等于最大产奶量 if(a[i].id==maxn)sum+=0;//如果这头牛就是最大产奶量的牛,那么不需要增加计数器 else sum++;//否则需要增加计数器 } if(mp[minn]!=mp[a[i].id]&&mp[a[i].id]>=mp[minn])sum++;//如果这头牛的产奶总量不等于最小产奶量且这头牛的产奶总量大于等于最小产奶量 if(mp[minn]==mp[a[i].id]){ if(a[i].id==minn)sum+=0;//如果这头牛就是最小产奶量的牛,那么不需要增加计数器 else sum++;//否则需要增加计数器 } } } printf("%d\n",sum); return 0; } ``` 该题的解题思路是拟,需要注意细节问题。我们可以首先将输入的数据按天数排序,然后拟每一天挤奶的情况,并根据题目要求进行计数即可。具体细节请见代码注释。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值