分而治之

**

分而治之

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

输入格式:
输入在第一行给出两个正整数 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<iostream>
#include<cstring>
#include<vector>

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<iostream>
#include<cstring>
#include<vector>

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<iostream>
#include<vector>
#include<algorithm>
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<iostream>
#include<queue>
#include<vector>
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<iostream>
#include<cstring>
#include<vector>
/*
利用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,}

*/
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;
}

### Python 中分而治之(Divide and Conquer)算法的实现与应用 #### 分而治之算法简介 分而治之是一种经典的算法设计策略,其核心思想是将复杂问题分解成若干个小规模子问题来解决。通过递归的方式逐步缩小问题规模,最终得到原问题的解决方案。 在 PTA 平台上的题目中,分而治之算法常用于优化时间复杂度较高的场景。例如,在最大子序和问题中,可以通过分而治之方法高效解决问题[^1]。 --- #### 最大子序和问题中的分而治之实现 以下是基于分而治之思想的最大子序和问题的实现: ```python def max_subarray_divide_and_conquer(arr, low, high): if low == high: # 基本情况:只有一个元素 return arr[low] mid = (low + high) // 2 # 将数组分为两部分 # 计算左侧子数组的最大子序和 left_max_sum = max_subarray_divide_and_conquer(arr, low, mid) # 计算右侧子数组的最大子序和 right_max_sum = max_subarray_divide_and_conquer(arr, mid + 1, high) # 跨越中间位置的最大子序和 cross_max_sum = find_crossing_max_sum(arr, low, mid, high) # 返回三个值中的最大者 return max(left_max_sum, right_max_sum, cross_max_sum) def find_crossing_max_sum(arr, low, mid, high): # 找到跨越中间位置的最大子序列和 left_sum = float('-inf') current_sum = 0 for i in range(mid, low - 1, -1): # 向左扩展 current_sum += arr[i] if current_sum > left_sum: left_sum = current_sum right_sum = float('-inf') current_sum = 0 for j in range(mid + 1, high + 1): # 向右扩展 current_sum += arr[j] if current_sum > right_sum: right_sum = current_sum return left_sum + right_sum if __name__ == "__main__": array = [-2, 1, -3, 4, -1, 2, 1, -5, 4] result = max_subarray_divide_and_conquer(array, 0, len(array) - 1) print(result) ``` 上述代码实现了分而治之的核心逻辑,即分别计算左右两侧以及跨越中间区域的最大子序和,并返回三者的最大值。 --- #### 折半查找中的分而治之思想 除了最大子序和问题外,折半查找也是分而治之的经典应用场景之一。它通过对有序列表不断二分操作快速定位目标值的位置[^2]。 以下是折半查找的一个简单实现: ```python def binary_search(arr, target): low, high = 0, len(arr) - 1 while low <= high: mid = (low + high) // 2 if arr[mid] == target: return mid # 找到目标值 elif arr[mid] < target: low = mid + 1 # 在右半边继续查找 else: high = mid - 1 # 在左半边继续查找 return -1 # 如果未找到则返回-1 if __name__ == "__main__": sorted_array = [1, 3, 5, 7, 9, 11, 13, 15] target_value = 7 index = binary_search(sorted_array, target_value) print(index) ``` 此代码展示了如何利用分而治之减少不必要的比较次数,从而提高搜索效率。 --- #### 循环日程安排问题中的分而治之 另一个典型的分而治之问题是循环日程安排问题。该问题的目标是在二维矩阵中合理分配比赛场次,使得每一场比赛都满足特定约束条件[^3]。 下面是具体实现: ```python import numpy as np def generate_schedule(k): n = 2 ** k a = np.zeros((n, n), dtype=int) # 初始化基础矩阵 a[0][0], a[1][1] = 1, 1 a[1][0], a[0][1] = 2, 2 # 使用分而治之填充整个矩阵 for step in range(1, k): half = 2 ** step for row in range(half): for col in range(half): a[row + half][col] = a[row][col] + half # 左下角 a[row][col + half] = a[row + half][col] # 右上角 a[row + half][col + half] = a[row][col] # 右下角 return a if __name__ == "__main__": schedule_matrix = generate_schedule(3) print(schedule_matrix) ``` 这段代码通过递归方式构建了一个完整的赛程表,体现了分而治之的强大能力。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值