【算法设计与数据结构】URAL 1323. Classmates

题目链接

http://acm.timus.ru/problem.aspx?space=1&num=1323

题目大意

    叶良辰班里有n个人,他想让大家都知道他的大名,但是国庆节大家都回家了,他只能通过电话让自己出名:“你只需要记住,我叫叶良辰,把我的大名告诉你的同学,待我大名远扬,我叶良辰必有重谢!”

    已知通一次电话需要1分钟,给定m对关系,每对关系中的两个人可以互相通话。

    求:叶良辰闻名全班所需要的最短时间。

解题思路

    先考虑单个人的行为:
    假如我接到了电话,那么有哪些可能发生的情况呢?
    1.我能联系到的同学都已经接到电话了:我不需要再做任何事;
    2.我刚好有一个未接到电话的同学:我打个电话给他;
    3.我有x个(x>1)未接到电话的同学:依次遍历x个同学,比如第一次选择A同学进行通知,当所有人都通知完毕后,吃一颗后悔药,第二次改变主意选择B同学进行通知。

    现考虑整体的行为:
    假如已经有x个人接到了电话,则在下一轮的打电话中,每个人都会遇到以上3种情况中的其中一种,如果是情况3,每个人都有吃后悔药的机会——实际上这是一个遍历的过程。

    总的来说,算法过程如下:
    做出选择->通知到所有人后记录最短时间->吃后悔药->重新做选择->再次通知到所有人后更新最短时间。

算法步骤

1) 初始化数据:

    string s[MAXN];         //序号到名字的映射
    map<string, int> mp;    //名字到序号的映射
    bool arc[MAXN][MAXN];   //邻接矩阵
    bool vis[MAXN];         //访问标记
    int Step = 999999999;   //最小的步数
    vector<int> q[MAXN];    //每一步中,已收到通知的列表

2) 将叶良辰加入第一次的队列q[0]中,以此为起点进行遍历。
3) 若所有人都收到了通知,更新ans,结束递归并返回步骤6。
    否则,取出第i次的队列q[i]中的第j个人的序号,设为k。
4) 若k没有未收到通知的邻居,如果k是第i次最后做出选择的人,则进入第i+1次的通知,使i=i+1,进入步骤3;
    如果k后面还有人没做出选择,则使j=j+1,进入步骤3。
5) 若k有未收到通知的邻居,则k选择一个邻居l进行通知,并标记已收到通知的人+1,将k以及l加入第i+1次的队列中。如果k是第i次最后做出选择的人,则进入第i+1次的通知,转入步骤3;如果k后面还有人没做出选择,则使j=j+1,转入步骤3。
6) 当递归返回时,执行遍历操作:将k以及l从第i+1次的队列中剔除,将l标记为未访问——这是一次吃“后悔药”的操作。

源程序

#include<iostream>
#include<algorithm>
#include<map>
#include<vector>
#include<cstring>
using namespace std;

const int MAXN=12;

int n,m;
string s[MAXN]; //序号到名字的映射
map<string, int> mp; //名字到序号的映射
bool arc[MAXN][MAXN];   //邻接矩阵
bool vis[MAXN]; //访问标记
int Step = 999999999;   //最小的步数
struct node //存放前一个结点的坐标p,到达本结点所需的步数step
{
    int p,step;
};
vector<int> q[MAXN];    //每一步中,已收到通知的列表

node tmp[MAXN], ans[MAXN];  //分别存放计算的中间值和最终答案

void dfs(int i, int j, int num, int step)
{
    if (num == n)
    {
        if (step < Step)
        {
            Step = step;
            for (int i = 0; i < n; i++)
            {
                ans[i].p = tmp[i].p;
                ans[i].step = tmp[i].step;
            }
        }
        return;
    }
    int k = q[i][j];
    bool flag = false;
    for (int l = 0; l < n; l++) //q[i][j]选邻居
    {
        if (!vis[l]&&arc[k][l]) //选邻居l
        {
            flag = vis[l] = true;
            q[i+1].push_back(k);
            q[i+1].push_back(l);
            tmp[l].p = k;
            tmp[l].step = i+1;
            if (j == q[i].size()-1)
                dfs(i+1,0,num+1,tmp[l].step); //在第i次选邻居时,所有j都选了,则进入下一次(i+1),step保持最大的一个(最新)
            else
                dfs(i,j+1,num+1, tmp[l].step); //还有人没选,继续选
            q[i+1].erase(q[i+1].end()-1);   //退回,假设不选择邻居l,而选择下一个邻居
            q[i+1].erase(q[i+1].end()-1);
            vis[l] = false;
        }
    }
    if (!flag)  //没有邻居了q[i][j]
    {
        if (j == q[i].size()-1)
            dfs(i+1,0,num,step);
        else
            dfs(i,j+1,num,step);
    }
}

int main()
{
    cin>>n>>m;
    int num = 0;
    memset(arc,0,sizeof(arc));
    memset(vis,0,sizeof(vis));
    while (m--)
    {
        string s1,s2;
        cin>>s1>>s2;
        if (mp.find(s1) == mp.end())
        {
            s[num] = s1;
            mp[s1] = num++;
        }
        if (mp.find(s2) == mp.end())
        {
            s[num] = s2;
            mp[s2] = num++;
        }
        arc[mp[s1]][mp[s2]] = arc[mp[s2]][mp[s1]] = true;
    }
    string start;
    cin>>start;
    if (n == 1)
    {
        cout<<'0'<<endl;
        return 0;
    }
    int v = mp[start];
    tmp[v].p = -1;
    tmp[v].step = 0;
    q[0].push_back(v);
    vis[v] = true;
    dfs(0,0,1,0);
    cout<<Step<<endl;
    for (int i = 1; i <= Step; i++)
    {
        int cnt = 0;
        for (int j = 0; j < n; j++)
            if (ans[j].step == i)
                ++cnt;
        cout<<cnt<<endl;
        for (int j = 0; j < n; j++)
            if (ans[j].step == i)
                cout<<s[ans[j].p]<<' '<<s[j]<<endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值