Hdu 3812 Sea Sky (模拟_搜索)

28 篇文章 0 订阅
20 篇文章 0 订阅

原文链接:http://blog.csdn.net/woshi250hua/article/details/7614161


题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3812


题目大意:给定n个字符对ai,bi,表示ai和bi相连,现在要求求出从“sea”连接到"sky"的一个序列,序列中的字符串出现两次,长度要求最大,并且字典序最小,如果没办达到要求,输出"what a pity“。


解题思路:这题是去年武汉邀请赛的题目,难度中上,在做模拟比赛的时候碰到了这题,当时没敢开,难题猛于虎。赛后为了防止比赛时再被虐花点时间过了这题。刚开始想的时候觉得是图论没思路,但一看到最多的字符串种类为16,有戏。就想着状态dp,状态就三万多个,怎么搞都不超时,又看了一下题目,要求输出字典序最小,顿时泄气,还是要搜索啊,那索性直接搜索再加个强力剪枝什么的过了他。

       开始的时候把这些字符串用map转化为节点并建图,建好图写了个十分暴力的深搜,试探性地交了下果然Tle。然后开始想如何剪枝,很常规的有两个:1、如果sea和sky不出现在输入中,那他们怎么连接都连接不到对方,人鬼殊途啊。2、如果sea和sky不在一个连通分支,那他们怎么连接都连接不到对方,不是同一个世界的人啊。这两个显然不能有效地增加效率,加上去再交还是Tle。然后开始找性质,其实剪枝就是要求我们找出题目中的性质,然后把不需要搜索的地方排除掉。本题要求的序列中字符串不能出现两次,那我们从sea节点开始遍历,如果某个节点不经过sky就能遍历,那说明在最后的深搜中可以到达,如果某些节点必须要通过sky节点,那说明这个节点必然没办法到达(可以到达的话sky节点就要经过两次),这些符合条件的节点组成几何Set1?同理,可以从end也找一次也能找到一个集合Set2。这样就得到了两个集合Set1和Set2,他们的交集就是可能出现在最后的序列中的节点。记录这个集合大小result,表示所有序列的最大长度,如果第一次碰到序列长度等于result,他就是最后的解(前提是字典序最小,这个在建图前对每个字符串排个序就可以保证)。

      举个例子: a <-->sea    b<---> sea  sky<--> b  sky<-->d  那Set1集合为sea a b sky,Set2集合为sky b d sea,他们的交集是sea sky b,只有这三个字符串可能出现在最后的序列中。 


测试数据:

10

sea a
sea b
sea c
c d
a sky
sky c
sky b
d sky

7
sea pure
pure air
air white
sky white
pure holy
holy white
sky holy

2
sea love
sky love

3
sea blue
blue green
fuckfuck white


