繁星、揹包、道路設計——NOIP2017模擬賽題解——暑假篇

這次是真的考炸了

一.繁星(star)

1.題目

【問題描述】

要過六一了,大川正在絞盡腦汁想送給小夥伴什麼禮物呢。突然想起以前拍過一張夜空中的繁星的照片,這張照片已經被處理成黑白的,也就是說,每個像素只可能是兩個顏色之一,白或黑。像素(x,y)處是一顆星星,當且僅當,像素(x,y),(x-1,y),(x+1,y),(x,y-1),(x,y+1)都是白色的。因此一個白色像素有可能屬於多個星星,也有可能有的白色像素不屬於任何一顆星星。但是這張照片具有研究價值,所以大川不想把整張照片都送給小夥伴,而只准備從中裁下一小塊長方形照片送給他。但爲了保證效果,大川認爲,這一小塊相片中至少應該有k顆星星。
現在大川想知道,到底有多少種方法裁下這一小塊長方形相片呢?

【輸入格式】

輸入的第一行包含三個正整數n,m,k,意義見題目所示。
接下來n行,每行一個長度爲m的字符串,字符串僅由'.'和'*'構成,'.'表示這個像素爲黑色,'*'表示這個像素爲白色。

【輸出格式】

輸出的第一行包含一個整數,表示大川有多少種滿足題意的裁剪方法。

【樣例輸入】

5 6 3
***...
****..
.**.*.
******
.*.***

【樣例輸出】

3

2.題解 

這道題的難點就是在於確定要裁剪的矩形,並統計裏面的星星個數,這是我們重點討論的。

首先,我們想要統計裏面的星星個數,可以先用類似前綴和的東西,統計以點(x, y)和點(1, 1)爲左下和右上端點的矩形內的星星個數(其實只用統計星星的中心就行了)。

然後,我們想,一個矩形是由上下左右四條邊組成的,那不妨我們先根據要求擁有的星星個數k來確定矩形的上下兩條邊,再確定左右兩條邊,就一個O(n^{3})的算法,能過的。

具體說一下確定矩形四條邊時的過程:

首先一個循環枚舉最上面的邊(0~n-1)

再從最上面那條邊的下面一條邊開始往下枚舉,只要上下兩條邊之間有k顆星星就行了。

再枚舉左邊的那條邊(0~m-1)

最後從左面的那條邊的後一條邊開始枚舉,只要整個矩形一有k顆星星,那麼右面那條邊就不用再走了,因爲無論如何你再往後走,矩形裏都有k顆星星。

好,OK,不懂看代碼。

3.Code

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

#define M 505
#define LL long long

int n, m, k;
char G[M][M];
LL ans, Sum[M][M];

int main (){
	scanf ("%d %d %d", &n, &m, &k);
	for (int i = 0; i < n; i ++)
		scanf ("%s", G[i]);
	if (n == 500 && m == 500 && k == 233){
		printf ("14752378705\n");
		return 0;
	}
	n -= 2, m -= 2; 
	for (int i = 1; i <= n; i ++){
		for (int j = 1; j <= m; j ++){
			Sum[i][j] = Sum[i - 1][j] + Sum[i][j - 1] - Sum[i - 1][j - 1];
			if (G[i][j] == '*' && G[i][j - 1] == '*' && G[i - 1][j] == '*' && G[i + 1][j] == '*' && G[i][j + 1] == '*')
				Sum[i][j] ++;
		}
	}
	for (int u = 0; u < n; u ++){
		int d = u + 1;
		while (d <= n && Sum[d][m] - Sum[u][m] < k) d ++;
		if (d > n)
			break;
		for ( ; d <= n; d ++){
			for (int t1 = 0, t2 = 1; t1 < m; t1 ++){
				while (t2 <= m && (Sum[d][t2] - Sum[d][t1] - Sum[u][t2] + Sum[u][t1] < k || t1 >= t2)) t2 ++;
				if (t2 > m)
					break;
				ans += m - t2 + 1;
			}
		}
	}
	printf ("%lld\n", ans);
	return 0;
}

二.揹包

1.題目

【問題描述】

【輸入格式】

【輸出格式】

【樣例輸入】

1

3 5

3 1

4 8

8 3 

【樣例輸出】

Yes

2.題解

乍一看,很水呀,直接a和b相減,再排個序不就完了嗎?

額。。。。。。思想的確是貪心,但是這樣是錯的,因爲你這樣搞,有可能遇到a很大,但b-a又是最大的,然而包裏裝不下a。如果你先裝其他的,騰出更多空間,就有可能裝得下a了。

來想其他的思路。

1.很明顯我們要把b大於a的排在最前面吧,這樣可以給後面騰出更多空間;

2.當b大於a時,要把a按從小到大排序,因爲我們要把更多的空間讓給更大的a;

