繁星、背包、道路设计——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;
}

谢谢!

发布了61 篇原创文章 · 获赞 32 · 访问量 8352

猜你喜欢

转载自blog.csdn.net/weixin_43908980/article/details/96444167