Codeforces 516E

100 篇文章 0 订阅
20 篇文章 0 订阅

分别计算两侧点的最大时间,不妨考虑男生一侧。
某个一开始不开心的男生 x x x,如果他最后开心了,那么一定是由某个一开始开心的人传递过来的,假设那个人的编号是 s s s(在哪侧不重要),传到 x x x时间的下界显然是 min ⁡ { s + k m ∣ ( s + k m ) ≡ x ( m o d    n ) } \min\{s+km|(s+km)\equiv x(\mod n)\} min{s+km(s+km)x(modn)},并且容易达到下界。
那么我们显然可以对于每个模 gcd ⁡ ( n , m ) \gcd(n,m) gcd(n,m)的等价类分开考虑。对于某个等价类,如果一开始没有开心的人那么显然无解,否则我们可以取其中的一个元素,将其他元素重新标号,使得相邻两个元素   m o d     n \bmod \ n mod n意义下公差为 m m m,这样得到了一个环,于是我们发现实质上是一开始环的某些位置有一些点,这些点每过 m m m个单位时间会向后扩散一个单位,问最少多少时间覆盖整个环。
这个问题显然可以二分+排序实现,时间复杂度 O ( ( b + g ) log ⁡ 2 n ) \mathcal O((b+g)\log^2n) O((b+g)log2n)。可以做到更优复杂度但没必要。

#include <bits/stdc++.h>
#define FR first
#define SE second
 
using namespace std;
 
typedef long long ll;
typedef pair<int,int> pr;
 
void exgcd(ll a,ll b,ll &x,ll &y) {
  if (!b) {
  	x=1;y=0;
  }
  else {
  	exgcd(b,a%b,y,x);
  	y-=(a/b)*x;
  }
}
 
int getinv(int x,int y) {
  ll t1,t2;
  exgcd(x,y,t1,t2);
  return (t1%y+y)%y;
}
 
vector <int> vt1[200005],vt2[200005];
int cur1[100005],cur2[100005];
pr q[500005];
 
bool check(int x,int n,int M,ll d) {
  int cnt=0;
  for(int i=0;i<vt1[x].size();i++) {
  	int u=cur1[i];
  	if (vt1[x][i]<=d) {
  		ll len=(d-vt1[x][i])/M+1;
  		if (len>=n) return 1;
  		if (u+len-1<n) q[++cnt]=pr(u,u+len-1);
  		else {
  			q[++cnt]=pr(u,n-1);
  			q[++cnt]=pr(0,(u+len-1)%n);
		  }
	  }
	else q[++cnt]=pr(u,u);
  }
  for(int i=0;i<vt2[x].size();i++) {
  	int u=cur2[i];
  	if (vt2[x][i]<=d) {
  		ll len=(d-vt2[x][i])/M+1;
  		if (len>=n) return 1;
  		if (u+len-1<n) q[++cnt]=pr(u,u+len-1);
  		else {
  			q[++cnt]=pr(u,n-1);
  			q[++cnt]=pr(0,(u+len-1)%n);
		  }
	  }
  }
  sort(q+1,q+cnt+1);
  int rx=-1;
  for(int i=1;i<=cnt;i++) {
  	int l=q[i].FR,r=q[i].SE;
  	if (l>rx+1) return 0;
  	rx=max(rx,r);
  }
  return rx==n-1;
}
 
ll solve(int x,int n,int m,int d) {
  if (!vt1[x].size()&&!vt2[x].size()) {
  	puts("-1");
  	exit(0);
  }
  ll inv=getinv(m,n);
  for(int i=0;i<vt1[x].size();i++) cur1[i]=(vt1[x][i]-x)/d*inv%n;
  for(int i=0;i<vt2[x].size();i++) cur2[i]=(vt2[x][i]-x)/d*inv%n;
  ll l=0,r=1e18;
  while (l<r) {
  	ll mid=((l+r)>>1);
  	if (check(x,n,m*d,mid)) r=mid; else l=mid+1;
  }
  return l;
}
 
int a[100005],b[100005];
 
int main() {
  int n,m;
  scanf("%d%d",&n,&m);
  int d=__gcd(n,m);
  n/=d;m/=d;
  int B;
  scanf("%d",&B);
  for(int i=1;i<=B;i++) scanf("%d",&a[i]);
  int G;
  scanf("%d",&G);
  for(int i=1;i<=G;i++) scanf("%d",&b[i]);
  if (B+G<d) {
  	puts("-1");
  	return 0;
  }
  ll ans=0;
  for(int i=0;i<d;i++) {
  	vt1[i].clear();
  	vt2[i].clear();
  }
  for(int i=1;i<=B;i++) vt1[a[i]%d].push_back(a[i]);
  for(int i=1;i<=G;i++) vt2[b[i]%d].push_back(b[i]);
  for(int i=0;i<d;i++)
    ans=max(ans,solve(i,n,m,d));
  for(int i=0;i<d;i++) {
  	vt1[i].clear();
  	vt2[i].clear();
  }
  for(int i=1;i<=B;i++) vt2[a[i]%d].push_back(a[i]);
  for(int i=1;i<=G;i++) vt1[b[i]%d].push_back(b[i]);
  for(int i=0;i<d;i++)
    ans=max(ans,solve(i,m,n,d));
  printf("%lld\n",ans);
  return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值