Hdu 3812 Sea Sky (模拟_搜索)

题目链接: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原创,但可以转载,因为我们是兄弟。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值