分而治之

**

分而治之

**
分而治之,各个击破是兵家常用的策略之一。在战争中,我们希望首先攻下敌方的部分城市,使其剩余的城市变成孤立无援,然后再分头各个击破。为此参谋部提供了若干打击方案。本题就请你编写程序,判断每个方案的可行性。

输入格式:
输入在第一行给出两个正整数 N 和 M(均不超过10 000),分别为敌方城市个数(于是默认城市从 1 到 N 编号)和连接两城市的通路条数。随后 M 行,每行给出一条通路所连接的两个城市的编号,其间以一个空格分隔。在城市信息之后给出参谋部的系列方案,即一个正整数 K (≤ 100)和随后的 K 行方案,每行按以下格式给出:
Np v[1] v[2] … v[Np]
其中 Np 是该方案中计划攻下的城市数量,后面的系列 v[i] 是计划攻下的城市编号。
输出格式:

对每一套方案,如果可行就输出YES,否则输出NO。
输入样例:

10 11
8 7
6 8
4 5
8 4
8 1
1 2
1 4
9 8
9 1
1 10
2 4
5
4 10 3 8 4
6 6 1 7 5 4 9
3 1 8 4
2 2 8
7 9 8 7 6 5 4 2

输出样例:

NO
YES
YES
NO
NO

并查集做法:将信息存储到邻接表中,然后一个标志数组标志该结点是否可以进行并操作。每次测试都要将标志数组初始化为false表示每个结点都可以进行并操作。在输入方案时被攻占的每个结点都不能被执行并操作将该结点对应的标志数组设置为true。
然后通过check函数对每个城市结点进行并操作(每次执行check时,父结点数组都要进行初始化)。
当发现有结点之间可以执行并操作则返回false(这个过程说明有城市亦然出现联通,就可以判断测试结果了)。
如果每个结点都遍历一遍结束后,仍然没有发现并操作的执行则说明该方案成功(即每个结点都是孤立的)

#include
#include
#include

using namespace std;

int N,M,K,Np;
vector<bool> flag;
vector< vector<int> > g;

vector<int> pre;

int find(int x)
{
    return x == pre[x] ? x : find(pre[x]);
}
bool unite(int nx ,int ny)
{
    int ax = find(nx);
    int ay = find(ny);
    if(ax != ay)//能够合并说明方案不可行
       return false;
    return true;
}//初始化父结点
void init()
{
    for(int i = 0;i <= N;++i)
        pre[i] = i;
}
bool check()
{
    init();
   for(int i = 1;i <= N;++i)
   {   //是否能够执行并查集操作
       if(!flag[i])
       {      //遍历每个邻接点
             for(auto w : g[i])
             {  //如果出现并集操作
                if(!flag[w]){
                     if(!unite(w,i))//出现并集操作(返回false)
                        return false;//该方案出现并集操作,不可行
                }     
            }
       }       
   } //遍历结束后依然没有发现并集操作说明方案可行
    return true;
}
int main()
{
    cin>>N>>M; 
    g.resize(N+1);
    flag.resize(N+1);
    pre.resize(N+1);
    for(int i = 0;i < M;++i)
    {
        int x,y;
        cin>>x>>y;
        g[x].push_back(y);
        g[y].push_back(x);
    }
    cin>>K;
    while (K--)
    {//初始化每个结点都可以进行并集操作
        fill(flag.begin(),flag.end(),false);
        cin>>Np;
        while (Np--)
        {
            int num;
            cin>>num;//被攻占的城市不可以执行并集操作
            flag[num] = true;
        }   
        cout<<(check() ? "YES\n":"NO\n");
    }
    return 0;
}

并查集做法:将信息存储到邻接表中,然后一个标志数组标志该结点是否可以进行并操作。每次测试都要将标志数组初始化为false表示每个结点都可以进行并操作。在输入方案时被攻占的每个结点都不能被执行并操作将该结点对应的标志数组设置为true。
然后通过check函数对每个城市结点进行并操作(每次执行check时,父结点数组都要进行初始化)。
当发现有结点之间可以执行并操作则返回false(这个过程说明有城市亦然出现联通,就可以判断测试结果了)。
如果每个结点都遍历一遍结束后,仍然没有发现并操作的执行则说明该方案成功(即每个结点都是孤立的)



#include
#include
#include

using namespace std;

int N,M,K,Np;
vector<bool> flag;
vector< vector<int> > g;

vector<int> pre;

int find(int x)
{
    return x == pre[x] ? x : find(pre[x]);
}
bool unite(int nx ,int ny)
{
    int ax = find(nx);
    int ay = find(ny);
    if(ax != ay)//能够合并说明方案不可行
       return false;
    return true;
}//初始化父结点
void init()
{
    for(int i = 0;i <= N;++i)
        pre[i] = i;
}
bool check()
{
    init();
   for(int i = 1;i <= N;++i)
   {   //是否能够执行并查集操作
       if(!flag[i])
       {      //遍历每个邻接点
             for(auto w : g[i])
             {  //如果出现并集操作
                if(!flag[w]){
                     if(!unite(w,i))//出现并集操作(返回false)
                        return false;//该方案出现并集操作,不可行
                }     
            }
       }       
   } //遍历结束后依然没有发现并集操作说明方案可行
    return true;
}
int main()
{
    cin>>N>>M; 
    g.resize(N+1);
    flag.resize(N+1);
    pre.resize(N+1);
    for(int i = 0;i < M;++i)
    {
        int x,y;
        cin>>x>>y;
        g[x].push_back(y);
        g[y].push_back(x);
    }
    cin>>K;
    while (K--)
    {//初始化每个结点都可以进行并集操作
        fill(flag.begin(),flag.end(),false);
        cin>>Np;
        while (Np--)
        {
            int num;
            cin>>num;//被攻占的城市不可以执行并集操作
            flag[num] = true;
        }   
        cout<<(check() ? "YES\n":"NO\n");
    }
    return 0;
}

