Particle

競技プログラミングについての雑記

SRM 657 Div1 Medium PolynomialGCD

素数ごとに独立になっているので、それぞれ解いて解を掛け合わせればよい。
素数 p の出現回数が一番多くなる場所を決めると、全体で何回 p が出現するかを O(NlogN/p) 程度で計算でき、場所の個数は、Nなので、各素数ごとに、O(N^2logN/p) で計算できる。
全体では、O(Nlog^2N) 程度で計算できる。

コーナーケースとして、s = "19592" のようなケースに注意する必要がある (1WA)
この例では、2 で 12 回割り切ることができる。 (一番左の 1 に 8 を割り当てるのが最適となるが、考察を間違えると、左右の 1 と 2 に 4 を割り当ててしまう。)

bool isPrime(ll x){
	if(x<=1) return false;
	for(ll i = 2; i*i <= x; i++) if(x%i==0) return false;
	return true;
}
ll pow_mod(ll a, ll r, ll m){
	ll x = 1;
	while(r){
		if(r&1) (x*=a)%=m;
		(a*=a)%=m;
		r>>=1;
	}
	return x;
}
 
class PolynomialGCD {
	public:
	int gcd(string s) {
		int n = s.size();
		vector<int> v(n);
		rep(i, n) v[i] = s[i]-'0';
		ll res = 1;
		for(ll i = 2; i <= n; i++){
			if(!isPrime(i)) continue;
			ll cnt = INF;
			for(ll j = 0; j < n; j++){
				ll c = 0, c2 = INF;
				ll x = i;
				while(c2>=i){
					c2 = 0;
					for(ll k = j%x; k < n; k += x) c += v[k], c2++;
					x *= i;
				}
				chmin(cnt, c);
			}
			(res*=pow_mod(i, cnt, mod))%=mod;
		}
		return res;
	}
};

SRM 654 Div1 Medium SuccessiveSubtraction2

区間を 2 つ選んで符号を反転させることができるので、区間和の最小値が計算できれば、あとは簡単に計算できる。
これは、O(N) でやってよいので、配列が更新される度に最初から計算し直せば良い。(解いているときは何も考えずに、StarrySky Tree を貼ってしまい、計算量が余計に log 倍されてしまった。)

どちらの解法でも TLE しているが、不具合が無ければ通るはず。

配列版 O(QN)

#define N 2010
int dp[N], dp2[N];
int s[N], s2[N];

class SuccessiveSubtraction2 {
	public:
	vector <int> calc(vector <int> a, vector <int> p, vector <int> v) {
		int n = a.size(), m = p.size();
		vector<int> res;
		mset(dp, 0); mset(dp2, 0); mset(s, 0); mset(s2, 0);

		rep(i, m){
			a[p[i]] = v[i];
			ll sum = a[0];
			for(int j = 1; j < n; j++) sum -= a[j];
			if(n<=2){
				res.pb(sum);
				continue;
			}
			for(int j = 2; j < n; j++) s[j+1] = s[j]+a[j];
			for(int j = n-1; j >= 2; j--) s2[j] = s2[j+1]+a[j];
			for(int j = 2, mn = 0; j <= n; j++){
				chmin(mn, s[j]);
				dp[j] = max(dp[j-1], s[j]-mn);
			}
			for(int j = n-1, mn = 0; j >= 1; j--){
				chmin(mn, s2[j]);
				dp2[j] = max(dp2[j+1], s2[j]-mn);
			}
			int r = -INF;
			for(int j = 2; j < n; j++) chmax(r, dp[j]+dp2[j]);
			res.pb(sum+2*r);
		}
		return res;
	}
};

StarrySky Tree 版 O(QNlogN)

struct starrysky {
	int seg_n;
	vector<ll> data, datb;
	void init(int n, ll initial_value = 0){
		seg_n = 1;
		while(seg_n<n) seg_n<<=1;
		data = vector<ll>(seg_n*2-1, 0);
		datb = vector<ll>(seg_n*2-1, 0);
	}

	// min(v[a], v[a+1],..., v[b-1])
	// to use: query(a, b);
	ll rmin(int a, int b, int k, int l, int r){
		if(r <= a || b <= l) return INF;
		if(a <= l && r <= b) return data[k]+datb[k];
		return min(rmin(a, b, k*2+1, l, (l+r)/2), rmin(a, b, k*2+2, (l+r)/2, r))+data[k];
	}

	ll rmin(int a, int b){
		return rmin(a, b, 0, 0, seg_n);
	}

