NOIP2017模拟题 Day18

NOIP2017模拟题 Day18

繁星

一、题目及数据范围

要过六一了,大川正在绞尽脑汁想送给小伙伴什么礼物呢。突然想起以前拍过一张夜空中的繁星的照片,这张照片已经被处理成黑白的,也就是说,每个像素只可能是两个颜色之一,白或黑。像素(x,y)处是一颗星星,当且仅当,像素(x,y),(x-1,y),(x+1,y),(x,y-1),(x,y+1)都是白色的。因此一个白色像素有可能属于多个星星,也有可能有的白色像素不属于任何一颗星星。但是这张照片具有研究价值,所以大川不想把整张照片都送给小伙伴,而只准备从中裁下一小块长方形照片送给他。但为了保证效果,大川认为,这一小块相片中至少应该有k颗星星。
现在大川想知道,到底有多少种方法裁下这一小块长方形相片呢?

对于20%的数据,满足N,M<=20.
对于40%的数据,满足N,M<=100.
对于70%的数据,满足N,M<=200.
对于100%的数据,满足N,M<=500,0<k<N*M.

二、解法

对于20%的数据,本人太弱,考场上打了个O(n^6)的暴力,20分,不解释。
对于40%的数据,用sum[i][j]统计前i行j列星星的个数,最后O(n^4)枚举4个边界,注意处理sum时,边界的星星也是要统计进去的。但是这一点本蒟蒻却想不通,凭什么统计(1,1)到(i,j)的矩形时,要把边界的星星算进去?(截取照片时明明截取不到啊)。考试的时候想不通,就没码出来。其实我们没有把sum[i][j]取边界,只是为了统计的需要,sum[i][j]实际上意味着实际上的sum[i+1][j+1],所以我们把n和m都减2,(其中一个来源于从0开始,另一个来源于sum的需要),可以保证正确性。
对于70%的数据,因为“至少应该有k颗星星”,可知方案具有单调性,确定三个边界,二分另一个边界,时间复杂度O(n^3*logn)
对于100%的数据,我们可以发现,当确定上下边界时,左边界增大时,右边界也相应的增大,这不就是经典的滑动窗口问题吗,统计答案时,对于每一个合法左端点,我们把答案加上右端点的取值可能。时间复杂度O(n^3)。见代码吧。

#include <cstdio>
int read()
{
	int x=0,flag=1;char c;
	while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
	while(c>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x*flag;
}
int n,m,k,sum[505][505];
long long ans;
char a[505][505];
int main()
{
	freopen("star.in","r",stdin);
	freopen("star.out","w",stdout);
	n=read();m=read();k=read();
	for(int i=0;i<n;i++)
		scanf("%s",a[i]);
	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]+(a[i][j]=='*' && a[i-1][j]=='*' && a[i][j-1]=='*' && a[i+1][j]=='*' && a[i][j+1]=='*');
	for(int l=0;l<n;l++)
	{
		int r=l+1;
		while(r<=n && sum[r][m]-sum[l][m]<k) r++;
		for(;r<=n;r++)
			for(int t1=0,t2=1;t1<m;t1++)
			{
				while(t2<=m && sum[r][t2]-sum[r][t1]-sum[l][t2]+sum[l][t1]<k || t2<=t1) t2++;
				if(t2>m) break;
				ans+=m+1-t2;
			}
	}
	printf("%lld\n",ans);
}

背包

一、题意及数据范围

有n件物体,有一个容量为v的神奇背包,每一个物体有ai和bi两种值,ai表示i物品放入背包所需的空间,bi表示放入此物体后背包能增长的容量,问能否将这n个物体放完。
对于100%的数据,n<=1e5。a,b<=1e5。多组数据。

二、解法

