Tenka1 Programmer Contest: ModularPowerEquation!! (AC解法(解法なんだからACなのは当たり前だろ))

http://tenka1-2017.contest.atcoder.jp/tasks/tenka1_2017_f

http://omochan.hatenablog.com/entry/2017/10/20/004232の続きです。

普通に蟻本通りにやればいいだけでした…。
x≡k(modm)
x≡k'(modm')を満たすxは
x=mt+kとして
mt≡k'-k(modm')をtについて解けばいいだけでした。k,m,k',m'がすべて10^9より小さければ、xはオーバーフローせずに求められます。
具体的にはmt'≡g(modm')(g=gcd(m,m'))となるt'(modm')を求めてからt=(k'-k)/g*t'とすればいいだけです。

modの基本定理のおさらい。

  • g|Mのときga≡gb(modM)<=>a≡b(modM/g)
  • gとMが互いに素の時,ga≡gb(modM)<=>a≡b(modM)

二つとも両辺に寄せてからg(a-b)=Mtとかすれば導出できます。これが一番modの中で基礎となる式かもしれない。modの積と商の変形ルールを述べています。
ちなみに和(と差)は当たり前ですが、
a≡b(modM)<=>a+c≡b+c(modM)です。

四則演算の変形+中国剰余定理でだいぶmodは使いやすくなった?

ll phi(ll M) {
	ll A = M;
	for(ll i = 2; i * i <= A; i++) {
		if(A % i == 0) {
			M = M / i * (i - 1);
			while(A % i == 0) A /= i;
		}
	}
	if(A != 1) M = M / A * (A - 1);
	return M;
}

ll gcd(ll a, ll b) {
	return b == 0 ? a : gcd(b, a % b);
}

ll extgcd(ll a, ll b, ll& x, ll& y) { //get(ax + by = gcd(a, b))
	if(b == 0) {
		x = 1; y = 0;
		return a;
	}
	ll res = extgcd(b, a % b, y, x);
	y -= (a / b) * x;
	return res;
}

ll mod_pow(ll a, ll n, ll mod) { //get a^n mod
	if(n == 0) return 1;
	ll res = mod_pow(a, n / 2, mod);
	if(n % 2 == 0) return res * res % mod;
	else return a * res % mod * res % mod;
}

ll mod_inv(ll a, ll m) { //get x such that ax=gcd(a, m)(modm)
	ll x, y;
	extgcd(a, m, x, y);
	return (x % m + m) % m;
}

int Q;
ll A, M;

ll loop(ll A, ll M) {
	if(M == 1) return 10000;
	ll K = loop(A, phi(M));
	ll tK = mod_pow(A, K, M);
	return tK + (tK < 100 ? M : 0);
}

void solve() {
	cin >> Q;
	while(Q--) {
		cin >> A >> M;
		ll pM = phi(M);
		ll K = loop(A, M) % M;
		ll pK = loop(A, pM) % pM;
		ll pt = mod_inv(M, pM);
		ll g = gcd(M, pM), l = M * pM / g;
		ll t = ((pK - K) / g * pt) % pM;
		//debug(M, pM, K, pK, pt, g, l, t);
		ll pans = ((t * M % l + K) % l + l) % l;
		ll ans = pans < 100 ? pans + l : pans;
		cout << ans << "\n";
		//debug(mod_pow(A, ans, M), ans % M);
	}
}

Codeforces Round #441F: Royal Questions

http://codeforces.com/contest/875/problem/F

princeを点、princessの二人のprinceの候補を辺とみます。それからkruskalっぽいことをします。具体的には
1.違う連結成分どおしで、二つともlockedじゃなければ繋ぐ。
2.同じ連結成分どおしで、lockedじゃなければ繋いでからlockedにする。
この貪欲が成り立つ理由は、連結成分内ではkruskalの要領で最大を示せ、違う連結成分をまたぐ辺が使われないことは、2つの連結成分内にその辺よりもコストが低い辺は存在しないためです。

わかるんだけどもこういうのがコンテスト中に通せるかというと微妙。通せるようになりましょう。

int N, M, E;
edge es[MAX_N];

int kruskal() {
	sort(es, es + M, comp);
	init(N);
	int res = 0;
	for(int i = 0; i < M; i++) {
		edge e = es[i];
		int x = find(e.u), y = find(e.v);
		if(!locked[x] && !locked[y]) {
			if(x != y) {
				unite(x, y);
				res += e.cost;
			}
			else {
				locked[x] = true;
				res += e.cost;
			}
		}
		else if(!locked[x]) {
			unite(x, y);
			res += e.cost;
			locked[find(x)] = true;
		}
		else if(!locked[y]) {
			unite(x, y);
			res += e.cost;
			locked[find(y)] = true;
		}
	}
	return res;
}


void add_edge(int s, int t, int cost) {
	es[E++] = edge{s, t, cost};
}

void solve() {
	E = 0;
	cin >> N >> M;
	rep(i, 0, M) {
		int a, b, c; cin >> a >> b >> c;
		a--; b--;
		add_edge(a, b, c);
	}
	cout << kruskal() << "\n";
}

Tenka1 Programmer Contest: ModularPowerEquation!! (WA解法(は?))

http://tenka1-2017.contest.atcoder.jp/tasks/tenka1_2017_f

ある十分大きいx(A<10^18とかなら100程度でよい)をとればA^x≡A^(x+φ(M))(modM)が成立します。
A^A^A^A...を考えるとKのmodφ(M)とmodMの値が求められるので、あとはユークリッドの互除法をして解を構成すればよい…なんですが、値がでかくなりすぎてWA…どうやって小さくするんだろう…。

ll phi(ll M) {
	ll A = M;
	for(ll i = 2; i * i <= A; i++) {
		if(A % i == 0) {
			M = M / i * (i - 1);
			while(A % i == 0) A /= i;
		}
	}
	if(A != 1) M = M / A * (A - 1);
	return M;
}

ll extgcd(ll a, ll b, ll& x, ll& y) { //get(ax + by = gcd(a, b))
	if(b == 0) {
		x = 1; y = 0;
		return a;
	}
	ll res = extgcd(b, a % b, y, x);
	y -= (a / b) * x;
	return res;
}

ll mod_pow(ll a, ll n, ll mod) { //get a^n mod
	if(n == 0) return 1;
	ll res = mod_pow(a, n / 2, mod);
	if(n % 2 == 0) return res * res % mod;
	else return a * res % mod * res % mod;
}

int Q;
ll A, M;


ll loop(ll A, ll M) {
	if(M == 1) return 10000;
	ll K = loop(A, phi(M));
	ll tK = mod_pow(A, K, M);
	return tK + (tK < 100 ? M : 0);
}

void solve() {
	cin >> Q;
	while(Q--) {
		cin >> A >> M;
		ll pM = phi(M);
		ll K = loop(A, M) % M;
		ll pK = loop(A, pM) % pM;
		debug(K, pK);
		ll x, y;
		ll g = extgcd(M, pM, x, y);
		debug(M, pM, x, y, g);
		ll l = M * pM / g;
		ll ans = (((M / g) * x * pK + (pM / g) * y * K) % l + l) % l + l;
		cout << ans << "\n";
		debug(mod_pow(A, ans, M), ans % M);
	}
}

mod系の問題、すぐオーバーフローして嫌になる…。

POI: Ploughing

https://szkopul.edu.pl/problemset/problem/6YiP6JA5U15hY94pLwuHoYPg/site/?key=statement

実践的アルゴリズム貪欲編です。

少し観察すると(列をstripする回数==M)または(行をstripする回数==N)が成立することがわかります。
対称なので(列をstripする回数==M)とします。
行について上からstripする回数と下からstripする回数を固定すれば、列は貪欲的にstripを行えます。
なのでdp[上からstripする回数][下からstripする回数]=make_pair(左から何回stripできるか,右から何回stripできるか) を埋めればよくて、それはならし計算量O((N+M)^2)で行うことができます。

ほそながいところより難しいということになっていましたが、こっちの方がまだ考えやすいと思いました。

int K, N, M;
int XS[2010][2010];//x-axis way sum,[ )
int YS[2010][2010];//y-axis way sum,[ )
pi dp[2010][2010];//[0, l) and [r, N)

pi narrow(int xl, int xr, int yl, int yr) { //xl, xr:constant, narrow yl and yr. [0, yl) and [yr, M)
	while(yr - yl >= 1 && XS[xr][yl] - XS[xl][yl] <= K) {
		yl++;
	}
	while(yr - yl >= 1 && XS[xr][yr - 1] - XS[xl][yr - 1] <= K) {
		yr--;
	}
	return pi(yl, yr);
}

int solve_sub() {
	int res = inf;
	rep(i, 0, N + 1) 
		rep(j, i + 1, N + 1) dp[i][j] = pinf;
	dp[0][N] = narrow(0, N, 0, M);
	rep(i, 0, N + 1) {
		rer(k, N + 1, 2) {
			int j = i + k;
			if(j > N || dp[i][j] == pinf) continue;
			int yl = dp[i][j].fst, yr = dp[i][j].sec;
			if(dp[i + 1][j] == pinf && YS[i][yr] - YS[i][yl] <= K) {
				dp[i + 1][j] = narrow(i + 1, j, yl, yr);
			}
			if(dp[i][j - 1] == pinf && YS[j - 1][yr] - YS[j - 1][yl] <= K) {
				dp[i][j - 1] = narrow(i, j - 1, yl, yr);
			}
		}
	}
	rep(i, 0, N + 1) {
		rep(j, 0, N + 1) {
			if(dp[i][j] == pinf) continue;
			if(dp[i][j].sec - dp[i][j].fst == 0) {
				MIN(res, M + i + N - j);
			}
		}
	}
	return res;
}

void solve() {
	cin >> K >> M >> N;
	rep(i, 0, N) {
		rep(j, 0, M) cin >> XS[i + 1][j];
	}
	rep(i, 0, N) 
		rep(j, 0, M) YS[i][j + 1] = YS[i][j] + XS[i + 1][j];
	rep(j, 0, M)
		rep(i, 1, N + 1) XS[i + 1][j] += XS[i][j];
	int res = inf;
	MIN(res, solve_sub());
	rep(i, 0, max(N, M) + 1) {
		rep(j, i + 1, max(N, M) + 1) {
			swap(XS[i][j], XS[j][i]);
			swap(YS[i][j], YS[j][i]);
		}
	}
	swap(XS, YS);
	swap(N, M);
	MIN(res, solve_sub());
	cout << res << "\n";
}

無駄にMLEが厳しいのやめて…

POJ 1284: Primitive Roots

http://poj.org/problem?id=1284

答えはφ(p-1)個になります。証明しましょう。

準備.n(|p-1)についてx^n≡1(modp)はちょうどn個の解を持つ。

証明.p-1=nkとしたとき、
x^(p-1)-1=(x^n-1)(x^(n(k-1))+x^(n(k-1)-1)+...+1)であり、
x^(p-1)-1≡0(modp)の解はp-1=nk個。
x^n-1≡0(modp)の解はn個以下
x^(n(k-1))+x^(n(k-1)-1)+...+1≡0の解はn(k-1)個以下なので
x^n-1≡(modp)の解はn個でなければならない。

n個の解x1,...,xnについてx1^a≡1(modp)を満たす最小の自然数aを位数と定義すると、位数はnの約数しかありえないので、
位数aの解の個数をF(a)と置くと、Σ(a|n)F(a)=nとなる。これがp-1の任意の約数nについて成立している。
またF(1)=1
F(p)=p-1
F(p^2)=p^2-p(pは素数)
F(ab)=F(a)F(b)(aとbは互いに素)が容易に示せ、帰納的にF(a)=φ(a)が成立する。
よって位数p-1の解の個数はF(p-1)=φ(p-1)となる。


x^k≡1(modp)となる集合A(k)を包除原理して求めてもよい。φ(n)を求めるときと合同な集合が出てくる。

ちなみに任意の自然数についてはφ(φ(N))個となります。


ほかにもこの証明をする過程でいろいろ考えた定理を挙げておきます。

  • AとNが互いに素の時、A^φ(N)≡1(modN)(証明はA*2A*...*φ(N)A≡1*2*...*φ(N)(modN)より)

(AとNが互いに素ではないときはgcd(A,N)に含まれている素因数を除いたA',N'について適応する)

  • A|Bの時φ(A)|φ(B) (φ(A)=A*(p1-1)/p1*(p2-1)/p2から簡単に示せる)
  • 十分大きなKをとればA^(K+φ(N))≡A^K(modN)(フェルマーの定理と中国剰余定理から)

これよりA^K(modN)の値はK modφ(N)に依存。

  • 素数Pの原始根の1つをgとしたとき1<=z<=p-1なる自然数zについてz≡g^i(modP)となるiが存在する。(原始根の定義そのもの)

これを利用して原始根の数がφ(φ(N))となることを証明する方法もある。

  • 原始根を効率よく見つけるアルゴリズムはない。(だから暗号とかがうまくいく)
int N;
int phi[100010];

int main() {
	rep(i, 1, 100000) phi[i] = i;
	rep(i, 2, 100000) {
		if(phi[i] != i) continue;
		for(int j = i; j < 100000; j += i) {
			phi[j] -= phi[j] / i;
		}
	}
	while(scanf("%d", &N) != EOF) {
		printf("%d\n", phi[N - 1]);
	}
}

POJ 2115: C Looooopsなど

POJ 2115:
http://poj.org/problem?id=2115

A+Cx=B+(2^K)yの解(x,y)のうちxが非負で最小のものを求めればよい。これはユークリッドの互除法やるだけで求められるが、少し遠回りに書いたらREを起こしてえぇ…ってなりました。たぶんオーバーフロー起こしてましたね。シンプルに書いてAC。

ll extgcd(ll a, ll b, ll& x, ll& y) {
	if(b == 0) {
		x = 1; y = 0;
		return a;
	}
	ll res = extgcd(b, a % b, x, y);
	ll tx = x, ty = y;
	x = -ty; y = -tx - (a / b) * ty;
	return res;
}

int main() {
	while(true) {
		ll A, B, C, K;
		scanf("%lld%lld%lld%lld", &A, &B, &C, &K);
		if(A == 0 && B == 0 && C == 0 && K == 0) break;
		K = (1LL << K);
		ll x, y;
		ll G = extgcd(K, C, y, x);
		if((A - B) % G != 0) {
			printf("FOREVER\n");
			continue;
		}
		K /= G; C /= G;
		x = x * ((A - B) / G);
		printf("%lld\n", (x % K + K) % K);
	}
	return 0;
}

POJ 2345:
http://poj.org/problem?id=2345

いっけんムリそうですが、行列の形で書き下すと連立方程式解くだけでよいことがわかるので掃き出し法などすればよいです。
ムリそうだなぁと思ったらフローとか数学とかで考えるのがよさそう。

bool gauss_jordan(mat& A, vi& B) {
	int n = sz(B);
	rep(j, 0, n) {
		int at = -1;
		rep(i, j, n) {
			if(A[i][j] == 1) {
				at = i;
				break;
			}
		}
		if(at == -1) continue;
		swap(A[j], A[at]); swap(B[j], B[at]);
		rep(i, 0, n) {
			if(i == j || A[i][j] == 0) continue;
			B[i] = B[i] ^ B[j];
			rep(k, 0, n) {
				A[i][k] = A[i][k] ^ A[j][k];
			}
		}
	}
	rep(i, 0, n) {
		bool found = false;
		rep(j, 0, n) {
			if(A[i][j]) {
				found = true; break;
			}
		}
		if(!found && B[i]) return false;
	}
	return true;
}

int N;

int main() {
	scanf("%d", &N);
	mat A(N, vi(N, 0));
	rep(i, 0, N) {
		while(true) {
			int a;
			scanf("%d", &a); a--;
			if(a == -2) break;
			A[a][i] = 1;
		}
	}
	vi B(N, 1);
	if(gauss_jordan(A, B)) {
		vi ans;
		rep(i, 0, N) {
			if(B[i]) ans.pb(i + 1);
		}
		rep(i, 0, sz(ans)) {
			printf("%d%c", ans[i], i != sz(ans) - 1 ? ' ' : '\n');
		}
	}
	else printf("No solution\n");
	return 0;
}

POJ 1150: The Last Non-zero Digit

http://poj.org/submit?problem_id=1150

初中国剰余定理かもしれない。
(X/10^k)mod10を求めればいいです。ここでkはXが10で割り切れる回数です。
中国剰余定理より
(X/10^k)mod10 <=> (X/10^k)mod2, (X/10^k)mod5となります。
対称性よりmod5についてのみ注目して考えましょう。
X=a*5^eとします。ここでa mod5 != 0です。a mod5とeは蟻本の方法で求められます。するとe>=kが成立するのは明らかです。
e>kのとき、(X/10^k)mod5 = 0です。
e=kのとき、(X/10^k)≡(X/5^k)*(1/2^k)≡a*inv[2^k](mod5)となるので求められます。

int fact[6][11];
int inv[6][11];

ll mod_pow(ll a, ll n, ll mod) {
	if(n == 0) return 1;
	ll res = mod_pow(a, n / 2, mod);
	if(n % 2 == 0) return res * res % mod;
	else return a * res % mod * res % mod;
}


int mod_fact(int n, int p, int &e) {
	e = 0;
	if(n == 0) return 1;
	int res = mod_fact(n / p, p, e);
	e += n / p;

	if((n / p) % 2 != 0) return res * (p - fact[p][n % p]) % p;
	else return res * fact[p][n % p] % p;
}

int N, M;
int ans[5][10];

int main() {
	fact[2][0] = fact[2][1] = 1;
	inv[2][1] = 1;
	fact[5][0] = fact[5][1] = 1;
	inv[5][1] = 1;
	rep(i, 2, 5) {
		fact[5][i] = fact[5][i - 1] * i % 5;
		inv[5][i] = 5 - inv[5][5 % i] * (5 / i) % 5;
	}
	rep(i, 0, 10) {
		ans[i % 2][i % 5] = i;
	}
	while(scanf("%d%d", &N, &M) != EOF) {
		int e2_1, e2_2;
		int e5_1, e5_2;
		int a2_1 = mod_fact(N, 2, e2_1);
		int a2_2 = mod_fact(N - M, 2, e2_2);
		int a5_1 = mod_fact(N, 5, e5_1);
		int a5_2 = mod_fact(N - M, 5, e5_2);
		int a2 = a2_1 * inv[2][a2_2] % 5;
		int a5 = a5_1 * inv[5][a5_2] % 5;
		int e2 = e2_1 - e2_2;
		int e5 = e5_1 - e5_2;
		if(e2 > e5) {
			a5 = a5 * inv[5][mod_pow(2, e5, 5) % 5] % 5;
			printf("%d\n", ans[0][a5]);
		}
		else if(e2 == e5) {
			a5 = a5 * inv[5][mod_pow(2, e5, 5) % 5] % 5;
			a2 = a2 * inv[2][mod_pow(5, e2, 2) % 2] % 2;
			printf("%d\n", ans[a2][a5]);
		}
		else {
			a2 = a2 * inv[2][mod_pow(5, e2, 2) % 2] % 2;
			printf("%d\n", ans[a2][0]);
		}
	}
}