**
全国信息学奥林匹克联赛(NOIP2010)复赛
提高组
1.机器翻译
(translate. pas/c/cpp)
【问题描述】
小晨的电脑上安装了一个机器翻译软件, 他经常用这个软件来翻译英语文章。
这个翻译软件的原理很简单, 它只是从头到尾, 依次将每个英文单词用对应的中文含义来替换。 对于每个英文单词, 软件会先在内存中查找这个单词的中文含义, 如果内存中有,软件就会用它进行翻译; 如果内存中没有, 软件就会在外存中的词典内查找, 查出单词的中文含义然后翻译,并将这个单词和译义放入内存,以备后续的查找和翻译。
假设内存中有 M个单元,每单元能存放一个单词和译义。每当软件将一个新单词存入内存前, 如果当前内存中已存入的单词数不超过 M−1, 软件会将新单词存入一个未使用的内存单元; 若内存中已存入 M个单词, 软件会清空最早进入内存的那个单词, 腾出单元来,存放新单词。
假设一篇英语文章的长度为 N个单词。给定这篇待译文章, 翻译软件需要去外存查找多少次词典?假设在翻译开始前, 内存中没有任何单词。
【输入】
输入文件名为 translate.in, 输入文件共2行。 每行中两个数之间用一个空格隔开。
第一行为两个正整数 M和 N, 代表内存容量和文章的长度。
第二行为 N个非负整数,按照文章的顺序,每个数(大小不超过1000)代表一个英文单词。 文章中两个单词是同一个单词, 当且仅当它们对应的非负整数相同。
【输出】
输出文件translate.out共1行,包含一个整数,为软件需要查词典的次数。
还算挺简单的一道题,大概的思路就是用一个循环队列模拟这个过程:记录内存最大值m,定义存储指针(其实就是个变量)K,判断只要K>=m就将K归一,其他情况下就只需要判断某个单词是否在队列里(用bool型),在,就跳过,不在,就加入队列(注意要将原位置的单词覆盖,对应单词的bool型也要设为false)。有一点要注意,就是队列初始化要用-1,因为如果有单词为0的话会有bug。
比较简单就不附代码了。
2.乌龟棋
(tortoise. pas/c/cpp)
【问题描述】
小明过生日的时候, 爸爸送给他一副乌龟棋当作礼物。
乌龟棋的棋盘是一行N个格子,每个格子上一个分数(非负整数)。棋盘第1格是唯一的起点, 第 N格是终点, 游戏要求玩家控制一个乌龟棋子从起点出发走到终点。
……
1 2 3 4 5 …… N
乌龟棋中 M张爬行卡片, 分成4种不同的类型(M张卡片中不一定包含所有4种类型的卡片,见样例),每种类型的卡片上分别标有1、2、3、4四个数字之一,表示使用这种卡片后, 乌龟棋子将向前爬行相应的格子数。 游戏中, 玩家每次需要从所有的爬行卡片中选择一张之前没有使用过的爬行卡片,控制乌龟棋子前进相应的格子数,每张卡片只能使用一次。游戏中,乌龟棋子自动获得起点格子的分数,并且在后续的爬行中每到达一个格子,就得到该格子相应的分数。玩家最终游戏得分就是乌龟棋子从起点到终点过程中到过的所有格子的
分数总和。
很明显, 用不同的爬行卡片使用顺序会使得最终游戏的得分不同, 小明想要找到一种卡片使用顺序使得最终游戏得分最多。
现在,告诉你棋盘上每个格子的分数和所有的爬行卡片,你能告诉小明,他最多能得到多少分吗?
【输入】
输入文件名 tortoise.in。 输入文件的每行中两个数之间用一个空格隔开。
第1行2个正整数 N和 M, 分别表示棋盘格子数和爬行卡片数。
第2行 N个非负整数, a1, a2, ……, aN, 其中 ai表示棋盘第 i个格子上的分数。
第3行 M个整数, b1,b2, ……, bM, 表示 M张爬行卡片上的数字。
M
输入数据保证到达终点时刚好用光 M张爬行卡片, 即 N−1=∑ bi。
1
【输出】
输出文件名 tortoise.out。
【数据范围】
对于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。输入数据保证N−1=∑bi。
一道DP题,开始做的时候想得太简单了,就开了个一维的DP数组,后来觉得不对,又开成二维…….再想想?………开三维…….发现三维都不够,模拟了一下发现状态有点多,以为是一个状压DP,看见数据这么大有点虚,就硬生生地写了一个“状压记忆化搜索”…..最后还没调出来,U盘中毒文件掉了就不放记忆化搜索了,放一个DP正解吧。
其实我考虑的状态多是对的,不过没有状压那么多。
首先我们要开一个四维数组 f[x1][x2][x3][x4]来保存所得的分数。 为什么呢?看题:有四种卡片,保证所有卡片用完就能到终点,且每种卡片不超过40张。很明显,只要枚举每一种卡片的组合就能覆盖每一种情况,在其中挑选最优解,最后输出就行了。
附上伪代码:
f[0][0][0][0] = sc[1];
for( int l1 = 0; l1 <= cnt[1]; l1++ )
for( int l2 = 0; l2 <= cnt[2]; l2++ )
for( int l3 = 0; l3 <= cnt[3]; l3++ )
for( int l4 = 0; l4 <= cnt[4]; l4++ )
{
int pos = l1 + 2 * l2 + 3 * l3 + 4 * l4 + 1;
if( l1 ) f[l1][l2][l3][l4] = max( f[l1-1][l2][l3][l4] + sc[pos], f[l1][l2][l3][l4] );
if( l2 ) f[l1][l2][l3][l4] = max( f[l1][l2-1][l3][l4] + sc[pos], f[l1][l2][l3][l4] );
if( l3 ) f[l1][l2][l3][l4] = max( f[l1][l2][l3-1][l4] + sc[pos], f[l1][l2][l3][l4] );
if( l4 ) f[l1][l2][l3][l4] = max( f[l1][l2][l3][l4-1] + sc[pos], f[l1][l2][l3][l4] );
}
四个for循环枚举卡片组合,每枚举一次挑选最优值,在加上跳到的地方的分数就行了。(sc保存每个格子的分值)
3.关押罪犯
(prison. pas/c/cpp)
【问题描述】
S城现有两座监狱,一共关押着 N名罪犯,编号分别为1~N。他们之间的关系自然也极
不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为 c的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为 c的冲突事件。
每年年末, 警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到 S城 Z市长那里。 公务繁忙的 Z市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。
在详细考察了 N名罪犯间的矛盾关系后, 警察局长觉得压力巨大。他准备将罪犯们在两座监狱内重新分配, 以求产生的冲突事件影响力都较小, 从而保住自己的乌纱帽。假设只要处于同一监狱内的某两个罪犯间有仇恨, 那么他们一定会在每年的某个时候发生摩擦。 那么,应如何分配罪犯,才能使 Z市长看到的那个冲突事件的影响力最小?这个最小值是多少?
【输入】
输入文件名为 prison.in。 输入文件的每行中两个数之间用一个空格隔开。第一行为两个正整数 N和 M, 分别表示罪犯的数目以及存在仇恨的罪犯对数。接下来的 M行每行为三个正整数 aj, bj, cj,表示 aj号和 bj号罪犯之间存在仇恨,其怨气值为 cj。
【输出】
输出文件 prison.out共1行, 为 Z市长看到的那个冲突事件的影响力。如果本年内监狱中未发生任何冲突事件,请输出 0。
【数据范围】
对于30%的数据有 N≤15。
对于70%的数据有 N≤2000, M≤50000。
对于100%的数据有N≤20000, M≤100000。
一开始想得有些复杂,打算用Kruskal找到最小生成树再删边。
如样例图, 找到 1-> 4-> 2-> 3 是一颗最小生成树,然后由定义知最小生成树就是一条链,就O(n)枚举每一条边,看删哪条使得剩下的两部分最大冲突最小。
…….非常复杂,160多行,没调出来…..
……Kruskal…….
void kruskal()
{
memset( exi, false, sizeof(exi));
sort( able+1, able+tail+1, cmp );
for( int i = 1; i <= numm; i++ )
fa[pers[i]] = pers[i];
st = edg[able[1]].from;
exi[st] = true;
for( int i = 1; i <= numm-1; i++ )
{
int x = edg[able[i]].from, y = edg[able[i]].dest;
if( getfa[x] != getfa[y] )
{
chois[++cnt] = able[i];
fa[y] = fa[x];
exi[edg[i].dest] = true;
}
}
}
然后发现一个问题…..如果不连通呢?赶紧加了一个DFS判连同,顺便把同一个连通分量里的点和边加进数组pers 和 able……
DFS……
void dfs_way( int u, int f )
{
for( int i = head[u]; i; i = poin[i].last )
{
int v = edg[i].dest;
if( v == f ) continue;
pers[++numm] = v;
able[++tail] = stat[v];
vis[v] = true;
dfs_way( v, u );
}
}
边表+邻接表……
struct rod
{
int dest, from, hat;
rod()
{
dest = from = hat = 0;
}
} edg[maxx<<1];
int ppoi = 0, head[maxn], taill = 0;
struct usea
{
int dest, last, hatt;
} poin[maxx<<1];
void addpoin( int x, int y, int z )
{
ppoi++;
poin[ppoi].dest = y;
poin[ppoi].last = ppoi;
poin[ppoi].hatt = z;
head[x] = ppoi;
}
int num = 0;
int able[maxx<<1], tail = 0, pers[maxn], numm = 0, chois[maxn], cnt = 0, stat[maxn];
void adde( int x, int y, int w )
{
num++;
edg[num].dest = y;
edg[num].from = x;
edg[num].hat = w;
stat[x] = numm;
stat[y] = numm;
}
DFS算链的前缀和(还要先找出这条链的头和尾,超麻烦啊啊啊)
void dfs( int u, int tot, int f )
{
if( tot == numm ) return;
for( int i = head[u]; i; i = poin[i].last )
{
int v = poin[i].dest;
if( v == f || !exi[v] ) continue;
dis[v] = dis[u] + poin[i].hatt;
point[++cntt] = v;
dfs( v, tot+1, u );
}
}
最后计算链上最大值……
long long minhat()
{
long long res = 0x7fffffff;
for( int i = 1; i <= cntt-1; i++ )
{
res = minn( res, dis[i] + dis[cntt] - dis[i+1] );
}
return res;
}
最后在main函数里加上操作…
for( int i = 1; i <= n; i++ )
{
if( !vis[i] )
{
u = i;
vis[u] = true;
pers[++numm] = u;
dfs_way( u, 0 );
kruskal();
dis[st] = 0;
point[++cntt] = st;
dfs( st, 1, 0 );
ress += minhat();
}
}
编完了一次编译通过那个兴奋…..然后发现…..它要的是两人之间最大冲突值…….当时就崩溃了….
最后评讲的时候讲的是并查集……核心代码不到15行…..
有种想砸电脑的冲动,但是是学校的电脑…..没办法,接受现实。
接下来讲一下正解:
因为要求的是两人之间冲突最大值,所以我们可以将犯人组合按冲突大小排序 然后从大到小拆散他们——也就是将他们放在不同的监狱。就这样一组一组地拆,拆到有一组已经在同一个监狱了,就输出他们的冲突值。为什么这是对的呢?假设我们不拆这一组,而是继续将他们放在不同的监狱,到么导致的后果是——之所以他们在一个监狱,是因为他们其中的至少一个人与另外一个监狱的人有更大的冲突,如果将他们放回去,肯定会使冲突值变大(先放的组合冲突值一定大于后放的组合),所以只能将他们放在一起,又得知后面任意一组的冲突值不会高于这一组,因此得出这一组的冲突值即为最大值,故输出。(注意,如果全部分完了还没有出现要输出的情况,一定要输出0)
附上代码:
#include
#include
#include
#include
using namespace std;
const int maxx = 100005;
const int maxn = 20005;
int n, m, num = 0, fa[maxn*2];
struct rod//存组
{
int x, y, hat;
} pai[maxx];
int cmp( const rod & a, const rod & b )//sort
{
if( a.hat < b.hat ) return 0;
return 1;
}
int getfa( int a )//并查集
{
if( fa[a] == a ) return a;
return fa[a] = getfa( fa[a] );
}
int solve()//核心
{
for( int i = 1; i <= m; i++ )
{
int far = getfa( pai[i].x ), fbr = getfa( pai[i].y );
if( far == fbr ) return pai[i].hat;
fa[far] = getfa( pai[i].y + n );
fa[fbr] = getfa( pai[i].x + n );
}
return 0;
}
int main()
{
freopen( "prison.in", "r", stdin );
freopen( "prison.out", "w", stdout );
cin>>n>>m;
for( int i = 1; i <= m; i++ )
{
int x, y, hat;
scanf( "%d%d%d", &x, &y, &hat );
pai[i].x = x;
pai[i].y = y;
pai[i].hat = hat;
}
for( int i = 1; i <= 2*n; i++ ) fa[i] = i;
sort( pai+1, pai+m+1, cmp );
printf( "%d\n", solve() );
return 0;
}
4 .引水入城
(flow. pas/c/cpp)
【问题描述】
湖泊
沙漠
在一个遥远的国度,一侧是风景秀美的湖泊,另一侧则是漫无边际的沙漠。该国的行政区划十分特殊, 刚好构成一个 N行 M列的矩形, 如上图所示, 其中每个格子都代表一座城市, 每座城市都有一个海拔高度。
为了使居民们都尽可能饮用到清澈的湖水, 现在要在某些城市建造水利设施。 水利设施有两种, 分别为蓄水厂和输水站。 蓄水厂的功能是利用水泵将湖泊中的水抽取到所在城市的蓄水池中。因此,只有与湖泊毗邻的第1行的城市可以建造蓄水厂。而输水站的功能则是通过输水管线利用高度落差, 将湖水从高处向低处输送。 故一座城市能建造输水站的前提, 是存在比它海拔更高且拥有公共边的相邻城市, 已经建有水利设施。
由于第 N行的城市靠近沙漠, 是该国的干旱区, 所以要求其中的每座城市都建有水利
设施。那么,这个要求能否满足呢?如果能,请计算最少建造几个蓄水厂;如果不能,求干旱区中不可能建有水利设施的城市数目 。
【输入】
输入文件名为 flow.in。 输入文件的每行中两个数之间用一个空格隔开。
输入的第一行是两个正整数 N和 M, 表示矩形的规模。
接下来 N行, 每行 M个正整数, 依次代表每座城市的海拔高度。
【输出】
输出文件名为 flow.out。
输出有两行。如果能满足要求,输出的第一行是整数1,第二行是一个整数,代表最少建造几个蓄水厂;如果不能满足要求,输出的第一行是整数0,第二行是一个整数,代表有几座干旱区中的城市不可能建有水利设施。
这道题其实不是很难。根据题意,只需要覆盖完沿沙漠的一排城市就可以了,所以我们先用BFS算出每一个沿海的城市建造蓄水池可以覆盖多长的沙漠城市,在区间覆盖DP算出怎样用最少的城市覆盖完沙漠城市。(注意最好用BFS,DFS有坑…)
BFS:
void bfs( int stx, int sty )
{
memset( vis, false, sizeof(vis));
int sitx[maxx*maxx], sity[maxx*maxx];
int k1 = 0, k2 = 1;
sitx[1] = stx; sity[1] = sty;
vis[stx][sty] = true;
do
{
k1++;
for( int i = 1; i <= 4; i++ )
{
int nx = sitx[k1] + xx[i], ny = sity[k1] + yy[i];
if( nx > n || nx < 1 || ny > m || ny < 1 ) continue;
if( map[sitx[k1]][sity[k1]] <= map[nx][ny] ) continue;
vis[nx][ny] = true, vv[nx][ny] = true;
k2++;
sitx[k2] = nx, sity[k2] = ny;
}
}while( k1 < k2 );
int lf = 0, rg = 0;
for( int i = 1; i <= m; i++ ) if( vis[n][i] ){lf = i; break;}
for( int i = m; i >= 1; i-- ) if( vis[n][i] ){rg = i; break;}
/*if( lf && rg ) */peri[++num].x = lf, peri[num].y = rg;
}
记得每次BFS前都要重置vis数组,全局还要有一个vv数组判断能不能被完全覆盖。
写这个只得了70分,应该是DP写错了就不附代码了……
总之考试时读题太重要了,一定要 细心读题,用心想题,专心写题,耐心改题。