2010noip提高组解题报告
https://www.luogu.org/problem/show?pid=1514
题目描述
在一个遥远的国度,一侧是风景秀美的湖泊,另一侧则是漫无边际的沙漠。该国的行政区划十分特殊,刚好构成一个N 行M 列的矩形,如上图所示,其中每个格子都代表一座城市,每座城市都有一个海拔高度。
为了使居民们都尽可能饮用到清澈的湖水,现在要在某些城市建造水利设施。水利设施有两种,分别为蓄水厂和输水站。蓄水厂的功能是利用水泵将湖泊中的水抽取到所在城市的蓄水池中。
因此,只有与湖泊毗邻的第1 行的城市可以建造蓄水厂。而输水站的功能则是通过输水管线利用高度落差,将湖水从高处向低处输送。故一座城市能建造输水站的前提,是存在比它海拔更高且拥有公共边的相邻城市,已经建有水利设施。由于第N 行的城市靠近沙漠,是该国的干旱区,所以要求其中的每座城市都建有水利设施。那么,这个要求能否满足呢?如果能,请计算最少建造几个蓄水厂;如果不能,求干旱区中不可能建有水利设施的城市数目。
输入输出格式
输入格式:
输入文件的每行中两个数之间用一个空格隔开。输入的第一行是两个正整数N 和M,表示矩形的规模。接下来N 行,每行M 个正整数,依次代表每座城市的海拔高度。
输出格式:
输出有两行。如果能满足要求,输出的第一行是整数1,第二行是一个整数,代表最少建造几个蓄水厂;如果不能满足要求,输出的第一行是整数0,第二行是一个整数,代表有几座干旱区中的城市不可能建有水利设施。
输入输出样例
【输入样例1】 2 5 9 1 5 4 3 8 7 6 1 2 【输入样例2】 3 6 8 4 5 6 4 4 7 3 4 3 3 3 3 2 2 1 1 2
【输出样例1】 1 1 【输出样例2】 1 3
说明
【样例1 说明】
只需要在海拔为9 的那座城市中建造蓄水厂,即可满足要求。
【样例2 说明】
上图中,在3 个粗线框出的城市中建造蓄水厂,可以满足要求。以这3 个蓄水厂为源头
在干旱区中建造的输水站分别用3 种颜色标出。当然,建造方法可能不唯一。
【数据范围】
【题目分析】
首先我们来观察一下这个数据范围,每一个测试点都给了,那么如果这题你觉得有难度,先来看,哪个点最简单?当然是4,因为只有一行,只要把这一行的严格上升序列的最后一个找出来就可以了
显然用暴力枚举是可以boom!我们后面考虑dp
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int inf = 2147483647,size = 505; int m,n,a[size][size],f[size];//a是输入的矩阵,f数组是dp所使用的状态数组 int dis[4][2] = {{1,0}, {-1,0}, {0,1}, {0,-1}}; bool visited[size][size],isno[size];//visited数组为搜索的判断是否已经访问的数组,isno数组为当前位置的水是否能到达 struct node{ int l,r;//l边界的最小值,r边界的最大值 }p[size]; void dfs(int x, int y, int cnt){ visited[x][y] = true; //标记为访问过 if(x == m)//搜完了(即能引到n行的某一列)
{ isno[y] = 1;//第y为可以引水 p[cnt].l = min(p[cnt].l, y);//取边界的最小值(dp的时候使用) p[cnt].r = max(p[cnt].r, y);//取边界的最大值(dp的时候使用) } //暴力枚举四个方向 if(a[x+1][y]<a[x][y]&&x!=m&&!visited[x+1][y]) dfs(x+1,y,cnt); if(a[x-1][y]<a[x][y]&&x!=1&&!visited[x-1][y]) dfs(x-1,y,cnt); if(a[x][y+1]<a[x][y]&&y!=n&&!visited[x][y+1]) dfs(x,y+1,cnt); if(a[x][y-1]<a[x][y]&&y!=1&&!visited[x][y-1]) dfs(x,y-1,cnt); } void init() { scanf("%d %d",&m,&n); for(int i=1;i<=n;i++) p[i].l=f[i]=inf;//设为INT_MAX,求min的时候更准确 f[0]=0;//dp边界 for(int i=1;i<=m;i++) for(int j=1;j<=n;j++) scanf("%d",&a[i][j]); } int main(int argc, char const *argv[]){ init(); for(int i=1;i<=n;i++) memset(visited,false,sizeof(visited)),//每次将visited数组全赋值为false,然后搜索 dfs(1,i,i); int count = 0;//count用来统计有多少水未到达 for(int i=1;i<=n;i++) if(!isno[i]) count++; if(count) //如果有位置水不能到达,则说明不可能,输出0 printf("0\n%d\n",count); else//否则说明水可以到达 { for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(p[j].l<=i&&i<=p[j].r)//如果在边界 f[i]=min(f[i],f[p[j].l-1]+1); printf("1\n%d\n",f[n]); } return 0; }
题目背景
https://www.luogu.org/problem/show?pid=1540#sub
小晨的电脑上安装了一个机器翻译软件,他经常用这个软件来翻译英语文章。
题目描述
这个翻译软件的原理很简单,它只是从头到尾,依次将每个英文单词用对应的中文含义来替换。对于每个英文单词,软件会先在内存中查找这个单词的中文含义,如果内存中有,软件就会用它进行翻译;如果内存中没有,软件就会在外存中的词典内查找,查出单词的中文含义然后翻译,并将这个单词和译义放入内存,以备后续的查找和翻译。
假设内存中有M个单元,每单元能存放一个单词和译义。每当软件将一个新单词存入内存前,如果当前内存中已存入的单词数不超过M-1,软件会将新单词存入一个未使用的内存单元;若内存中已存入M个单词,软件会清空最早进入内存的那个单词,腾出单元来,存放新单词。
假设一篇英语文章的长度为N个单词。给定这篇待译文章,翻译软件需要去外存查找多少次词典?假设在翻译开始前,内存中没有任何单词。
输入输出格式
输入格式:
输入文件共2行。每行中两个数之间用一个空格隔开。
第一行为两个正整数M和N,代表内存容量和文章的长度。
第二行为N个非负整数,按照文章的顺序,每个数(大小不超过1000)代表一个英文单词。文章中两个单词是同一个单词,当且仅当它们对应的非负整数相同。
输出格式:
包含一个整数,为软件需要查词典的次数。
输入输出样例
3 7 1 2 1 5 4 4 1
5
说明
每个测试点1s
对于10%的数据有M=1,N≤5。
对于100%的数据有0<=M<=100,0<=N<=1000。
整个查字典过程如下:每行表示一个单词的翻译,冒号前为本次翻译后的内存状况:
空:内存初始状态为空。
1. 1:查找单词1并调入内存。
2. 1 2:查找单词2并调入内存。
3. 1 2:在内存中找到单词1。
4. 1 2 5:查找单词5并调入内存。
5. 2 5 4:查找单词4并调入内存替代单词1。
6. 2 5 4:在内存中找到单词4。
7. 5 4 1:查找单词1并调入内存替代单词2。
共计查了5次词典。
【题目分析】
一定注意初值都赋成-1,因为标号有可能为零!!因为是线性的查找,可以用head 和 tail来查找
//不好好写就会出现很多垃圾错误(100) #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; int n,m,x; int a[2020]; int isno=0,ans=0; int main() { scanf("%d%d",&m,&n); memset(a,-1,sizeof a); for(int i=1;i<=n;i++) { int isno=0;//这里要记得每次都清零 scanf("%d",&x); for(int j=1;j<=m;j++) if(a[j]==x) isno=1; if(isno==0) { ans++; if(a[m]!=-1) { for(int j=1;j<m;j++) a[j]=a[j+1]; a[m]=x; } else { for(int j=1;j<=m;j++) if(a[j]==-1){ a[j]=x; break; } } } } printf("%d",ans); return 0; }
//60分,明天再改,如果不加那个赋初值的话只能的40分 #include<cstdio> #include<iostream> #include<vector> using namespace std; int a[1010]; int n,m,cnt=0; int head=1,tail=1; int k=1; int main() { scanf("%d%d",&n,&m); for(int i=1;i<=1009;i++) a[i]=-1; for(int i=1;i<=m;i++) { int x; int isno=0; scanf("%d",&x); for(int j=head;j<=tail;j++) if(a[j]==x) isno=1; if(isno==0) { a[k++]=x; if(k-head==n) tail=k, head++; else if(k-head<n) tail++; cnt++; } } printf("%d",cnt); }
https://www.luogu.org/problem/show?pid=1525
题目描述
S 城现有两座监狱,一共关押着N 名罪犯,编号分别为1~N。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为c 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为c 的冲突事件。
每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到S 城Z 市长那里。公务繁忙的Z 市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。
在详细考察了N 名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。
那么,应如何分配罪犯,才能使Z 市长看到的那个冲突事件的影响力最小?这个最小值是多少?
输入输出格式
输入格式:
输入文件的每行中两个数之间用一个空格隔开。第一行为两个正整数N 和M,分别表示罪犯的数目以及存在仇恨的罪犯对数。接下来的M 行每行为三个正整数aj,bj,cj,表示aj 号和bj 号罪犯之间存在仇恨,其怨气值为cj。数据保证1<aj=<=bj<=N ,0 < cj≤ 1,000,000,000,且每对罪犯组合只出现一次。
输出格式:
共1 行,为Z 市长看到的那个冲突事件的影响力。如果本年内监狱中未发生任何冲突事件,请输出0。
输入输出样例
4 6 1 4 2534 2 3 3512 1 2 28351 1 3 6618 2 4 1805 3 4 12884
3512
说明
【输入输出样例说明】罪犯之间的怨气值如下面左图所示,右图所示为罪犯的分配方法,市长看到的冲突事件影响力是3512(由2 号和3 号罪犯引发)。其他任何分法都不会比这个分法更优。
【数据范围】对于30%的数据有N≤ 15。对于70%的数据有N≤ 2000,M≤ 50000。对于100%的数据有N≤ 20000,M≤ 100000。
【题目分析】
并查集
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; #define maxn 100010 #define maxm 40010 int n,m,x,y; int father[maxm]; struct node{ int a,b,c; }d[maxn]; int find1(int x) { if(father[x]==x) return x; else return father[x]=find1(father[x]); } bool cmp(const node p,const node q) //降序排列 { return p.c > q.c; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) scanf("%d%d%d",&d[i].a,&d[i].b,&d[i].c); for(int i=1;i<=n*2;i++) father[i]=i; sort(d+1,d+m+1,cmp); for(int i=1;i<=m;i++) { x=find1(d[i].a); y=find1(d[i].b); if(x==y) { printf("%d",d[i].c); return 0; } father[x]=find1(d[i].b+n); father[y]=find1(d[i].a+n); } printf("0"); return 0; }
题目背景
https://www.luogu.org/problem/show?pid=1541
小明过生日的时候,爸爸送给他一副乌龟棋当作礼物。
题目描述
乌龟棋的棋盘是一行N个格子,每个格子上一个分数(非负整数)。棋盘第1格是唯一的起点,第N格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点。
乌龟棋中M张爬行卡片,分成4种不同的类型(M张卡片中不一定包含所有4种类型的卡片,见样例),每种类型的卡片上分别标有1、2、3、4四个数字之一,表示使用这种卡片后,乌龟棋子将向前爬行相应的格子数。游戏中,玩家每次需要从所有的爬行卡片中选择一张之前没有使用过的爬行卡片,控制乌龟棋子前进相应的格子数,每张卡片只能使用一次。
游戏中,乌龟棋子自动获得起点格子的分数,并且在后续的爬行中每到达一个格子,就得到该格子相应的分数。玩家最终游戏得分就是乌龟棋子从起点到终点过程中到过的所有格子的分数总和。
很明显,用不同的爬行卡片使用顺序会使得最终游戏的得分不同,小明想要找到一种卡片使用顺序使得最终游戏得分最多。
现在,告诉你棋盘上每个格子的分数和所有的爬行卡片,你能告诉小明,他最多能得到多少分吗?
输入输出格式
输入格式:
输入文件的每行中两个数之间用一个空格隔开。
第1行2个正整数N和M,分别表示棋盘格子数和爬行卡片数。
第2行N个非负整数,a1a2……aN,其中ai表示棋盘第i个格子上的分数。
第3行M个整数,b1b2……bM,表示M张爬行卡片上的数字。
输入数据保证到达终点时刚好用光M张爬行卡片。
输出格式:
输出只有1行,1个整数,表示小明最多能得到的分数。
输入输出样例
9 5 6 10 14 2 8 8 18 5 17 1 3 1 2 1
73
说明
每个测试点1s
小明使用爬行卡片顺序为1,1,3,1,2,得到的分数为6+10+14+8+18+17=73。注意,由于起点是1,所以自动获得第1格的分数6。
对于30%的数据有1≤N≤30,1≤M≤12。
对于50%的数据有1≤N≤120,1≤M≤50,且4种爬行卡片,每种卡片的张数不会超过20。
对于100%的数据有1≤N≤350,1≤M≤120,且4种爬行卡片,每种卡片的张数不会超过40;0≤ai≤100,1≤i≤N;1≤bi≤4,1≤i≤M。
【题目分析】
#include<iostream> using namespace std; int n,m,x,i,j,k,l; int f[51][51][51][51],a[360],b[5]; int main() { cin>>n>>m; for(i=1;i<=n;i++) cin>>a[i]; for(i=1;i<=m;i++) { cin>>x; b[x]++; } for(i=1;i<=b[1]+1;i++) for(j=1;j<=b[2]+1;j++) for(k=1;k<=b[3]+1;k++) for(l=1;l<=b[4]+1;l++) f[i][j][k][l]=max(max(f[i-1][j][k][l],f[i][j-1][k][l]),max(f[i][j][k-1][l],f[i][j][k][l-1]))+a[1+i-1+2*(j-1)+3*(k-1)+4*(l-1)]; cout<<f[b[1]+1][b[2]+1][b[3]+1][b[4]+1]; return 0; }