被这个题目蒙蔽了,我一直以为是dp优化,结果卡死,只能打无脑全排(感觉做成了搜索专练)。
交卷时才忽然意识到这是贪心,很快就想出了贪心策略,我们可以分两种情况讨论。
1、当a<b时,放入这个物体会增大背包容量,我们肯定先放这一类物体,放入时按a从小到大排序(想一想,我们增长的容量是一定的,就要让占用空间最小的先放)
2、当a>b时,由于需要消耗的量时一定的(指剩下物体a之和),所以我们就要让增长量越大越好,直接按b从大到小排序!(意会意会,可以举一些具体例子的理解)

#include <cstdio>
#include <algorithm>
#define LL long long
using namespace std;
const LL MAXN = 100005;
LL read()
{
	LL x=0,flag=1;char c;
	while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
	while(c>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x*flag;
}
LL T,n,v;
struct node
{
	LL a,b;
	bool operator < (node x) const {
		if(x.a<x.b && a<b) return a<x.a;
		if(x.a<x.b) return 0;
		if(a<b) return 1;
		return b>x.b;
	}
}s[MAXN];
int main()
{
	freopen("backpack.in","r",stdin);
	freopen("backpack.out","w",stdout);
	T=read();
	while(T--)
	{
		n=read(),v=read();
		for(LL i=1;i<=n;i++)
			s[i].a=read(),s[i].b=read();
		sort(s+1,s+1+n);
		for(LL i=1;i<=n;i++)
		{
			if(v-s[i].a<0)
			{
				printf("No\n");
				goto In;
			}
			else
				v=v-s[i].a+s[i].b;
		}
		puts("Yes");
		In:;
	}
}

道路设计

一、题意及数据范围

【题目描述】
最近市区的交通拥挤不堪,交通局长如果再不能采取措施改善这种糟糕的状况,他就不可避免地要被免职了。市区的道路已经修得够多了,总共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

二、解法

这道题数据出奇的小,考试时模仿拓扑的思路贪心,打出来都不敢相信,结果只得10分。

n个点,n*(n-1)/2条边的有向图中不存在环的充要条件是:n个点的出度或入度互不相同,出度或入度从0到n-1都有。 证明:首先必须有一个点出度为0,否则从任意一个点出发,沿着有向边可以一直走下去。那自然会重复访问到某个重复点,这就表明有环了。 也不能有多于一个点出度为0。否则这两个点之间就没有边了。 所以,必须刚好一个点的出度为0.而它的入度刚好是n-1. 删掉这个点及它的n-1条入边,我们得到一个n-1点的图,刚好有(n-1)*(n-2)/2条边。同样的分析方法,我们知道,新图中只能由一个点出度为0.那意味着它原来的出度为1. 依次类推,可以知道,出度为2、为3、……,为n-1的点都只有唯一一个。这样才能保证没有环路。
知道了这个性质以后,就可以利用搜索来做了。 这里介绍一种状压DP的方法。 设dp[i]表示i的二进制位中,为1的位表示按照证明中的方法,因为出度为0已经被删掉了,为0的位表示还存在于图中。任选一位为0的位,设该位为第j位,让它出度为0,即其他的0位都向它连有向边,这里统计需要改变方向的边的数量,记为wj。 则dp[i]=min(dp[i|(1<<j)]+wj) 其中j枚举所有为0的位。

#include <cstdio>
#include <cstring>
int read()
{
	int x=0,flag=1;char c;
	while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
	while(c>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x*flag;
}
int n,m,a[25][25],dp[1<<20],que[25];
int min(int a,int b) {
	return a<b?a:b;
}
int main()
{
	freopen("road.in","r",stdin);
	freopen("road.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			a[i][j]=read();
	m=(1<<n)-1;
	memset(dp,0x3f,sizeof dp);
	dp[0]=0;
	for(int i=0;i<m;i++)
	{
		int tot=0,now=0;
		for(int j=0;j<n;j++)
			if(!(i&(1<<j)))
				que[++tot]=j+1;
		for(int j=1;j<=tot;j++)
		{
			now=0;
			for(int k=1;k<=tot;k++)
				if(j!=k && !a[que[k]][que[j]])
					now++;
			int x=i|(1<<que[j]-1);
			dp[x]=min(dp[x],dp[i]+now);
		}
	}
	printf("%d\n",dp[m]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值