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]);
}