NOIP2009 潜伏者 Hankson的趣味题 最优贸易 靶形数独

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~),各种优化一加上跑的飞快!怎么做到这一点呢?概括的说就是要寻找题目的特性,分析题目。这显然是一个比较灵活的东西,只有靠自己的悟性和训练了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值