1. 程式人生 > >校內集訓 9-24

校內集訓 9-24

T1

Description

尤里背叛了蘇維埃聯盟!尤里具有心靈控制的能力,可以控制我方的士兵攻擊同伴。為了避免這種情況,斯大林同志要求你合理地排兵佈陣,使得沒有兩個士兵可以互相攻擊。在這個問題裡,你可以認為士兵的攻擊範圍類似於國際象棋中的馬。也即,位置為(x,y)(x,y)的士兵可以攻擊位置為(x±2,y±1)(x±2,y±1)或(x±1,y±2)(x±1,y±2)的士兵。請計算:在 N*M 的棋盤上最多能 放置多少個士兵。(當然,兩個士兵不能在同一個位置)

n<mn < m

發現當n>3n > 3的時候交叉放一定最優

n=1n = 1的時候都可以放

n=2n = 2的時候2×22×2的正方形交替放最優

然後就做完了

Codes

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;

void File() {
	freopen("psycho.in", "r", stdin);
	freopen("psycho.out", "w", stdout);
}

void Solve() {
	int T, n, m;
	for(scanf("%d", &T); T -- ; ) {
		scanf
("%d%d", &n, &m); if(n > m) swap(n, m); if(n == 1) {printf("%d\n", m); continue;} if(n == 2) {printf("%d\n", 4 * (((m / 2) + 1) / 2) + ((!((m / 2) & 1)) & (m & 1)) * 2); continue;} int n1 = n / 2, n2 = n - n1, m1 = m / 2, m2 = m - m1; printf("%lld\n", (ll)m2 * n2 + (ll)m1 *
n1); } } int main() { //File(); Solve(); return 0; }

T2

Description

“我是超級大沙茶”—— MatoNo1MatoNo1 為了證明自己是一個超級大沙茶,MatoMato 神犇決定展示自己對叉(十字型)有多麼的瞭解。

MatoMato 神犇有一個平面直角座標系,上面有一些線段,保證這些線段至少與一條座標軸平行。MatoMato 神犇需要指出,這些線段構成的最大的十字型有多大。

稱一個圖形為大小為 RR(RR 為正整數)的十字型,當且僅當,這個圖形具有一箇中心點,它存在於某一條線段上,並且由該點向上下左右延伸出的長度為 RR 的線段都被已有的線段 覆蓋。

你可以假定:沒有兩條共線的線段具有公共點,沒有重合的線段。

因為題目保證資料隨機

那麼答案顯然很容易達到

直接O(n2)O(n^2)暴力加上最優性剪枝就過了

Codes

#include<bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int n, ch, cs, ans;

struct node {
	int _x1, _y1, _x2, _y2;
	bool operator < (const node &T) const {
		return _x1 < T._x1;
	}
}heng[N], shu[N], tmp;

void File() {
	freopen("cross.in", "r", stdin);
	freopen("cross.out", "w", stdout);
}

int read() {
	int _ = 0, __ = getchar(), ___ = 1;
	for(; !isdigit(__); __ = getchar()) if(__ == '-') ___ = -1;
	for(; isdigit(__); __ = getchar()) _ = (_ << 3) + (_ << 1) + (__ ^ 48);
	return _ * ___;
}

void Solve() {
	int _x1, _x2, _y1, _y2, l, r; n = read();
	for(int i = 1; i <= n; ++ i) {
		_x1 = read(), _y1 = read(), _x2 = read(), _y2 = read();
		if(_x1 > _x2) swap(_x1, _x2);
		if(_y1 > _y2) swap(_y1, _y2);
		if(_x1 == _x2) shu[++ cs] = node{_x1, _y1, _x2, _y2};
		else heng[++ ch] = node{_x1, _y2, _x2, _y2};
	}
	if(!cs || !ch) return (void)puts("Human intelligence is really terrible");
	sort(shu + 1, shu + cs + 1);	
	for(int i = 1; i <= ch; ++ i) {
		tmp._x1 = heng[i]._x1; l = lower_bound(shu + 1, shu + cs + 1, tmp) - shu;
		tmp._x1 = heng[i]._x2; r = upper_bound(shu + 1, shu + cs + 1, tmp) - shu;
		//cout << l << ' ' << r << endl;
		int len = (heng[i]._x2 - heng[i]._x1) / 2;
		for(int j = l; j < r; ++ j) {
			if(ans >= len) break;
			if(heng[i]._x1 <= shu[j]._x1 && shu[j]._x1 <= heng[i]._x2)	
				if(shu[j]._y1 <= heng[i]._y1 && heng[i]._y1 <= shu[j]._y2) {
					int th = min(heng[i]._y1 - shu[j]._y1, shu[j]._y2 - heng[i]._y1);
					int ts = min(shu[j]._x1 - heng[i]._x1, heng[i]._x2 - shu[j]._x1);
					ans = max(ans, min(th, ts));
				}
		}
	}
	if(ans) printf("%d\n", ans);
	else puts("Human intelligence is really terrible");
}

int main() {
//	File();
	Solve();
	return 0;
}

來考慮一下資料不隨機怎麼做

我們發現這個答案是具有單調性的

意思是存在大十字架就存在小十字架

那麼我們二分十字架的RR

每次找到長度大於等於2R2R的線段

把他們兩端刪掉 即線段[x,y]&gt;[x+mid,ymid][x, y] -&gt;[x + mid, y - mid]

這樣子如果線段存在交集就是合法的

對於線段求交我們可以用掃描線法

每次掃到線段起點就加入,線段終點就刪除

遇到豎著的線段就查詢區間內是否存在橫著的線段

這些操作可以用multisetmultiset實現

複雜度O(nlognlogL)O(nlognlogL)

Codes

#include<bits/stdc++.h>

using namespace std;

const int N = 4e5 + 10;

int n, ch, cs, cnt;

struct Seg {
	int x, y, _y, len;
}H[N], S[N];

struct node {
	int id, pos, L, R;
	bool operator < (const node &T) const {
		return pos == T.pos ? id < T.id : pos < T.pos;
	}
}tmp[N];

multiset<int> T;

bool check(int now) {
	for(int i = 1; i <= ch; ++ i) 
		if(H[i].len >= now * 2) 
			tmp[++ cnt] = node{1, H[i].y + now, H[i].x, 0}, 
			tmp[++ cnt] = node{3, H[i]._y - now, H[i].x, 0};
	for(int i = 1; i <= cs; ++ i) 
		if(S[i].len >= now * 2) 
			tmp[++ cnt] = node{2, S[i].x, S[i].y + now, S[i]._y - now};
	sort(tmp + 1, tmp + cnt + 1);
	for(int i = 1; i <= cnt; ++ i) {
		if(tmp[i].id == 1) T.insert(tmp[i].L);
		if(tmp[i].id == 3) T.erase(T.find(tmp[i].L));
		if(tmp[i].id == 2 && T.upper_bound(tmp[i].R) != T.lower_bound(tmp[i].L)) return true;
	}
	return false;
}

int main() {
#ifdef ylsakioi
	freopen("cross.in", "r", stdin);
	freopen("cross.out", "w", stdout);
#endif
	int x, _x, y, _y; scanf("%d", &n);
	for(int i = 1; i <= n; ++ i) {
		scanf("%d%d%d%d", &x, &y, &_x, &_y);
		if(x > _x) swap(x, _x); if(y > _y) swap(y, _y);
		if(x == _x) S[++ cs] = Seg{x, y, _y, _y - y};
		if(y == _y) H[++ ch] = Seg{y, x, _x, _x - x};
	}
	int L = 0, R = 1e9, ans = 0;
	while(cnt = 0, T.clear(), L <= R) {
		int mid = (L + R) >> 1;
		if(check(mid)) ans = mid, L = mid + 1;
		else R = mid - 1;
	}
	if(ans) printf("%d\n", ans);
	else puts("Human intelligence is really terrible");
	return 0;
}

T2

Description

從前有一個 RudyRudy。

從前還有一個網格圖。

RudyRudy 喜歡爆炸。

RudyRudy 偶爾會炸掉網格圖中的一條邊(u,v)(u,v)。之後他會嘗試從 uu 走到 vv。

如果他成功地從 uu 走到 vv,他會很高興;否則他會找人打架。

從第二次爆炸開始,根據 RudyRudy 此時心情的不同,RudyRudy 會炸掉不同的邊。

你被要求編寫一個程式,對於每次爆炸,給出此時 RudyRudy 是否還能從 uu 到 vv。

把網格圖的點變成格子

刪一條邊相當於讓兩個格子聯通

如果兩個格子在刪這條邊前就聯通那就無解了

否則就有解 把格子外面也當做一個大格子就可以了

用並查集維護即可

Codes

#include<bits/stdc++.h>

using namespace std;

const int N = 500 + 10;

int flag, fa[N * N], n, q;

void File() {
	freopen("babystep.in", "r", stdin);
	freopen("babystep.out", "w", stdout);
}

int read() {
	int _ = 0, __ = getchar(), ___ = 1;
	for(; !isdigit(__); __ = getchar()) if(__ == '-') __ = -1;
	for(; isdigit(__); __ = getchar()) _ = (_ << 3) + (_ << 1) + (__ ^ 48);
	return _ * ___;
}

int find(int x) {
	return fa[x] = x == fa[x] ? x : find(fa[x]);
}

bool merge(int x, int y) {
	//cout << x << ' ' << y << endl;
	x = find(x), y = find(y);
	if(x == y) return false;
	return fa[x] = y, true;
}

void get_flag(int _x1, int _y1, int _x2, int _y2) {
	//cout << _x1 << ' ' << _y1 << ' ' << _x2 << ' ' << _y2 << endl;
	if(_x1 == _x2 && _x1 == 1) flag = merge((n - 1) * (n - 1) + 1, _y1);
	else if(_x1 == _x2 && _x1 == n) flag = merge((n - 1) * (n - 1) + 1, (n - 1) * (n - 2) + _y1);
	else if(_y1 == _y2 && _y1 == 1) flag = merge((n - 1) * (n - 1) + 1,