	void radd(int a, int b, ll x, int k, int l, int r){
		if(r <= a || b <= l) return;//return INF;
		if(a <= l && r <= b){
			data[k] += x;
			return;
		} else {
			radd(a, b, x, k*2+1, l, (l+r)/2);
			radd(a, b, x, k*2+2, (l+r)/2, r);
			datb[k] = min(datb[k*2+1]+data[k*2+1], datb[k*2+2]+data[k*2+2]);
		}
	}

	void radd(int a, int b, ll x){
		radd(a, b, x, 0, 0, seg_n);
	}
};

#define N 2010
int dp[N], dp2[N];

class SuccessiveSubtraction2 {
	public:
	vector <int> calc(vector <int> a, vector <int> p, vector <int> v) {
		int n = a.size(), m = p.size();
		vector<int> res;
		starrysky x, y;
		x.init(n+2); y.init(n+2);
		ll sum = a[0];
		for(int i = 1; i < n; i++) sum -= a[i];
		for(int i = 2; i < n; i++){
			x.radd(i, n+2, a[i]);
			y.radd(0, i, a[i]);
		}
		rep(i, m){
			if(p[i]==0) sum += v[i]-a[0];
			else sum -= v[i]-a[p[i]];
			if(p[i]<2) a[p[i]] = v[i];
			else {
				int d = v[i]-a[p[i]];
				x.radd(p[i], n+2, d);
				y.radd(0, p[i], d);
				a[p[i]] = v[i];
			}
			int r = -INF;
			mset(dp, 0); mset(dp2, 0);
			for(int j = 2; j < n; j++){
				int mn = x.rmin(j, j+1)-x.rmin(0, j+1);
				dp[j] = max(dp[j-1], mn);
			}
			for(int j = n-1; j >= 2; j--){
				int mn = y.rmin(j, j+1)-y.rmin(j, n);
				dp2[j] = max(dp2[j+1], mn);
			}
			for(int j = 2; j < n; j++) chmax(r, dp[j]+dp2[j+1]);
			if(n<=2) r = 0;
			res.pb(sum+r*2);
		}
		return res;
	}
};

SRM 652 Div1 Medium MaliciousPath

K = 0 のときは、 dijkstra で解ける。
頂点を *1 と拡張すると、K>0 の場合も dijkstra を多少書き換えるだけで解くことができる。
辺の長さが少し特殊なので、k=0..K として順番に dijkstra を行うことで効率よく最短路が求まる。
計算量は、O(K(N+M)logN)となる。

下記コードは editorial や他人の解説記事を読んだ感じではおそらく正しいが、practice ルームでは TLE となるので注意。(同じような現象が最近 (2018/02/07) よく起こるので、サーバ側の問題だと思う)

#define NN 1010
vector<P> g[NN], g2[NN];
ll d[NN][NN], d2[NN][NN];

class MaliciousPath {
	public:
	long long minPath(int n, int K, vector <int> f, vector <int> t, vector <int> c) {
		rep(i, n) g[i].clear();
		rep(i, n) g2[i].clear();
		int m = f.size();
		rep(i, m){
			g[f[i]].pb(P(c[i], t[i]));
			g2[t[i]].pb(P(c[i], f[i]));
		}
		rep(i, n){
			sort(all(g[i]));
			sort(all(g2[i]));
		}
		rep(i, n) fill(d[i], d[i]+NN, INF);
		mset(d2, 0);
		rep(k, K+1) d[n-1][k] = 0;
		rep(k, K+1){
			priority_queue<P, vector<P>, greater<P>> q;
			q.push(P(0, n-1));
			while(!q.empty()){
				P p = q.top(); q.pop();
				ll l = p.fst, u = p.snd;
				if(d[u][k]<l) continue;
				for(auto x: g2[u]){
					ll l2 = max(d2[x.snd][k], l+x.fst), v = x.snd;
					if(d[v][k]>l2){
						d[v][k] = l2;
						q.push(P(l2, v));
					}
				}
			}
			rep(i, m) chmax(d2[f[i]][k+1], d[t[i]][k]+c[i]);
		}
		return d[0][K]==INF?-1:d[0][K];
	}
};

