考了一套10年的NOIP真题,虽然比2016年 的要简单许多,但还是没有做好。
机器翻译
【问题描述】
假设内存中有M 个单元,每单元能存放一个单词和译义。每当软件将一个新单词存入内存前,如果当前内存中已存入的单词数不超过M−1,软件会将新单词存入一个未使用的内存单元;若内存中已存入M 个单词,软件会清空最早进入内存的那个单词,腾出单元来,存放新单词。
假设一篇英语文章的长度为N 个单词。给定这篇待译文章,翻译软件需要去外存查找多少次词典?假设在翻译开始前,内存中没有任何单词。
【输入】
输入文件名为translate.in,输入文件共2 行。每行中两个数之间用一个空格隔开。第一行为两个正整数M 和N,代表内存容量和文章的长度。第二行为N 个非负整数,按照文章的顺序,每个数(大小不超过1000)代表一个英文单词。文章中两个单词是同一个单词,当且仅当它们对应的非负整数相同。
【输出】
输出文件translate.out 共1 行,包含一个整数,为软件需要查词典的次数。
【输入输出样例1】
translate.in translate.out
3 7
1 2 1 5 4 4 1
这道题没有做头,然而我一上样例就爆了,后来发现是head和tail转移不当导致空了一个点
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int main() {
int n,m,b[1005],c[1105];
int tail=1,head=1,ans = 0;
memset(b,0,sizeof(b));
scanf("%d%d", &m, &n);
for(int i = 1; i <= n; i++ ) {
int a;
scanf("%d", &a);
if(b[a] == 1){
continue;//已存在
}
else {
if(head-tail < m) {//队列未满
c[head] = a;
head++;
b[a] = 1;
ans++;
} else{//队列已满
b[c[tail]] = 0;
tail++;
c[head] = a;
head++;
b[a] = 1;
ans++;
}
}
}
printf("%d", ans);
return 0;
}
第二题讲起来是挺简单的,但我就是没有想到DP怎么做,硬上了一个dfs,果断T掉
2.
乌龟棋
乌龟棋的棋盘是一行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个整数,表示小明最多能得到的分数。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,num[5],a[355],f[41][41][41][41];//num为一种牌的数目,f保存了当前用各种卡的数目
int main() {
scanf("%d%d", &n, &m);
for(int i = 0; i < n; i++ )
scanf("%d", &a[i]);
for(int i = 1; i <= m; i++ ){
int ghjohklj;
scanf("%d", &ghjohklj);
num[ghjohklj]++;
}
for(int i = 0; i <= num[4]; i++ )
for(int j = 0; j <= num[3]; j++ )
for(int k = 0; k <= num[2]; k++ )
for(int l = 0; l <= num[1]; l++ ){
if(i != 0) f[i][j][k][l] = max(f[i][j][k][l],f[i-1][j][k][l]);
if(j != 0) f[i][j][k][l] = max(f[i][j][k][l],f[i][j-1][k][l]);
if(k != 0) f[i][j][k][l] = max(f[i][j][k][l],f[i][j][k-1][l]);
if(l != 0) f[i][j][k][l] = max(f[i][j][k][l],f[i][j][k][l-1]);
f[i][j][k][l] += a[i*4+j*3+k*2+l];//增加当前牌的积分
}
printf("%d",f[num[4]][num[3]][num[2]][num[1]]);
return 0;
}
关押罪犯
S城现有两座监狱,一共关押着N名罪犯,编号分别为1~N。。我们用“怨 气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为 c 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为c的冲突事件。
每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到S城Z市长那里。公务繁忙的Z市长只会去看列表中的第一个事件的影响力。
那么,应如何分配罪犯,才能使Z市长看到的那个冲突事件的影响力最小?这个最小值是多少?
Input
第一行为两个正整数N和M,分别表示罪犯的数目以及存在仇恨的罪犯对数。接下来的M行每行为三个正整数 aj,bj,cj,表示aj号和bj号罪犯之间存在仇恨,其怨气值为cj。数据保证1≤aj
Output
输出共1行,为Z市长看到的那个冲突事件的影响力。如果本年内监狱 中未发生任何冲突事件,请输出0。
Sample Input
4 6
1 4 2534
2 3 3512
1 2 28351
1 3 6618
2 4 1805
3 4 12884
Sample Output
3512
【样例说明】
罪犯之间的怨气值如下面左图所示,右图所示为罪犯的分配方法,市长看到的冲突事件 影响力是3512(由2号和3号罪犯引发)。其他任何分法都不会比这个分法更优。
这道题首先让人想到并查集,因为分了2个监狱,只需查找是否在一个监狱即可。因此我们可以先按怨气值排序,并查找他们的情况,若不在同一监狱直接跳过,在同一监狱就直接输出当前怨气值,都不在的话就存在不同监狱即可。
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
typedef struct {
int a,b,anger;
}Node;
int n,m,ans;
int f[40005];
Node crime[100005];
void qsort(int l,int r){
int i=l,j=r,mid=crime[(i+j)/2].anger;
while(i<=j){
while(crime[i].anger>mid) i++;
while(crime[j].anger<mid) j--;
if(i<=j){
Node t=crime[i];
crime[i]=crime[j];
crime[j]=t;
i++;
j--;
}
}
if(i<r) qsort(i,r);
if(j>l) qsort(l,j);
}
int find(int x) {
if(x != f[x])
f[x] = find(f[x]);
return f[x];
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++ )
scanf("%d%d%d", &crime[i].a, &crime[i].b, &crime[i].anger);
for(int i = 1; i <= n*2; i++ )
f[i] = i;
qsort(1,m);
for(int i = 1; i <= m; i++ ) {
int xa = find(crime[i].a);
int xb = find(crime[i].b);
if(xa == xb) {
ans = i;
break;
} else {
f[xa] = f[find(crime[i].b+n)];
f[xb] = f[find(crime[i].a+n)];
}
}`
`
printf("%d",crime[ans].anger);
return 0;
}
引水入城
题目略
这道题大概的意思是让我们找到最少的城市数,因此可以先dfs或bfs一遍找出第一行所有可以搜的城市能到的点(注意是可以搜的点,即其他城市到不了的点,这样可以大大减少时间复杂度),且能到的点都要打标记,如果有没有标记的点就是到不了的,直接输出0并统计个数。如果都能到,就去DP一遍,类似于区间合并即可。
//应该是比较短的了
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m;
int h[505][505];
bool fla=0, bo[505], flag[505][505];
//flag存是否搜过每次需清零,bo存是否到过那个点,用于判断能否到达
int f[505], l[505], r[505];
//left,right
void dfs(int s,int x,int y) {
//s是当前点的编号,一直不会变
if(x == n) {
bo[y] = 1;
if(y<l[s] || !l[s]) l[s]=y;
if(y>r[s]) r[s]=y;
}
flag[x][y] = 1;
if(x > 1 && flag[x-1][y] != 1 && h[x][y] > h[x-1][y]) dfs(s,x-1,y);
if(x < n && flag[x+1][y] != 1 && h[x][y] > h[x+1][y]) dfs(s,x+1,y);
if(y > 1 && flag[x][y-1] != 1 && h[x][y] > h[x][y-1]) dfs(s,x,y-1);
if(y < m && flag[x][y+1] != 1 && h[x][y] > h[x][y+1]) dfs(s,x,y+1);
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++ )for(int j = 1; j <= m; j++ )scanf("%d", &h[i][j]);
for(int i= 1; i <= m; i++ ){
if(i > 1 && h[1][i] < h[1][i-1]) continue;
if(i < m && h[1][i] < h[1][i+1]) continue;
dfs(i,1,i);
if(i != m) memset(flag,0,sizeof(flag));
}
int sum = 0;
for(int i = 1; i <= m; i++ )
if(!bo[i]){fla = 1;sum ++;}
if(fla){printf("0\n%d",sum);return 0;}
else printf("1\n");
for(int i=1;i<=m;i++) {
//DP过程
for(int j=l[i];j<=r[i];j++){
if(f[j]>f[l[i]-1]+1 || !f[j])
f[j]=f[l[i]-1]+1;
}
}
printf("%d",f[m]);
return 0;
}