《GMOJ-Senior-5449 Pacifist》/《洛谷-CF763C / CodeForces-763-C Timofey and remoduling》题解

题目大意

给出一个长度为 n n n的元素两两不同的序列和一个质数 m m m。若该序列是由一个等差数列在模 m m m意义下打乱而得到的,请尝试求出原数列的首项和公差。若可能的原数列不唯一,请随便输出一个可行数列的首项和公差;若序列不是由任何一个等差数列变换而来的,请输出-1
对于 100 % 100\% 100%的数据, 2 ≤ m ≤ 1 0 9 + 7 2 \leq m \leq 10^9+7 2m109+7 1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1n105 0 ≤ 序 列 中 的 每 一 个 数 < m 0 \leq 序列中的每一个数<m 0<m
要求输出的两个数在 [ 0 , m ) [0,m) [0,m)之间。另外,对于《GMOJ-Senior-5449 Pacifist》,同时要求输出的第二个数不大于 ⌊ m 2 ⌋ \lfloor \frac{m}{2} \rfloor 2m

分析

这道题目不考什么困难的算法,但是考脑洞。

对于洛谷-CF763CCodeForces-763-C

首先,考虑 2 n ≤ m 2n \leq m 2nm的情况。记变换前的数列为 s , s + d , s + 2 d , ⋯   , s + ( n − 1 ) d s,s+d,s+2d, \cdots ,s+(n-1)d s,s+d,s+2d,,s+(n1)d,并设 x x x为变换前的数列中某两个元素(记为 a , b a,b a,b,且 a < b a<b a<b)的差,于是我们有 x ≡ b − a ( m o d m ) x \equiv b-a \pmod m xba(modm)。记 a a a在变换前的数列中的位置为 i i i b b b在变换前的数列中的位置为 i + k i+k i+k,则有 x ≡ d k ( m o d m ) x \equiv dk \pmod m xdk(modm)。当 2 n ≤ m 2n \leq m 2nm时,变换后的序列中一定有 ( n − k + 1 ) (n-k+1) (nk+1)对数的差为 x x x。于是我们可以借助二分查找找出变换后的序列中差为 x x x的数对的数量,并由此算出 k k k的值。因为 x ≡ d k ( m o d m ) x \equiv dk \pmod m xdk(modm),所以 d ≡ x k ( m o d m ) d \equiv \frac{x}{k} \pmod m dkx(modm);又因为 m m m为质数,所以我们可以用费马小定理( a p − 1 ≡ 1 ( m o d p ) a^{p-1} \equiv 1 \pmod p ap11(modp),其中 p p p为质数,且 gcd ⁡ ( a , p ) = 1 \gcd(a,p)=1 gcd(a,p)=1)算出 d d d
知道 d d d后,我们遍历变换后的序列以寻找合适的 s s s。当我们发现对于序列中的某个数 t t t满足 ( t − d + m ) m o d    m (t-d+m) \mod m (td+m)modm不在序列中,那么它就可能是 s s s。但是当我们发现序列中有不止一个数满足时,我们可以确定这个序列不是由任何一个等差数列变换而来的(一个数列的开头只有一个)。
但是,当 2 n > m 2n>m 2n>m时,情况不一样了。因为差最大的两组数是 s , s + ( n − 1 ) d s,s+(n-1)d s,s+(n1)d以及 s , s + ( n − 2 ) d s,s+(n-2)d s,s+(n2)d,它们的差之和为 ( 2 n − 3 ) d (2n-3)d (2n3)d。如果 2 n − 3 ≥ m 2n-3 \geq m 2n3m,那么就会存在两个差,它们的和模 m m m 0 0 0,就会使得我们在查找 序 列 的 后 面 的 项 + x 序列的后面的项+x +x时会找到序列前面的项,就会多统计;但是当 2 n ≤ m 2n \leq m 2nm时却不会。这时,我们算出变换后的序列在 { x ∈ Z ∣ 0 ≤ x < m } \{ x \in Z|0 \leq x<m \} {xZ0x<m}中的补集,并用上述的方法求出补集所对的变换前的等差数列,再将 s s s加上 ( m − n ) m o d    d (m-n) \mod d (mn)modd。因为补集的大小 n ′ = m − n < m − m 2 n'=m-n<m-\frac{m}{2} n=mn<m2m,所以有 2 n ′ < m 2n'<m 2n<m,可以使用上述的方法,同时,因为只要 m m m d d d互质,补集所对的原数列就可以为 s + n d , s + ( n + 1 ) d , s + ( n + 2 ) d , ⋯   , s + ( m − 1 ) d s+nd,s+(n+1)d,s+(n+2)d, \cdots ,s+(m-1)d s+nd,s+(n+1)d,s+(n+2)d,,s+(m1)d,那么该序列与补集上得到的原数列可以有同样的公差。

对于GMOJ-Senior-5449