*1:頂点), (token数

SRM 651 Div1 Medium FoxConnection3

2次元グリッド上にきつねがN匹いる (N <= 6)
すべてのきつねが辺で連結になるように動かすとき、最小で何ステップでできるか?(きつねは重なることは出来ない)

まず、1次元の場合には、中央値に寄せるのが一番効率がよい。 (N が偶数のときは、真ん中の2つの値の平均を中央値とする。)
2次元の場合にもできるだけ中央に寄せるのがよい。
中央に近づけたあとは、高々 5*5 マスのグリッド上にすべてのきつねがいることになるので、BFS で調べれば良い。(状態数は、高々25C6 (=177,100)で、遷移も高々24なので、map を使っても十分間に合う。)

vector<int> x, y, x2, y2;
int n;
map<vector<int>, int> dp;
int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};
int h, w;

struct UF {
	vector<int> par, rank, sz;
	
	void init(int n){
		par = rank = vector<int>(n, 0);
		sz = vector<int>(n, 1);
		for(int i = 0; i < n; i++){
			par[i] = i;
		}
	}
	
	int find(int x){
		if(par[x] == x){
			return x;
		}else{
			return par[x] = find(par[x]);
		}
	}

	int size(int x){
		return sz[find(x)];
	}
	
	void unite(int x, int y){
		x = find(x);
		y = find(y);
		if(x == y) return;
		
		if(rank[x] < rank[y]){
			par[x] = y;
			sz[y] += sz[x];
		}else{
			par[y] = par[x];
			if(rank[x] == rank[y]) rank[y]++;
			sz[x] += sz[y];
		}
	}
	
	bool same(int x, int y){
		return find(x) == find(y);
	}
};


class FoxConnection3 {
	public:
	bool check(vector<int> &v){
		UF uf;
		uf.init(n);
		rep(i, n) rep(j, n){
			int a = v[i]/w, b = v[i]%w, c = v[j]/w, d = v[j]%w;
			if(abs(a-c)+abs(b-d)==1) uf.unite(i, j);
		}
		return uf.size(0)==n;
	}
	void comp(vector<int> &x, ll &res){
		for(int i = 4; i >= 0; i--){
			int cnt = 0, cnt2 = 0;
			rep(j, n){
				if(x[j]==i) cnt++;
				else if(x[j]>i) cnt2++;
			}
			if(cnt==0){
				rep(j, n) if(x[j]>i) x[j]--;
				res += min(cnt2, n-cnt2);
			}
		}
	}
	long long minimalSteps(vector <int> x_, vector <int> y_) {
		x=x_;y=y_;
		n = x.size();
		x2=x; y2=y;
		sort(all(x2)); sort(all(y2));
		ll xx = ((ll)x2[n/2]+x2[(n-1)/2])/2, yy = ((ll)y2[n/2]+y2[(n-1)/2])/2;
		cerr<<xx<<endl;
		ll res = 0;
		rep(i, n){
			if(x[i]<xx-2){
				res += xx-2-x[i];
				x[i] = xx-2;
				rep(_, 2) rep(j, n) if(i!=j&&x[i]==x[j]&&y[i]==y[j]) x[i]++,res++;
			}
			if(y[i]<yy-2){
				res += yy-2-y[i];
				y[i] = yy-2;
				rep(_, 2) rep(j, n) if(i!=j&&x[i]==x[j]&&y[i]==y[j]) y[i]++,res++;
			}
			if(x[i]>xx+3){
				res += x[i]-xx-3;
				x[i] = xx+3;
				rep(_, 2) rep(j, n) if(i!=j&&x[i]==x[j]&&y[i]==y[j]) x[i]--,res++;
			}
			if(y[i]>yy+3){
				res += y[i]-yy-3;
				y[i] = yy+3;
				rep(_, 2) rep(j, n) if(i!=j&&x[i]==x[j]&&y[i]==y[j]) y[i]--,res++;
			}
		}
		rep(i, n) x[n-i-1] -= xx-2;
		rep(i, n) y[n-i-1] -= yy-2;
		comp(x, res);
		comp(y, res);
		h = w = 1;
		rep(i, n) chmax(h, x[i]+1);
		rep(i, n) chmax(w, y[i]+1);
		vector<int> v(n);
		rep(i, n) v[i] = x[i]*w+y[i];
		sort(all(v));
		dp[v] = 0;
		queue<vector<int>> q;
		q.push(v);
		while(true){
			vector<int> vv = q.front(); q.pop();
			int t = dp[vv];
			if(check(vv)) return res+t;
			for(auto &a: vv){
				int aa = a;
				rep(i, 4){
					int ax = aa/w+dx[i], ay = aa%w+dy[i];
					if(ax<0||ax>=h||ay<0||ay>=w) continue;
					a = ax*w+ay;
					vector<int> vv2(vv);
					int cnt = 0;
					rep(j, n) if(vv2[j]==a) cnt++;
					if(cnt>1) continue;
					sort(all(vv2));
					if(!dp.count(vv2)){
						dp[vv2] = t+1;
						q.push(vv2);
					}
				}
				a = aa;
			}
		}
	}
};

