中心台站建设

中心台站建设

时间限制:1 s 内存限制:128 MB
【问题描述】
n个城市之间有通讯网络,从这n个城镇中选定几座城镇,在那里建立中心台站,要求它们与其它各城镇相邻,同时为降低造价,要使中心台站数目最少。
【输入格式】
输入文件有若干行
第一行,一个整数n,表示共有n个城市(2<=n<=100)
下面有n行,每行有n个数字。第p行第q列的数字表示城镇p与城镇q之间有无直接通讯线路。数字为1表示有,0表示无。
【输出格式】
输出文件有若干行
第一行,1个整数a,表示最少中心台站数目。
第二行一个整数b,表示共有b种方案。下面有b行,每行有a个整数,表示一种建站方案。多种方案输出时,输出顺序按城镇编号由小到大字典序输出。
【输入输出样例】
输入文件名: zpj.in
6
0 1 1 1 0 0
1 0 0 1 0 0
1 0 0 0 1 0
1 1 0 0 0 1
0 0 1 0 0 1
0 0 0 1 1 0
输出文件名:zpj.out
2
5
1 5
1 6
2 5
3 4
4 5

最小支配集

为了方便,我们先对每个点建自环。
那么。对于每个点u,一定有一条或多条(u,v) 满足点v是建基站的。
我们可以从小到大枚举v是否有基站。
函数原型和之前一样。void dfs(int u,int now)
1. 如果v本来就有基站,那么 dfs(u+1,now) 搜索下一个点。
2. 如果v没有基站,我们设置基站,那么 dfs(u+1,now+1) 搜索一下个点。

剪枝

最优性剪枝:和之前一样。
排除等效冗余:我们枚举时,只需要枚举某个还没建基站的点,和任意一个建基站的点进行搜索。
这个思路可以保证调用dfs(u,now)时, 1..(u-1)全部都满足约束条件。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXV=100+1,MOD=1000003;
int n,minn=1000,ans;
bool e[MAXV][MAXV],pkd[MAXV],ht[MOD];
char buf[20000],tmp[2000];
bool try_insert(){
    unsigned int hv=0,fac=1;
    for(int i=1;i<=n;i++,fac*=2,fac%=MOD) if(pkd[i]) hv+=fac,hv%=MOD;
    if(ht[hv]) return false;
    else return ht[hv]=1;
}
void dfs(int u=1,int now=0){//考虑点u,目前已经建了now个基站
    if(now>minn) return;
    else if(u>n){
        if(now<minn) minn=now,ans=0,buf[0]=0;
        if(!try_insert()) return;
        tmp[0]=0;
        ans++;
        for(int i=1;i<=n;i++) if(pkd[i]) sprintf(tmp+strlen(tmp),"%d ",i);
        sprintf(tmp+strlen(tmp),"\n");
        strcat(buf,tmp);
    }
    else {
        bool flag=0;
        for(int v=1;v<=n;v++) if(e[u][v]){
            if(pkd[v]) {
                if(!flag) flag=1,dfs(u+1,now);
            } 
            else{
                pkd[v]=1;
                dfs(u+1,now+1);
                pkd[v]=0;
            }
        }
    } 
}
int main(){
    freopen("zpj.in","r",stdin);
    freopen("zpj.out","w",stdout);
    cin>>n;
    for(int i=1;i<=n;e[i][i]=1,i++) for(int j=1;j<=n;j++) cin>>e[i][j];
    dfs();
    cout<<minn<<endl<<ans<<endl<<buf;
}

为了方便,我们先对每个点建自环。
那么。对于每个点 u,一定有一条或多条 (u,v) 满足点 v 是建基站的。
我们可以从小到大枚举 v 是否有基站。
函数原型和之前一样。void dfs(int u,int now)
1. 如果 v 本来就有基站,那么 dfs(u+1,now) 搜索下一个点。
2. 如果 v 没有基站,我们设置基站,那么 dfs(u+1,now+1) 搜索一下个点。

剪枝

最优性剪枝:和之前一样。
排除等效冗余:我们枚举时,只需要枚举某个还没建基站的点,和任意一个建基站的点进行搜索。

其他