深度优先搜索:对每一个可以访问结点进行搜索,如果发现某一个可以访问的结点可以继续向下搜索说明有城市可以和该城市相连(该方案不可行)。对每个城市都访问结束后,没有发现有城市可以访问其他城市则(该方案可行)


#include
#include
#include
using namespace std;
vector<vector<int> > ves;
vector<bool> visited;
int N,M,K,Np;
//是否能访问
bool dfs(int x)
{
    visited[x] = true;
    for(auto w : ves[x])
    {
        if(!visited[w]){
           // dfs(w); 
   //如果该结点的邻接点可以被访问直接返回false,不用继续进行深度搜索操作了
           return false;
         }      
    }//邻接点没有可以访问的返回true
    return true;
}
bool judge()
{
    for(int i = 1 ;i <= N;++i)
    {   //如果有一个点(没有被攻占的城市)能访问(它的)邻接点(深度搜索)就返回false
       //如果没有被攻占的城市的邻接点可以被访问则返回false(该方案不可行)
        if(!visited[i] && !dfs(i))
            return false;
    }//每个结点不能访问其他邻接点
    return true;
}
int main()
{
    cin>>N>>M;
    visited.resize(N+1);
    ves.resize(N+1);
    for(int i = 0;i < M;++i)
    {
        int x,y;
        cin>>x>>y;
        ves[x].push_back(y);
        ves[y].push_back(x);
    }
    cin>>K;
    while (K--)
    {
        fill(visited.begin(),visited.end(),false);
        cin>>Np;
        while (Np--)
        {
            int num;
            cin>>num;
            visited[num] = true;
        }
        cout<<(judge() ? "YES\n" : "NO\n");    
    } 
    return 0;
}

广度优先搜索与深度优先类似,每个被攻占的城市都设置为不可访问。通过对每个结点进行广度遍历,查找是否有节点可以访问邻接点即可。


#include
#include
#include
using namespace std;
vector< vector<int> > g;
vector<bool> visited;
int N,M,K,Np;
bool bfs(int x)
{
    queue<int> pq;
    pq.push(x);
    visited[x] = true;
    while (!pq.empty())
    {
        int v  =  pq.front();
        pq.pop();
        for(auto w : g[v])
            if(!visited[w])
                return true;    
    }
    return false;
}
bool check()
{
    for(int i = 1;i <= N;++i)
        if(!visited[i] && bfs(i))
            return false;
    return true;
}
int main()
{
    cin>>N>>M;
    g.resize(N+1);
    visited.resize(N+1);
    for(int i = 0;i < M;++i)
    {
        int x,y;
        cin>>x>>y;
        g[x].push_back(y);
        g[y].push_back(x);
    }
    cin>>K;
    while (K--)
    {
        fill(visited.begin(),visited.end(),false);
        cin>>Np;
        while (Np--)
        {
            int num;
            cin>>num;
            visited[num] = true;
        }   
        cout<<(check() ? "YES\n":"NO\n");
    }
    return 0;
}

题目要求通过攻占指定城市使得其他未攻占的城市孤立无援,也就是每个结点的入度为零的时候。记录图的信息和每个结点的入度或者出度。
在输入方案的是否。将每个被攻占的邻接点的入度减一。(当入度为零是就不用进行减减操作了)。执行完毕后,判断是否有节点的入度或出度不为零即可(入度或者出度都为零则说明该方案合适,存在节点的入度或者出度不为零则该方案不合适)


利用assign()函数(清空并深复制)
vector<int> array{3, 5, 2, 6, 4};
vector<int> outArray{ 1,2,3 };
outArray.assign(array.begin(),array.end());//清空原数据,赋予新数据={3,5,2,6,4}
outArray.assign(5, 0);//5个0,清空原数据={0,0,0,0,0,}


#include
#include
#include
/*
利用assign()函数(清空并深复制)
vector array{3, 5, 2, 6, 4};
vector outArray{ 1,2,3 };
outArray.assign(array.begin(),array.end());//清空原数据,赋予新数据={3,5,2,6,4}
outArray.assign(5, 0);//5个0,清空原数据={0,0,0,0,0,}

*/
using namespace std;
//入度(无向图),测试数组(每次测试都要将测试数组初始化为Indgree数组的内容)
vector<int> Indgree,testV;
int N,M,K,Np;
vector<vector<int> > ves;
//测试每个节点的入度是否存在不为零的情况
bool Valid(vector<int> test){
    for(int i = 1;i <= N;++i)
        if(test[i])return false;
    return true;
}
int main()
{
    cin>>N>>M;
    ves.resize(N+1);
    Indgree.resize(N+1);
    testV.resize(N+1);
    for(int i = 0;i < M;++i)
    {
        int x,y;
        cin>>x>>y;
        ves[x].push_back(y);
        ves[y].push_back(x);

        Indgree[x]++;
        Indgree[y]++;
    }
    cin>>K;
    
    while (K--)
    {
      //  memcpy(testV[0],Indgree[0],sizeof(Indgree));错误
        testV.assign(Indgree.begin(),Indgree.end());
        cin>>Np;
        while(Np--)
        {
            int num;
            cin>>num;
            //将该城市的入度(无向图)设置为0
            testV[num] = 0;
            //该城市的邻接城市入度减1
            for(auto w : ves[num])
            {//如果邻接城市的入度已经为0.就不用执行减1操作
                if(testV[w] != 0)
                    testV[w]--;
            }
        }     
        cout<<(Valid(testV) ? "YES" : "NO")<<endl;
    }
    return 0;
}

你可能感兴趣的:(分而治之)