NOIP2009
第一题
一看到字母就想用map做了,先读入再处理mp,过程中判断是否合法,最后逐个字母翻译就行。不用map用0到25表示26个大写字母也是没问题的。题目较简单,在此不再赘述。只是要注意三种非法情况(一对多或多对一或不完全,当时就是因为只写了两种就wa了)。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>
#include <cstring>
using namespace std;
map<char,char>mp;
char sa[105], sb[105], qs[105], ans[105];
int used[26], usedd[26];
int main(){
freopen("spy.in", "r", stdin);
freopen("spy.out", "w", stdout);
scanf("%s%s%s", sa, sb, qs);//一起读入
int lena = strlen(sa);
int lenb = strlen(sb);
int lenq = strlen(qs);
if(lena != lenb){printf("Failed");return 0;}
for(int i=0; i<lena; i++){
if(mp[sa[i]] != 0 && mp[sa[i]] != sb[i]){printf("Failed");return 0;}//一对多非法
mp[sa[i]] = sb[i];
used[sa[i]-'A'] = 1;//不完全非法
usedd[sb[i]-'A'] = 1;//多对一非法(sa完全了,sb不完全就是多对一)
}
char cc = 'A';
for(int i=0; i<=25; i++)
if(used[i] == 0 || usedd[i] == 0){printf("Failed");return 0;}
for(int i=0; i<lenq; i++){
printf("%c", mp[qs[i]]);
}
printf("\n");
return 0;
}
第二题
唯一分解定理求解。先初始化50000以下的素数(最大值开根求得),用线性筛要快一些,像这种有多组数据的题目,一定要清空数组!!!(错太多次了)。mi的四个数组存的是分解质因数后的指数,对每一位指数进行讨论,求出其可能值,再累乘起来就是答案了。注意,最后有一个特判,b0>1,表示的是可能分解之后剩下一个大于50000的质数(一定是质数,因为50000*50000已超上限),所以取或是不取这个数又有两种情况,*2。详细原理不好叙述,请读者自己用数据模拟。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 50010;
int aa[N], su[N], mia[N], mib[N], mic[N], mid[N];
int num;
void qsu(){//初始化素数
int idc = 0;
for(int i=2; i<=N; i++){
if(aa[i] == 1) continue;
su[++idc] = i;
for(int j=i*2; j<=N; j+=i)
aa[j] = 1;
}
num = idc;
}
int main(){
freopen("son.in", "r", stdin);
freopen("son.out", "w", stdout);
int T;
scanf("%d",&T);
qsu();
while(T--){
memset(mia,0,sizeof(mia));
memset(mib,0,sizeof(mib));
memset(mic,0,sizeof(mic));
memset(mid,0,sizeof(mid));
int a0, a1, b0, b1;
scanf("%d%d%d%d", &a0,&a1,&b0,&b1);
for(int i=1; i<=num, b0>1 ; i++){
if(i>num) break;
if(b0 % su[i] == 0){
mia[i]++; b0 /= su[i]; i--;
}
}
for(int i=1; i<=num, b1>1 ; i++){
if(i>num) break;
if(b1 % su[i] == 0){
mib[i]++; b1 /= su[i]; i--;
}
}
for(int i=1; i<=num, a0>1 ; i++){
if(a0 % su[i] == 0){
mic[i]++; a0 /= su[i]; i--;
}
}
for(int i=1; i<=num, a1>1 ; i++){
if(a1 % su[i] == 0){
mid[i]++; a1 /= su[i]; i--;
}
}//分解
int ans = 1;
for(int i=1; i<=num+1; i++){
if(mia[i] == mib[i] && mid[i] == mic[i]){
int x = mia[i] + 1 - mid[i];
if(x < 1) x = 0;
ans *= x;
}
if(mia[i] == mib[i] && mia[i] < mid[i]){
ans *= 0;
}
if(mia[i] != mib[i] && mib[i] < mid[i]){
ans *= 0;
}
if(mia[i] != mib[i] && mib[i] > mid[i] && mid[i] != mic[i]){
ans *= 0;
}
}
int c = 1;
if(b0>1) c = 2;//最后剩下的质数大于50005时,取与不取应该再*2
printf("%d\n", ans*c);
}
return 0;
}
第三题
这道题当时没有想出比较优的解法。一直在想怎么一遍dfs或者bfs就把同一路径中的max和min都搞定,结果发现很难实现。后来讨论到才发现其实完全没有必要一次性就搞定,直接把每一个城市都当做一个节点,从起点到它求一个min,再从终点到它求一个max,等于是开头存遍的时候存两次,然后直接两遍bfs,代码都是差不多的,只需要换一个方向就行。因为有一个买卖关系,所以说两次bfs不能交换位置(先买再卖)。又因为这可能是一个带环的图,所以采用spfa。最后只需要每个点(每条路径)上的max-min取一个最大值就行了。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int M = 500010;
const int N = 100010;
int n, m, idc;
int head1[N], head2[N], w[N], minn[N], maxx[N];
struct ln{
int next1,next2,u,v;
ln(){next1 = -1; next2 = -1;}
}ed[M * 2];//邻接表开两倍
void adde(int u,int v){
idc++;
ed[idc].u = u;
ed[idc].v = v;
ed[idc].next1 = head1[u];
head1[u] = idc;
ed[idc].next2 = head2[v];
head2[v] = idc;
}
void bfs1(){
int head = 0, tail = 1;
int L[N], flag[N];
memset(flag,0,sizeof(flag));
memset(minn,0x7f,sizeof(minn));
L[tail] = 1; minn[1] = w[1]; flag[1] = 1;
do{
head++;
int u = L[head];
for(int k = head1[u]; k; k = ed[k].next1){
int v = ed[k].v;
if(minn[v] > minn[u] || w[v] < minn[v]){
minn[v] = min(w[v], minn[u]);
if(flag[v] == 0) L[++tail] = v;
flag[v] = 1;
}
}
flag[u] = 0;//出队
}while(head < tail);
}
void bfs2(){
int head = 0, tail = 1;
int L[N], flag[N];
memset(flag,0,sizeof(flag));
memset(maxx,-1,sizeof(maxx));
L[tail] = n; maxx[n] = w[n]; flag[n] = 1;
do{
head++;
int u = L[head];
for(int k = head2[u]; k; k = ed[k].next2){
int v = ed[k].u;
if(maxx[u] > maxx[v] || w[v] > maxx[v]){
maxx[v] = max(w[v], maxx[u]);
if(flag[v] == 0) L[++tail] = v;
flag[v] = 1;
}
}
flag[u] = 0;
}while(head < tail);
}
int main(){
freopen("trade.in","r",stdin);
freopen("trade.out","w",stdout);
memset(head1,-1,sizeof(head1));
memset(head2,-1,sizeof(head2));
scanf("%d%d", &n, &m);
for(int i=1; i<=n; i++)
scanf("%d", &w[i]);
for(int i=1; i<=m; i++){
int u, v, k;
scanf("%d%d%d", &u, &v, &k);
if(k == 1) adde(u,v);
else{
adde(u,v);
adde(v,u);
}
}
bfs1();//先买
bfs2();//再卖
int ans = 0;
for(int i=1; i<=n; i++)
ans = max(ans, maxx[i] - minn[i]);
printf("%d", ans);
return 0;
}
第四题
当听说这道题正解就是搜索时还是有点无语的(除了跳舞链,听说应用不太广泛就没有学,不过用来解数独还是挺六的)。大神教了一种神奇的优化方案,位运算。类似状压dp,用二进制记录状态,位运算寻找要填的地方,以及排除不可能填的数。一下子就把O(27)的排除降成了O(1)。这下基本上就不会T掉了。最开始构造一个score数组就避免了最后for一遍求ans的情况了(直接带着走,填一个加一个)。还有一个优化就是从可能性最小的地方填起(按照每行中空位的多少,从小到大排出一个填的顺序,至于为什么,就想想做数独的时候怎么做容易就好了)。还有就是据说倒着填会快一些(可能是数据想卡吧)。
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int score[10][10] = {{0,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 emp[10],colu[10],rowu[10],coll[10],order[10],map[10][10],latu[4][4];
int ans,p,sum;
int r[1 << 10];
int init(){
memset(colu,0,sizeof(colu));
memset(rowu,0,sizeof(rowu));
memset(emp,0,sizeof(emp));
memset(coll,0,sizeof(coll));
memset(order,0,sizeof(order));
memset(map,0,sizeof(map));//存图
memset(latu,0,sizeof(latu));
sum = 0;
for(int i=1; i<=9; i++)
for(int j=1; j<=9; j++) {
scanf("%d", &map[i][j]);
if(map[i][j]){
sum += map[i][j] * score[i][j];//积分
emp[i] |= 1 << (j - 1);//每行的已填位置
p = 1 << (map[i][j] - 1);//转二进制
if((colu[i] & p) || (rowu[j] & p) || (latu[(i - 1) / 3][(j - 1) / 3] & p)){
//已经出现重复
printf("-1");
return 0;
}
colu[i] |= p;//列
rowu[j] |= p;//行
latu[(i - 1) / 3][(j - 1) / 3] |= p;//块
}
else
coll[i]++;//每行待填个数
}
for(int i=1; i<=9; i++)
order[i] = i;//搜索顺序:从已填数多的一行开始填
for(int i=1; i<=8; i++)
for(int j=i+1; j<=9; j++)
if(coll[order[i]] > coll[order[j]]){
int t = order[i];
order[i] = order[j];
order[j] = t;//从限制多的行填到限制少的行,数组保存填行的顺序
}
for(int i=1; i<=9; i++)
if(coll[order[i]]) return i;//此行有空
}
void dfs(int k, int sum){
if(k == 10)
ans = max(ans, sum);
else{
int x,first,row,able,pos,col;
col = order[k];//第几行
x = 511 - emp[col];//511->111111111
first = x & ( -x );//未填的最后一个位置
emp[col] |= first;
row = r[first]+1;//找格子 务必加一
pos = 511 - (colu[col] | rowu[row] | latu[(col - 1) / 3][(row - 1) / 3]);
//可以填的数(排除了不能填的数)
while(pos){ //填格子
able = pos & ( -pos );
pos -= able;//枚举能填的数
map[col][row] = r[able]+1;//务必加一
colu[col] |= able;
rowu[row] |= able;
latu[(col - 1) / 3][(row - 1) / 3] |= able; //更新
if(x == first)//一行填完
dfs(k+1, sum + map[col][row] * score[col][row]);
else//填此行的上一个
dfs(k, sum + map[col][row] * score[col][row]);
colu[col] -= able;
rowu[row] -= able;
latu[(col - 1) / 3][(row - 1) / 3] -= able; //还原
}
emp[col] -= first;//还原
}
}
int main(){
freopen("sudoku.in","r",stdin);
freopen("sudoku.out","w",stdout);
for(int i=1; i<=9; i++){
r[1 << i] = i;
}
int st = init();//dfs起始行
if(!st){//非法
printf("-1");
return 0;
}
dfs(st, sum);
if(!ans)
printf("-1");
else
printf("%d",ans);
return 0;
}
考试总结
这一次的失误主要在于时间上,首先就是第一题解决的速度不够,用了将近40min,而且分还没有得全,需要注意的是应该在做完题目之后再读一遍题目,检查一下有没有忽略的条件,限制等。切忌样例数据一通过就跑。最好在读题的时候就形成成熟的思路,考虑到所有的情况。站在自己想出解决方案的对立面,找出方法的漏洞。还有一个技巧值得一提,就是数据分治。在数据跨度较大,或是部分数据有明显特征的时候,学会用不同的方法解决不同类的数据。这样就可以多得部分分数(要注意的就是首先要提升代码速度)。谈到代码速度就不得不说说暴力了。对于一些毫无头绪的题目,就只能选择暴力的方式骗分了,不过这就会又产生一个问题,就是说也许你写暴力的时间都多于想出正解的时间了,但是分数却要少很多。为什么写暴力?就是为了节省时间,所以说又快又好的写一个暴力就显得至关重要了。很多的时候我们都不太敢写暴力,可事实就是需要我们果断地巧妙地写暴力。还有一个就是暴力的优化,一些大神写一个暴力都能得七八十分(orz~),各种优化一加上跑的飞快!怎么做到这一点呢?概括的说就是要寻找题目的特性,分析题目。这显然是一个比较灵活的东西,只有靠自己的悟性和训练了。