思路1是盲目的枚举,直到最后一步才判断是否可行。
而这个思路可以保证调用dfs(u,now)时,1…(u-1) 全部都满足约束条件。

ps:

//此题为图论中的最小支配集
#include <cstdio>
#include <bitset>
#include <vector>
#include <algorithm>
using namespace std;

struct stVs
{
  vector<int> Vs;
  bool operator<(const stVs &x) const
  {
    for (int i=0;i<(int)Vs.size();++i)
    {
      if(Vs[i]!=x.Vs[i])
        return (Vs[i]<x.Vs[i]);
    }
    return false;
  }
};

int nN;
int a[100][100];
vector<bitset<100> > vVs[100];
vector<stVs> uVs;

int main(void)
{
  freopen("zpj.in","r",stdin);
  freopen("zpj.out","w",stdout);
  scanf("%d",&nN);
  for (int i=0;i<nN;++i)
  {
    for (int j=0;j<nN;++j)
    {
      int nTemp;
      scanf("%d",&nTemp);
      a[i][j]=nTemp;
      if(nTemp==1)
      {
        bitset<100> bsTemp;
        bsTemp.set(j);
        vVs[i].push_back(bsTemp);
      }
    }
    bitset<100> bsTemp;
    bsTemp.set(i);
    vVs[i].push_back(bsTemp);
  }
  {
    vector<int> V;
    for (int i=0;i<nN;++i)
    {
      if((int)vVs[i].size()==nN)
      {
        V.push_back(i+1);
      }
    }
    if(V.size()>0)
    {
      printf("1\n");
      printf("%d\n",(int)V.size());
      sort(V.begin(),V.end());
      for (int i=0;i<(int)V.size();++i)
      {
        printf("%d\n",V[i]);
      }
      fclose(stdin);
      fclose(stdout);
      return 0;
    }
  }
  vector<bitset<100> > bsTemp,bsTemp1;
  bsTemp=vVs[0];
  for (int k=1;k<nN;++k)
  {
    for (int i=0;i<(int)bsTemp.size();++i)
    {
      bitset<100> bs1=bsTemp[i];
      for (int j=0;j<(int)vVs[k].size();++j)
      {
        bitset<100> bs2=vVs[k][j];
        bs2 |= bs1;
        bsTemp1.push_back(bs2);
      }
    }
    bool bFind=true;
    while(bFind)
    {
      bFind=false;
      for (int i=0;i<(int)bsTemp1.size();++i)
      {
        bitset<100> bs=bsTemp1[i];
        vector<bitset<100> >::iterator itr;
        int j=0;
        for (itr=bsTemp1.begin();itr!=bsTemp1.end();++itr,++j)
        {
          if(i==j)
            continue;
          bitset<100> bs1=*itr;
          bs1 &= bs;
          if(bs == bs1)
          {
            bsTemp1.erase(itr);
            bFind=true;
            break;
          }
        }
        if(bFind)
          break;
      }
    }
    bsTemp=bsTemp1;
    bsTemp1.clear();
  }
  int nCount=nN;
  for (int i=0;i<(int)bsTemp.size();++i)
    nCount=min(nCount,(int)bsTemp[i].count());
  printf("%d\n",nCount);
  vector<int> vResults;
  for (int i=0;i<(int)bsTemp.size();++i)
  {
    if(bsTemp[i].count()==nCount)
      vResults.push_back(i);
  }
  printf("%d\n",(int)vResults.size());
  for (int i=0;i<(int)vResults.size();++i)
  {
    vector<int> vTemp;
    for (int j=0;j<nN;++j)
    {
      if(bsTemp[vResults[i]].test(j))
        vTemp.push_back(j+1);
    }
    sort(vTemp.begin(),vTemp.end());
    stVs stTemp;
    stTemp.Vs=vTemp;
    uVs.push_back(stTemp);
  }
  sort(uVs.begin(),uVs.end());
  for (int i=0;i<(int)uVs.size();++i)
  {
    for (int j=0;j<(int)uVs[i].Vs.size();++j)
      printf("%d ",uVs[i].Vs[j]);
    printf("\n");
  }
  fclose(stdin);
  fclose(stdout);
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值