代码:

    #include <stdio.h>  
    #include <string.h>  
    #include <string>  
    #include <map>  
    #include <vector>  
    #include <algorithm>  
    using namespace std;  
    #define MIN 30  
    #define MAX 300  
    #define max(a,b) (a)>(b)?(a):(b)  
      
      
    struct node {  
      
        char s1[MAX],s2[MAX];  
    }ver[MAX];  
      
    vector<int> maze[MIN];  
    map<string,int> mmap;  
      
    int n,result,flag,maybe[MIN][4];  
    int beg,end,maxx,tot,fa[MIN],vis[MIN];  
    char str[MAX][MIN],ans[MAX][MIN];  
      
      
    int cmp(node a,node b) {  
    //排序用到的比较函数  
        if (strcmp(a.s1,b.s1) == 0)  
            return strcmp(a.s2,b.s2) < 0;  
        else return strcmp(a.s1,b.s1) < 0;  
    }  
    int GetFa(int x) {  
    //获取父亲节点  
        int y = fa[x];  
        while (y != fa[y]) y = fa[y];  
        return y;  
    }  
    void UnionSet(int u,int v) {  
    //并查集的并操作  
        int fau = GetFa(u);  
        int fav = GetFa(v);  
        if (fav != fau) fa[fau] =fav;  
    }  
    void Create_Graph() {  
    //建好一张有序的图  
        int i,u,v;  
        for (i = 1; i <= 2 * n; ++i) {  
      
            string tps1 = string(ver[i].s1);  
            string tps2 = string(ver[i].s2);  
            if (mmap.find(tps1) == mmap.end())  
                u = ++tot,mmap[tps1] = u;  
            else u = mmap[tps1];  
            if (mmap.find(tps2) == mmap.end())  
                v = ++tot,mmap[tps2] = v;  
            else v = mmap[tps2];  
            maze[u].push_back(v);  
      
              
            UnionSet(u,v);  
            strcpy(str[u],ver[i].s1);  
            strcpy(str[v],ver[i].s2);  
            if (tps1 == "sea") beg = u;  
            else if (tps2 == "sea") beg = v;  
            if (tps1 == "sky") end = u;  
            else if (tps2 == "sky") end = v;  
        }  
    }  
      
      
    void LookConn(int k,int flag,int in) {  
    //寻找从beg开始到不经过end到达的点或者从end开始不经过beg能到达的点  
        vis[k] = 1,maybe[k][in] = 1;  
        if (k == flag) return;  
        for (int i = 0; i < maze[k].size(); ++i) {  
      
            int v = maze[k][i];  
            if (vis[v]) continue;  
            LookConn(v,flag,in);  
        }  
    }  
    void Dfs(int k,int cnt,int *vis,int *arr) {  
    //普通深搜,加个剪枝而已  
        vis[k] = 1,arr[cnt] = k;  
        if (k == end) {  
      
            if (cnt > maxx) {  
      
                maxx = cnt;  
                for (int i = 1; i <= maxx; ++i)  
                    strcpy(ans[i],str[arr[i]]);  
            }  
            if (maxx == result) flag = 1;//剪枝,result是能够到达的最多数量,因为有序,此解必是最优  
            return;  
        }  
      
      
        for (int i = 0; i < maze[k].size(); ++i) {  
      
            int v = maze[k][i];  
            if (vis[v] == 0 && maybe[v][2] == 1) {  
      
                vis[v] = 1;  
                Dfs(v,cnt+1,vis,arr);  
                if (flag) return;  
                vis[v] = 0;  
            }  
        }  
    }  
    int Solve_1A(){  
      
        int i,j,k,arr[MIN];  
      
        //寻找可能到达的点  
        memset(maybe,0,sizeof(maybe));  
        memset(vis,0,sizeof(vis));  
        LookConn(beg,end,0);  
        memset(vis,0,sizeof(vis));  
        LookConn(end,beg,1);  
        result = 0;  
        for (i = 1; i <= tot; ++i) {  
      
            if (maybe[i][1] && maybe[i][0])  
                    maybe[i][2] = 1;  
            if (maybe[i][2]) result++;  
        }  
      
        //赋初值,并从beg开始深搜  
        memset(vis,0,sizeof(vis));  
        Dfs(beg,1,vis,arr);  
        return maxx;  
    }  
      
      
      
    int main()  
    {  
        int u,v,tpu,tpk;  
        int i,j,k,t,cas = 0;  
      
      
        scanf("%d",&t);  
        while (t--) {  
      
            scanf("%d",&n);  
            mmap.clear();  
            flag = maxx = 0;  
            tot = beg = end = 0;  
            for (i = 1; i < MIN; ++i)  
                maze[i].clear(),fa[i] = i;  
      
      
            for (i = 1; i <= n; ++i) {  
            //无向处理成有向  
                scanf("%s%s",ver[i].s1,ver[i].s2);  
                strcpy(ver[i+n].s1,ver[i].s2);  
                strcpy(ver[i+n].s2,ver[i].s1);  
            }  
      
            //对节点根据字典序排序,把同一个节点出来的归类  
            sort(ver+1,ver+1+2*n,cmp);  
            Create_Graph();  
              
            //输出解  
            printf("Case %d: ",++cas);  
            if (end == 0 || beg == 0   
                || GetFa(beg) != GetFa(end))  
                printf("what a pity\n");  
            else {  
                  
                int maxx = Solve_1A();  
                for (i = 1; i <= maxx; ++i)  
                    printf(i == maxx ? "%s\n" : "%s ",ans[i]);  
            }  
        }  
    }  

本文ZeroClock原创,但可以转载,因为我们是兄弟。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值