NOIP 2009 模拟测试总结
NOIP 2009 模拟测试总结
前序
题目的难易安排挺合适的,第一题Spy和第二题Son都可以简单通过,从第三题开始就有点难度了。第三题Trade就有点难度,代码倒不难,只是模型转换和双向BFS方法难想到(其实第四题DFS也是万万想不到的),还有Tarjan加上拓扑排序的方法有些恶心……第四题靶形数独,貌似只能搜索,只不过有技巧。
好了,开始正题。
Question1: 潜伏者 Spy
非常简单的一道题,可以用字母的ASCLL码或直接用map建立映射。
直接上代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <map>
using namespace std;int tot = 0;
char ss1[105], ss2[105], ori[105];
bool word[27], exi[27];
map<char, char> trans;int main()
{freopen( "spy.in", "r", stdin );freopen( "spy.out", "w", stdout );memset( word, false, sizeof(word));memset( exi, false, sizeof(exi));scanf( "%s", ss1 );scanf( "%s", ss2 );scanf( "%s", ori );int len = strlen(ss1);for( int i = 0; i < len; i++ ){if( !exi[ss2[i]-'A'] ) tot++;exi[ss2[i]-'A'] = true;if( !word[ss1[i]-'A'] || trans[ss1[i]] == ss2[i] ){trans[ss1[i]] = ss2[i];word[ss1[i]-'A'] = true;} else{printf( "Failed\n" );return 0;}}if( tot < 26 ) {printf( "Failed\n" );return 0;}int len1 = strlen(ori);for( int i = 0; i < len1; i++ ){if( !word[ori[i]-'A'] ){printf( "Failed\n" );return 0;}}for( int i = 0; i < len1; i++ ){printf( "%c", trans[ori[i]] );}return 0;
}
Question2: Hankson的趣味题 Son
小小的数论题,也是两种做法,一是枚举加判断,而是质因数分解。第一种好写很多,如果是直接写的话没法得满,要有点小技巧。
也不多说直接上代码:
组合判断套装
int gcd( int a, int b )
{b == 0 ? a : gcd( b, a%b );
}
int lcm( int a, int b )
{return a / gcd( a, b ) * b;
}
int cal( int x )
{if( gcd( x, a0 ) == a1 )if( lcm( x, b0 ) == b1 ) ans++;
}
循环枚举
while( n-- ){ans = 0;scanf( "%d%d%d%d", &a0, &a1, &b0, &b1 );for( int i = 1; i * i <= b1; i++ ){if( b1%i == 0 ){cal( i );if( i * i != b1 ) cal( b1 / i );}}printf( "%d\n", ans );}
有一点要注意,只用枚举到根号b1就行,否则要超时。
Question3: 最优贸易 Trade
本来一开始写的Tarjan,但是在缩点上出了点问题,最后折中输出了贪心最优值。考下来评讲才发现原来能用双向SPFA,非常巧妙!
具体思路就是,要得到最大利润,就要较低买入,较高卖出,所以就可以建立两个图,一个正向图(只保存正向边),一个反向图(只保存反向边),为什么呢?
其实是这样的。低价买入在高价卖出之前,所以我们要先找到一个从起点出发能够达到的最低价格(用low[i]数组保存从起点到i城能最低买入的价格),再用反向图从终点出发,寻找每一个能够到达的城市的最高卖出价(用high[i]表示从终点出发到i城能够卖出的最高价) ,到了这时,每个城市都有了两个属性——从起点到这个城市一路上最低买入价和从终点到这个城市一路上最高卖出价,又终点和起点肯定是相通的,就只需枚举每一个城市计算高价减去低价的值,再在其中寻找最大值就行了。
代码:
inline void spfa_1()
{q.push(1);vis[1] = true;MIN[1] = Data[1];while(!q.empty()){int x = q.front();q.pop();vis[x] = false;for( int i = head[x][0];i;i = edge[i].next ){int v = edge[i].v;if( MIN[v] > MIN[x] || MIN[v] > Data[v] ){MIN[v] = min( MIN[x],Data[v] );if( !vis[v] ) q.push(v),vis[v] = true;}}}}inline void spfa_2()
{q.push(n);vis[n] = true;MAX[n] = Data[n];ans = max( ans,MAX[n] - MIN[n] );while( !q.empty() ){int x = q.front();q.pop();vis[x] = false;for( int i = head[x][1];i;i = edge[i].next ){int v = edge[i].v;if( MAX[v] < MAX[x] || MAX[v] < Data[v] ){MAX[v] = max( MAX[x],Data[v] );ans = max( MAX[v] - MIN[v],ans );if( !vis[v] ) q.push(v),vis[v] = true;}}}
}
MAX[] 和MIN[] 分别保存上面提到的最高价和最低价。
Question4: 靶形数独 Sudoku
这题正解用深搜是我没想到的(不过也只有9 * 9 ,也是有道理的)
也算见识到了深搜的强大。
主要思想就是类似八皇后的回溯,搜索->放置->进入下一层->到达边界返回->复原再搜…..
思路非常简单,但是有技巧。首先只深搜是过不了的,很明显会TLE,所以要明智的选取对象。什么意思呢?
我们来回忆我们怎么做数独的。我们会选取剩余可能性最小的格子去填,这也就是核心思想,这可以使得搜索树变小,也就优化了时间,再加上二进制位运算来优化数字选取的过程,就可以过了。鉴于网上二进制的代码很多,我就放一个不用二进制的,初学者可以看一下。
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;int map[10][10], val[10][10] = { {0, 0, 0, 0, 0, 0, 0, 0, 0},{0,6 ,6 ,6 ,6 ,6 ,6 ,6 ,6 ,6},{0,6 ,7 ,7 ,7 ,7 ,7 ,7 ,7 ,6}, {0,6, 7, 8, 8, 8, 8, 8, 7, 6},{0,6 ,7 ,8 ,9 ,9 ,9 ,8 ,7 ,6},{0,6 ,7 ,8 ,9 ,10, 9, 8, 7, 6},{0,6 ,7 ,8 ,9 ,9 ,9 ,8 ,7 ,6},{0,6, 7, 8, 8, 8, 8, 8, 7, 6},{0,6 ,7 ,7 ,7 ,7 ,7 ,7 ,7 ,6}, {0,6 ,6 ,6 ,6 ,6 ,6 ,6 ,6 ,6},};
int box[10][10], ans = -1;
bool vis1[10][10], vis2[10][10], vis3[10][10];inline void dfs()
{int step = 10, x, y;for( register int i = 1; i <= 9; i++ )for( register int j = 1; j <= 9; j++ ){if( !map[i][j] ){int cnt = 0;for( int k = 1; k <= 9; k++ )if( !vis3[box[i][j]][k] && !vis1[i][k] && !vis2[j][k] )cnt++;if( cnt < step ){step = cnt;x = i, y = j;}} } if( step == 10 ){int tot = 0;for( int i = 1; i <= 9; i++ )for( int j = 1; j <= 9; j++ )tot += map[i][j] * val[i][j];ans = max( ans, tot );return; }for( int k = 1; k <= 9; k++ ){if( !vis3[box[x][y]][k] && !vis1[x][k] && !vis2[y][k] ){vis1[x][k] = vis2[y][k] = vis3[box[x][y]][k] = true;map[x][y] = k;dfs();map[x][y] = 0;vis1[x][k] = vis2[y][k] = vis3[box[x][y]][k] = false;}}
}
int main()
{memset( vis1, false, sizeof(vis1));memset( vis2, false, sizeof(vis2));memset( vis3, false, sizeof(vis3));for( int i = 1; i <= 9; i++ )for( int j = 1; j <= 9; j++ ){cin>>map[i][j];box[i][j] = (i-1) / 3 * 3 + (j-1) / 3 + 1;if( map[i][j] ){ /**/if( vis1[i][map[i][j]] || vis2[j][map[i][j]] || vis3[box[i][j]][map[i][j]] ){printf( "-1\n" );return 0;}vis1[i][map[i][j]] = true;vis2[j][map[i][j]] = true; vis3[box[i][j]][map[i][j]] = true;}}dfs();printf( "%d\n", ans );return 0;
}
九宫格的判断还可以像我直接打表一样自己写在程序里。