SRM 650 Div1 Medium TheKingsRoadsDiv1

グラフ G が与えられる。G が深さ9以下の完全二分木に3本の余計な辺を追加したグラフであるかを判定する問題である。

色々な解法が考えられるが、根の候補を小さくしてから、それが正しいか判定するという方針で解いた。
根の候補を小さくする部分は、
全点間距離を O(N^2) で前計算し、u からの距離が h 以上の頂点の頂点が無く、 距離が h-1 である頂点が floor(2^(h-3)) 個以上存在する頂点 u を候補とすると、だいたい 1 個くらいに絞ることができる。(未証明)

根を固定したあとは、取り除く辺を全部試せば良い。
完全二分木は、葉以外の頂点は子が2つあるという性質を用いて効率よく全探索できる。深さt と、深さ t の頂点集合と、使用済み頂点集合 (used配列) を状態として持つような dfs を行った。(下記コード参照)
下記コードでは、next_permutation を用いて無駄な計算を行っているが、高々、O(N) * (5!)*(4!)*(3!) 程度で計算できる。

#define N 1030
int d[N][N];
vector<int> g[N];
int n, h;
bool used[N];

class TheKingsRoadsDiv1 {
	public:
	void bfs(int u0){
		int *du = &d[u0][0];
		fill(du, du+N, INF);
		du[u0] = 0;
		queue<int> q;
		q.push(u0);
		while(!q.empty()){
			int u = q.front(); q.pop();
			for(auto v: g[u]){
				if(du[v]>du[u]+1){
					du[v] = du[u]+1;
					q.push(v);
				}
			}
		}
	}
	bool dfs(vector<int> us, int t){
		if(t==h-1) return true;
		for(auto u: us) used[u] = true;
		vector<vector<int>> xs;
		vector<int> vs;
		for(auto u: us){
			vector<int> x;
			for(auto v: g[u]){
				if(used[v]) continue;
				x.pb(v);
			}
			if(x.size()>=6||(x.size()<2)){
				for(auto u: us) used[u] = false;
				return false;
			}
			if(x.size()==2){
				vs.pb(x[0]);
				vs.pb(x[1]);
				continue;
			}
			sort(all(x));
			xs.pb(x);
		}
		int l = xs.size();
		if(l<=0){
			if(vs.size()==(1<<(t+1)) && dfs(vs, t+1)) return true;
			for(auto u: us) used[u] = false;
			return false;
		}

		while(true){
			do{
				vector<int> vs2(vs);
				for(auto x: xs){
					rep(i, 2) vs2.pb(x[i]);
				}
				sort(all(vs2)); UNIQUE(vs2);
				if(vs2.size()!=(1<<(t+1))) continue;
				if(dfs(vs2, t+1)) return true;
			}while(next_permutation(all(xs[l-1])));
			for(int i = l-2; ; i--){
				if(i<0){
					for(auto u: us) used[u] = false;
					return false;
				}
				if(next_permutation(all(xs[i]))){
					for(int j = i+1; j <= l-1; j++) sort(all(xs[j]));
					break;
				}

			}
		}
	}
	string getAnswer(int h_, vector <int> a, vector <int> b) {
		h=h_;
		mset(used, 0);
		n = (1<<h)-1;
		rep(i, n) g[i].clear();
		rep(i, a.size()){
			a[i]--; b[i]--;
			g[a[i]].pb(b[i]);
			g[b[i]].pb(a[i]);
		}
		rep(i, n) bfs(i);
		int lb = 1<<(max(h-3, 0));
		rep(i, n){
			int cnt = 0;
			rep(j, n){
				if(d[i][j]==h-1) cnt++;
				if(d[i][j]>=h) cnt = -INF;
			}
			if(cnt >= lb){
				vector<int> x;
				x.pb(i);
				if(dfs(x, 0)) return "Correct";
			}
		}
		return "Incorrect";
	}
};

SRM 646 Div1 Medium TheGridDivOne

制約を見ると明らかに座標圧縮なので、座標圧縮して dijkstra 等のアルゴリズムで最短経路を求めると解ける。

