[CF1045B]Space Isaac

280 篇文章 1 订阅
38 篇文章 0 订阅

题目

传送门 to CF

题目概要
求出这个集合:
S = { ( x + y )   m o d   M    ∣    x ∈ A ,    y ∈ B } S=\big\{(x+y)\bmod M\;\big|\;x\in A,\;y\in B\big\} S={(x+y)modMxA,yB}

其中 A A A B B B 是两个整数集合,并满足 A ∪ B = [ 0 , M ) ∩ Z A\cup B=[0,M)\cap\Z AB=[0,M)Z

可以证明 [ 0 , M ) ∩ Z [0,M)\cap\Z [0,M)Z S S S 的补集的大小不超过 ∣ A ∣ |A| A,所以只需输出其补集。

数据范围与约定
1 ⩽ ∣ A ∣ ⩽ 2 × 1 0 5 1\leqslant|A|\leqslant 2\times 10^5 1A2×105 M ⩽ 1 0 9 M\leqslant 10^9 M109

思路

假设 k ∉ S k\notin S k/S 。考虑一个 a    ( a ∈ A ) a\;(a\in A) a(aA) 。若 ( k − a )   m o d   M (k-a)\bmod M (ka)modM B B B 中,那么二者相加即可得到 k k k 。故
∀ a ∈ A ,    ( k − a )   m o d   M ∈ A \forall a\in A,\;(k-a)\bmod M\in A aA,(ka)modMA

所以, A A A 中的元素可以 “配对”,并且每一个元素都出现在了某个 “配对” 中。注意一个元素可以跟自己 “配对”。

从数轴上直观感受,则 A A A 是关于 k k k 对称 的。联想到回文。其实就是 差分数组 构成回文串。当然,要求是 A A A 从小到大排序。

然而问题是在模 M M M 剩余系下的;一个元素 x x x 实际上代表了 x + κ M    ( k ∈ Z ) x+\kappa M\;(k\in\Z) x+κM(kZ) 。在数轴上,把这些数字都标记为 A A A 中元素,就行了。毕竟一个元素本来就被允许用于 “配对” 多次。

不难发现,连续的 n n n 个数总是恰好表示原本的 A A A 。所以在这个 expand- A \text{expand-}A expand-A 上,找到差分值的回文串即可。同时我们发现,这个无限大 expand- A \text{expand-}A expand-A 的差分值是循环的,所以只需要保留 x x x ( x + M ) (x{+}M) (x+M) 。用 m a n a c h e r \tt manacher manacher 即可 O ( n ) \mathcal O(n) O(n) 求出所有答案。

但是输出答案恐怕需要 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn) 排序……

代码

#include <cstdio>
#include <iostream>
#include <vector>
#include <set>
using namespace std;
inline int readint(){
	int a = 0, f = 1; char c = getchar();
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(long long x){
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) writeint(x/10);
	putchar((x%10)^48);
}

# define MB template < typename T >
MB void getMax(T &a,const T &b){ if(a < b) a = b; }
MB void getMin(T &a,const T &b){ if(b < a) a = b; }

const int MaxN = 200020<<2, infty = (1<<30)-1;
int n, r[MaxN], a[MaxN], d[MaxN], M; set<int> ans;

# define add(x,y) ((0ll+(x)+(y))%M)

void manacher(){
	for(int i=0; i<=(n<<1); ++i){
		d[i<<1] = a[i+1]-a[i];
		d[i<<1|1] = -5201314; // 特♀殊字符
	}
	d[0] = -infty, d[n<<2] = infty;
// for(int i=0; i<=(n<<2); ++i)
// 	printf("%d ",d[i]); puts("");
	for(int i=1,id=0; i<(n<<2); ++i){
		if(i < id+r[id])
			r[i] = min(id+r[id]-i,r[id*2-i]);
		while(d[i-r[i]-1] == d[i+r[i]+1])
			++ r[i];
		if(i+r[i] > id+r[id]) id = i;

		if(r[i] >= n-1){
			int x = a[(i+1)>>1], y = a[(i+2)>>1];
			ans.insert(add(x,y));
		}
		// i为偶:a[i>>1]+a[(i>>1)+1]
		// i为奇:a[(i+1)>>1]<<1
	}
}

int main(){
	n = readint(), M = readint();
	for(int i=1; i<=n; ++i)
		a[i] = readint(), a[i+n] = a[i]+M;
	manacher(), printf("%d\n",int(ans.size()));
	for(auto i : ans) printf("%d ",i); puts("");
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值