Loading

P11211 随机数生成器 题解

前置知识:原根,exCRT

首先 \(t=1\) 是容易的,直接相邻的除一下即可。


否则考虑询问除连续的 \(5\) 个数,分别为 \(a_0,a_1,\cdots,a_4\)

首先特判掉存在 \(a_i=0\) 的情况,此时直接枚举 \(s\) 即可。

我们先求出 \(p\) 的一个原根 \(g\),设离散对数 \(\log(x)=y\) 表示 \(g^y\equiv x\pmod p\)

否则我们枚举 \(x\),并且 \(x\sim x+4\) 没有一个是 \(\bmod p=0\)

此时我们有 \(5\) 个线性同余方程:\(\forall 0\le i<5,s\log (x+i)\equiv \log a_i\pmod {p-1}\)

除掉 \(\gcd\),留下合法的方程,然后 exCRT 合并所有方程。

最终我们得到了 \(s\equiv A\pmod B\),其中 \(B\)\(p-1\) 的因子。

然后我们枚举 \(s=Bk+A,k\in [0,(p-1)/B)\),然后判断 \((s,x)\) 是否合法即可。

复杂度是所有的 \((p-1)/B\) 之和乘一些 exgcd\(\log\)

写个程序算一下,这个和的上界大概是不超过 \(2p\) 的,即 \(4\times 10^6\),常数稍微写优秀一点就足以通过了。

#include<bits/stdc++.h>
#define LL long long
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
#define ff fflush(stdout)
using namespace std;
const int N=2e6+5;
int id,mod,g,a[5],lg[N];bool v[N];
inline int ask(){puts("?");ff;int x;scanf("%d",&x);return x;}
inline int md(int x){return x>=mod?x-mod:x;}
inline int ksm(int x,int p){int s=1;for(;p;(p&1)&&(s=1ll*s*x%mod),x=1ll*x*x%mod,p>>=1);return s;}
#define mytz __builtin_ctz
inline int gcd(int a,int b)
{
	int az=mytz(a),bz=mytz(b),z=min(az,bz),diff;b>>=bz;
	while(a) a>>=az,diff=a-b,az=mytz(diff),b=min(a,b),a=abs(diff);return b<<z;
}
namespace GG
{
	int n;vector<int>g;
	inline int ksm(int x,int p){int s=1;for(;p;(p&1)&&(s=1ll*s*x%n),x=1ll*x*x%n,p>>=1);return s;}
	inline bool isy(int x){if(gcd(x,n)>1) return 0;for(int i:g) if(ksm(x,i)==1) return 0;return 1;}
	inline int gg(int x)
	{
		g.clear();int t=(n=x)-1,y=t;
		for(int i=2;i*i<=y;i++) if(y%i==0){while(y%i==0) y/=i;g.push_back(t/i);}(y^1)&&(g.push_back(t/y),1);
		for(int i=1;i<x;i++) if(isy(i)) return i;
	}
}//离散对数板子
inline bool chk(int x,int s){for(int i=0;i<5;i++) if(1ll*s*lg[md(x+i)]%(mod-1)!=a[i]) return 0;return 1;}
void exgcd(int a,int b,int &x,int &y)
{
	if(!b) return x=1,y=0,void();
	exgcd(b,a%b,x,y);int t=x;
	x=y;y=(t-(a/b)*y);
}
inline int inv(int x,int p){int a,b;exgcd(x,p,a,b);return (a%p+p)%p;}
inline void mg(int &X,int &L,int x,int y)
{
	if(!L) return L=x,X=y,void();int _x,_y,ll=L/gcd(L,x)*x;
	exgcd(L,x,_x,_y);_x=(LL)(y-X)/gcd(L,x)*_x%ll;_x=(_x+ll)%ll;
	X=((LL)L*_x+X)%ll;L=ll;
}//exgcd,excrt 板子
int main()
{
	scanf("%d%d",&id,&mod);
	if(id==1)
	{
		int A=ask(),B=ask();
		return printf("! %d\n",1ll*B*ksm(A,mod-2)%mod),0;
	}
	g=GG::gg(mod);
	for(int i=0,s=1;i<mod-1;i++,s=1ll*s*g%mod) lg[s]=i;//原根离散对数
	for(int i=0;i<5;i++) a[i]=ask();
	for(int i=0;i<5;i++) if(!a[i])
	{
		int x=md(mod-i);
		for(int s=0;s<mod-1;s++) if(chk(x,s))
			return printf("! %d\n",s),0;
	}
	for(int i=0;i<5;i++) a[i]=lg[a[i]];
	for(int x=1;x+4<mod;x++)//枚举 x
	{
		int X=0,L=0;bool o=1;
		for(int j=0;j<5;j++)
		{
			int t=md(j+x);
			if(t==1) continue;
			int A=lg[t],B=a[j],C=mod-1,g=gcd(A,C);
			if(B%g){o=0;break;}A/=g,B/=g,C/=g;//除掉 gcd
			mg(X,L,C,1ll*B*inv(A,C)%C);//excrt 合并同余方程
		}
		if(!o) continue;
		for(int j=X;j<mod-1;j+=L) if(chk(x,j))
			return printf("! %d\n",j),0;//枚举求解
	}
	printf("! 1\n");
	return 0;
}
posted @ 2024-10-20 23:02  HaHeHyt  阅读(53)  评论(0编辑  收藏  举报