kmjp's blog

競技プログラミング参加記です

yukicoder : No.984 Inversion

こういう問題苦手なんだよね…。
https://yukicoder.me/problems/no/984

問題

素数PとP未満の正整数Nが与えられる。
P-1項からなる数列A[i]=(i+1)*N % Pを考える。
この数列の転倒数を求めよ。

解法

P=2の時は自明。以下、Pが奇数の場合を考える。
A[i]=i+1とすると、B[i]=A[i]*N%Pとなる。
このときAとBの転倒数の偶奇を考える。当然Aの転倒数は0である。
Pの適当な原子根rを取って対数を考えたとき、A'[i]=log(A[i])、B'[i]=log(B[i])とする。
A→A'→B'の転倒数の偶奇の変化とA→B→B'の転倒数の偶奇の変化は等しいはずである。
A→A'とB→B'はいずれもlogを取る作業で同じ置換を取る作業なので、転倒数の偶奇の変化は等しい。
とするとA→BとA'→B'の変化も等しいはずなので、結局A'→B'の転倒数の偶奇の変化が求める会となる。

B'[i]=(A'[i]+log(N))%Pなので、Pが奇数であることを考えるとlog(N)が奇数だと各要素の偶奇が反転し、転倒数の偶奇も反転する。
あとはBaby-Step Giant-Stepでlog(N)を求めればよい。

ll N,P,mo;

ll modpow(ll a, ll n = mo-2, ll mo=mo) {
	ll r=1;
	while(n) r=r*((n%2)?a:1)%mo,a=a*a%mo,n>>=1;
	return r;
}

ll modlog(int g,int a) {  // return g^x=a mod a
	map<int,int> M;
	ll cur=1;
	int sq=sqrt(mo);
	int i;
	FOR(i,sq) {
		M[cur]=i;
		cur=cur*g%mo;
	}
	
	ll step=cur;
	cur=1;
	ll num=0;
	int lp=0;
	while(1) {
		ll x=a*modpow(cur)%mo;
		if(lp++>sq+5) return 1LL<<50;
		if(M.count(x)) return num+M[x];
		cur=cur*step%mo;
		num+=sq;
	}
}

int get_prim(int p) { //primitive element of prime p
	int q=p-1;
	vector<int> V;
	ll x;
	for(x=2;x*x<=p;x++) if(q%x==0) V.push_back(x),V.push_back(q/x);
	
	for(x=2;;x++) {
		int ng=0;
		FORR(v,V) if(modpow(x,v,p)==1) {
			ng=1;
			break;
		}
		if(ng==0) return x;
	}
	
}


void solve() {
	int i,j,k,l,r,x,y; string s;
	
	
	cin>>P>>N;
	mo=P;
	if(N==1 || P==2) return _P("0\n");
	
	x=modlog(get_prim(P),N);
	cout<<x%2<<endl;
	
	
	
}

まとめ

そこでlogを使うとさっと思い浮かばないな…。