出題された当時は地力 (実装力) が足りず解けなかったが、今見ると、特に難しいところのない素直な問題に見える。

#define N 150
ll d[N][N];

vector<int> x, y, x2, y2;
int n;
map<int, int> xd, yd;
int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};

class TheGridDivOne {
	public:
	int find(vector <int> x_, vector <int> y_, ll k) {
		x=x_; y=y_;
		n = x.size();
		x2.clear(); y2.clear();
		x2.pb(0); y2.pb(0);
		rep(i, n) rep(j, 3){ x2.pb(x[i]+j-1); y2.pb(y[i]+j-1);}
		sort(all(x2)); UNIQUE(x2);
		sort(all(y2)); UNIQUE(y2);
		int h = x2.size(), w = y2.size();
		rep(i, h) xd[x2[i]] = i;
		rep(i, w) yd[y2[i]] = i;
		rep(i, N) fill(d[i], d[i]+N, INF);
		rep(i, n) d[xd[x[i]]][yd[y[i]]] = -1;
		d[xd[0]][yd[0]] = 0;
		priority_queue<P, vector<P>, greater<P>> q;
		q.push(P(0, xd[0]*N+yd[0]));
		ll res = 0;
		while(!q.empty()){
			P p = q.top(); q.pop();
			int a = p.snd/N, b = p.snd%N;
			ll l = p.fst;
			if(l>d[a][b]) continue;
			rep(i, 4){
				int a2 = a+dx[i], b2 = b+dy[i];
				if(a2==h) chmax(res, k-l+x2[a]);
				if(a2<0||a2>h||b2<0||b2>w||d[a2][b2]<0) continue;
				ll l2 = l+abs(x2[a]-x2[a2])+abs(y2[b]-y2[b2]);
				if(dx[i]==1) chmax(res, x2[a]+min(l2, k)-l);
				if(l2<d[a2][b2]){
					d[a2][b2] = l2;
					if(l2<=k) q.push(P(l2, a2*N+b2));
				}
			}
		}
		return res;
	}
};

SRM 644 Div1 Medium MakingTournament

実験すると、K=1 のときは、フィボナッチ数列になる。
K>1 のときは、2^K +1 項間漸化式になりそうなので、最初の 2^K +1 項あたりまでを愚直に計算すると、行列を復元することができる。

というのを実装したら通った。直感的には正しいことが分かるが、ちゃんと証明して解きたい。

#define M 300
ll f[7][M], g[7][M];

ll pow_mod(ll a, ll r, ll m){
	ll x = 1;
	while(r){
		if(r&1) (x*=a)%=m;
		(a*=a)%=m;
		r>>=1;
	}
	return x;
}

vector<ll> mul(vector<vector<ll>> &A, vector<ll> &x){
	int n = x.size();
	vector<ll> y(n, 0);
	rep(i, n) rep(j, n) (y[i]+=A[i][j]*x[j])%=mod;
	return y;
}

void mul(vector<vector<ll>> &A){
	vector<vector<ll>> B(A);
	int n = A.size();
	A = vector<vector<ll>>(n, vector<ll>(n, 0));
	rep(k, n) rep(i, n) rep(j, n) (A[i][j]+=B[i][k]*B[k][j])%=mod;
}

class MakingTournament {
	public:
	int howManyWays(long long N, int K) {
		mset(f, 0); mset(g, 0);
		rep(i, M) g[0][i] = 1;
		g[0][1] = 0;
		f[0][0] = 1;
		for(ll i = 2; i < M; i++) f[0][i] = (f[0][i-1]+f[0][i-2])%mod;

		for(ll k = 1; k < 6; k++){
			rep(i, M) g[k][i] = (f[k-1][i]-g[k-1][i]+mod)%mod;
			f[k][0] = 1;
			rep(i, M) rep(j, i) (f[k][i]+=f[k][j]*g[k][i-j])%=mod;
		}
		ll m = 1<<K;
		vector<vector<ll>> A(m, vector<ll>(m, 0));
		rep(i, m-1) A[i+1][i] = 1;
		rep(i, m){
			ll sum = 0;
			rep(j, m) (sum+=f[K-1][i+j+1]*A[0][m-j-1])%=mod;
			A[0][i] = (f[K-1][i+m+1]-sum+mod)%mod;
		}
		vector<ll> x(m, 0);
		x[0] = 1;
		N--;
		while(N){
			if(N%2) x = mul(A, x);
			mul(A);
			N>>=1;
		}
		return (x[m-1]+mod)%mod;
	}
};