在输出 s , d s,d s,d的基础上,这道题还要求 d ≤ ⌊ m 2 ⌋ d \leq \lfloor \frac{m}{2} \rfloor d2m。当 d > ⌊ m 2 ⌋ d> \lfloor \frac{m}{2} \rfloor d>2m时,我们考虑尝试把我们还原出的数列反转。数列一开始为 s , s + d , s + 2 d , ⋯   , s + ( n − 1 ) d s,s+d,s+2d, \cdots ,s+(n-1)d s,s+d,s+2d,,s+(n1)d,反转后就变成了 s + ( n − 1 ) d , s + ( n − 2 ) d , s + ( n − 3 ) d , ⋯   , s s+(n-1)d,s+(n-2)d,s+(n-3)d, \cdots ,s s+(n1)d,s+(n2)d,s+(n3)d,,s,公差也就由 d d d变成了 − d -d d。但因为变换后的序列是在模 m m m意义下的,所以 − d -d d等价于 m − d m-d md。此时,因为 d > ⌊ m 2 ⌋ d> \lfloor \frac{m}{2} \rfloor d>2m,所以 m − d < m − ⌊ m 2 ⌋ m-d<m- \lfloor \frac{m}{2} \rfloor md<m2m m − d ≤ ⌊ m 2 ⌋ m-d \leq \lfloor \frac{m}{2} \rfloor md2m,符合要求。此时, a n s ans ans也要加上 ( n − 1 ) d (n-1)d (n1)d。这样, d ≤ ⌊ m 2 ⌋ d \leq \lfloor \frac{m}{2} \rfloor d2m的限制就被解决了。

代码

根据思路,可以写出如下代码:

#include<cstdio>
#include<algorithm>
using namespace std;
using ll = long long;
char buf[16777216];
inline ll Read() //快速读入
{
	static char* c = buf;
	while (*c > '9' || *c < '0')
	{
		++c;
	}
	ll ans = *c ^ 48ll;
	while (*(++c) >= '0' && *c <= '9')
	{
		ans = (ans * 10) + (*c ^ 48ll);
	}
	return ans;
}
ll a[200002], b[200002];
inline bool Find(const ll val) //二分查找给定数值是否在a中
{
	ll l = 1, r = a[0];
	while (l < r)
	{
		const ll mid = (l + r) >> 1;
		if (a[mid] < val)
		{
			l = mid + 1;
		}
		else
		{
			r = mid;
		}
	}
	return a[r] == val;
}
int main()
{
#ifdef IN_GMOJ
	static_cast<void>(freopen("pacifist.in", "r", stdin)); //定义文件输入输出
	static_cast<void>(freopen("pacifist.out", "w", stdout));
#endif
	fread(buf, 1, 16777216, stdin); //读入所有输入
	const ll m = Read(), n = Read(); //读入n和m
	if(n == 1) //特判当n=1时的答案
	{
		printf("%lld 0", Read());
		return 0;
	}
	if(n == m) //特判当n=m时的答案
	{
		putchar('0');
		putchar(' ');
		putchar('1');
		return 0;
	}
	for (ll i = 1; i <= n; ++i) //读入变换后的序列
	{
		a[i] = Read();
	}
	sort(a + 1, a + (n + 1)); //对序列排序
	a[0] = n;
	const bool mode = (n * 2 > m);
	if (mode) //当2n>m时
	{
		for (ll i = 0; i < m; ++i) //计算补集
		{
			if (!Find(i))
			{
				b[++b[0]] = i;
			}
		}
		for (ll i = 0; i <= b[0]; ++i)
		{
			a[i] = b[i];
		}
	}
	ll start, d;
	if (a[0] == 1) //特判序列长度为1的情况
	{
		start = a[1];
		d = 1;
	}
	else
	{
		d = a[2] - a[1]; //寻找x
		ll cnt = 0;
		for (ll i = 1; i <= a[0]; ++i) //统计变换后的序列中差为x的数对的数量
		{
			if (Find((a[i] + d) % m))
			{
				++cnt;
			}
		}
		for (ll t1 = (a[0] - cnt) % m, t2 = m - 2; t2 >= 1; d = ((t2 & 1) == 1 ? d * t1 % m : d), t1 = t1 * t1 % m, t2 >>= 1); //计算d
		start = -1;
		for (ll t = m - d, i = 1; i <= a[0]; ++i) //寻找s
		{
			if (!Find((a[i] + t) % m)) //找到可能的s
			{
				if (start != -1) //如果有多个可能的s
				{
					start = -1; //无解
					break;
				}
				start = a[i]; //保存s
			}
		}
		if (d > m >> 1) //当d大于m/2时
		{
			start = (start + (a[0] - 1) * d) % m; //改变s和d
			d = m - d;
		}
	}
	if (mode && start != -1) //当2n>m时
	{
		start = (start + d * b[0]) % m; //改变s
	}
	printf((start == -1 ? "-1" : "%lld %lld"), start, d); //输出答案
	return 0;
}

总结

这道题虽然对算法的难度要求不高,但对思维的要求很高。做对这道题需要很好的思维。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值