Codeforces 707(Div2) D - Two chandeliers (二分 + 拓展中国剩余定理)

9 篇文章 0 订阅
5 篇文章 0 订阅

在这里插入图片描述
题意:
很长一大串的题意,可以化简为给定你两个序列,每个序列中都不会有重复的数,然后这两个序列都可以分别看做是可无限延伸循环的,然后给定你一个数字 k k k i i i 1 1 1开始,问你 i i i到达多少后,前 i i i a i , b i a_i,b_i ai,bi中,有 k k k个位置 a i ≠ b i a_i \neq b_i ai=bi

很明显影响 i i i因素是有多个位置他们的 a i = b i a_i = b_i ai=bi,假如对于一个值 v a l val val,它在 a a a中位置是 x x x,在 b b b中的位置时 y y y,那么也就是找到一个最小的位置pos使得
{ p o s ≡ x   ( m o d   n ) p o s ≡ y   ( m o d   m ) \begin{cases} pos \equiv x \ (mod \ n) \\pos \equiv y\ (mod \ m) \end{cases} {posx (mod n)posy (mod m)
很明显这是个关于 p o s pos pos的同余方程,假如 n n n m m m互质那么可以直接 c r t crt crt解决,但是题目这里没要求,那么用 e x c r t excrt excrt也可解决,一般 e x c r t excrt excrt需要注意的细节我们也要注意。

再就是 k k k的范围太大了,但是题目要求我们找得到是等于 k k k的位置,也就是求一个最低的下限,很明显这个位置可以用二分去找出来,每次二分一个答案去检验,对应的值在当前位置区间内不相同的位置是否合法即可,尽量往小找。

用这个题可以学一下中国剩余定理和拓展中国剩余定理。

#include <bits/stdc++.h>

using namespace std;

#define pb emplace_back
#define MP make_pair
#define pii pair<int,int>
#define pll pair<ll,ll>
#define lson rt<<1
#define rson rt<<1|1
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-6;
const int MAXN = 5e5 + 7;
ll n,m,k;
int p1[MAXN<<1],p2[MAXN<<1];//2 * max(n,m) 一个序列里面的数都不同保证了每一个数位置的唯一性
//二分 + crt
/************exCRT*************/
ll gcd(ll a,ll b) {
	return b == 0 ? a : gcd(b,a%b);
}
ll exgcd(ll a,ll b,ll &x,ll &y) {
	if(b == 0) {
		x = 1,y = 0;
		return a;
	}
	else {
		ll res = exgcd(b,a%b,x,y);
		ll t = x;
		x = y;
		y = t - a / b * y;
		return res;
	}
}
ll excrt(ll m1,ll m2,ll a1,ll a2) {//解决模数不互质的情况
	ll x,y,c,g;
	c = a2 - a1;
	g = exgcd(m1,m2,x,y);
	x = x * c / g;//把方程右侧化为
	y = m2 / g;
	x = (x % y + y) % y;//求最小正整数解
	a1 = a1 + x * m1;
	m1 = m1 * m2 / g;
	return a1;//解x
}
/****************************/
ll crt[MAXN<<1],LCM;

bool check(ll x) {
	ll sum = 0,ma = 2 * max(n,m);
	for(int i = 1;i <= ma;i ++) {
		if(!p1[i] || !p2[i]) continue;
		if(!crt[i] || x < crt[i]) continue;//别越界
		//x通解 x = x0 + lcm(m1,m2);
		sum += (x - crt[i]) / LCM + 1;//看看后面还有几个通解
		if(x - sum < k) return false;
	}
	return x - sum >= k;//总为x天,其中sum天相同,则x - sum天不同
}

int main() {
	scanf("%lld%lld%lld",&n,&m,&k);
	for(int i = 1,x;i <= n;i ++) {
		scanf("%d",&x);
		p1[x] = i;
	}
	for(int i = 1,x;i <= m;i ++) {
		scanf("%d",&x);
		p2[x] = i;
	}
	ll ma = 2 * max(n,m),g = gcd(n,m);
	for(int i = 1;i <= ma;i ++) {//求解每一个值的最小出现的位置在哪
		if(!p1[i] || !p2[i]) continue;
		if(abs(p1[i]-p2[i]) % g != 0) continue;
		crt[i] = excrt(n,m,p1[i],p2[i]);//存的是相等的值出现的位置
	}
	LCM = n * m / g;
	ll l = 1,r = ma * k,ans;//ma * k就是相等比较多的极端情况
	while(l <= r) {
		ll mid = (l + r) >> 1;
		if(check(mid)) {
			ans =  mid;
			r = mid - 1;
		}
		else l = mid + 1;
	}
	printf("%lld\n",ans);
	return 0;
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值