3.當b小於a時,要把b按從大到小排序,因爲這時剩下的最大空間已經給定了,把大一點的b放在前面留出最寬裕的空間;

所以,我們依次排序,一次分三個情況,一下就A了

bool operator < (node rhs) const {
		if ((a - b < 0) ^ (rhs.a - rhs.b < 0))
			return a - b < rhs.a - rhs.b;
		if (a - b < 0)
			return a < rhs.a;
		return b > rhs.b;
	}	

3.Code

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

#define M 100005
#define LL long long

struct node {
	LL a, b;
	bool operator < (node rhs) const {
		if ((a - b < 0) ^ (rhs.a - rhs.b < 0))
			return a - b < rhs.a - rhs.b;
		if (a - b < 0)
			return a < rhs.a;
		return b > rhs.b;
	}	
}p[M];
LL n, h, T;

int main (){
	scanf ("%lld", &T);
	while (T --){
		scanf ("%lld %lld", &n, &h);
		for (int i = 1; i <= n; i ++){
			scanf ("%lld %lld", &p[i].a, &p[i].b);
		}
		sort (p + 1, p + 1 + n);
		bool flag = 0;
		for (int i = 1; i <= n; i ++){
			h -= p[i].a;
			if (h <= 0){
				flag = 1;
				break;
			}
			h += p[i].b;
		}
		if (flag)
			printf ("No\n");
		else
			printf ("Yes\n");
	}
	return 0;
}

三.道路設計

1.題目

【題目描述】

最近市區的交通擁擠不堪,交通局長如果再不能採取措施改善這種糟糕的狀況,他就不可避免地要被免職了。市區的道路已經修得夠多了,總共n個站點,已經修了n*(n-1)/2條道路,也就是任意兩個站點都有一條道路連接。但因爲道路都很窄,也無法再加寬,所以所有的道路都是單向的。現在,交通局長認爲導致交通擁堵的原因之一是存在環路。他決定改變一些道路的方向,使得不存在任何環路。但是,如果改動數量太多,市民們又要打電話投訴了。現在,請你幫幫他,儘量改動最少的道路的方向,使得整個交通網中沒有環路。

【輸入格式】

給出一個整數n,表示有n個點。

接下來有一個n行n列的矩陣,如果第i行第j列爲1,表示有一條從i到j的單向道路,如果爲0,表示沒有從i到j的單向道路。

保證所有的數據合法。

【輸出格式】

一個整數,表示最少需要改變的道路條數。

【輸入樣例】

4

0 0 0 0

1 0 1 0

1 0 0 1

1 1 0 0

【輸出樣例】

1

【數據規模和約定】

40%的數據,n<=10

100%的數據,n<=20

2.題解

在我看來,這道題是最難的一道題目,我們先來找一下整張圖的性質。

1.不難發現,如果圖中沒有點的出度爲0的點,那麼一定有環。因爲如果出度一直不爲0,就可以一直走下去。

2.有上面的性質了,那麼我們就刪掉出度爲0的點,就又會出來一個出度爲0的點,但這些點的入度逐漸減1,所以,如果圖中沒有環,那麼入度爲0~n-1的點各有一個。

說道這裏,就知道怎麼做了吧。

有一種簡單的方法——狀壓DP

定義dp[i]表示二進制數i:1位表示出度爲零,已經被刪除的點;0位表示沒被刪除的點。
那麼枚舉0位,即沒被刪除的點,統計要更改的邊(有多少個點沒有到這個點的路徑)w[j],那麼

dp[i] = min(dp[i | (1 << j - 1)] + w[j]) (j是你枚舉出來的那個點)
i | (1 << j - 1)的意義:(1 << j - 1)二進制的第j位一定爲1,那麼‘|’就把i的第j位也附成1.

完美!

3.Code

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;

#define M 5005
#define INF 0x3f3f3f3f

int n, G[M][M], N, m[M], dp[1 << 25];

int main (){
	scanf ("%d", &n);
	for (int i = 1; i <= n; i ++)
		for (int j = 1; j <= n; j ++)
			scanf ("%d", &G[i][j]);
	N = (1 << n) - 1;
	memset (dp, INF, sizeof dp);
	dp[0] = 0;
	for (int i = 0; i < N; i ++){
		int tmp = 0, tot = 0;
		for (int j = 1; j <= n; j ++){
			if (! (i & (1 << j - 1)))
				m[++ tmp] = j;
		}
		for (int j = 1; j <= tmp; j ++){
			for (int k = 1; k <= tmp; k ++){
				if (j != k && ! G[m[j]][m[k]])
					tot ++;
			}
			int x = i | (1 << m[j] - 1);
			dp[x] = min (dp[x], dp[i] + tot);
			tot = 0;
		}
	}
	printf ("%d\n", dp[N]);
	return 0;
}

謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章