刷题日记day4(搜索)

第一篇题解

蒟蒻的第四篇题解希望大家支持

题目描述

P3915 树的分解

题目描述

给出 N N N 个点的树和 K K K,问能否把树划分成 N K \frac{N}{K} KN 个连通块,且每个连通块的点数都是 K K K

输入格式

第一行,一个整数 T T T,表示数据组数。接下来 T T T 组数据,对于每组数据:

第一行,两个整数 N , K N, K N,K

接下来 N − 1 N - 1 N1 行,每行两个整数 A i , B i A_i, B_i Ai,Bi,表示边 ( A i , B i ) (A_i, B_i) (Ai,Bi)。点用 1 , 2 , … , N 1, 2, \ldots, N 1,2,,N 编号。

输出格式

对于每组数据,输出 YESNO

输入输出样例 #1

输入 #1

2
4 2
1 2
2 3
3 4
4 2
1 2
1 3
1 4

输出 #1

YES
NO

说明/提示

  • 对于 60 % 60 \% 60% 的数据, 1 ≤ N , K ≤ 1 0 3 1 \le N, K \le 10^3 1N,K103
  • 对于 100 % 100 \% 100% 的数据, 1 ≤ T ≤ 10 1 \le T \le 10 1T10 1 ≤ N , K ≤ 1 0 5 1 \le N ,K \le 10^5 1N,K105
思路分析

这是一道有关于树的遍历的题目,和树相关的题目我们一般采用递归实现。在树当中联通块的意思其实就是包括某一个根节点和其所有子节点的一棵子树,这题我们就是要将一整棵树一片片地分解开来,以某一个节点为根节点进行分析,会遇到一下这三种情况1.以该节点为根节点的子树所包括的总节点数小于k2.以该节点为根节点的子树所包括的节点总数等于k3.以该节点为根节点的总结点数大于k,接下来我们分别分析这三种情况该如何处理

  • 1.总节点数不足k个,那么就把这个部分子树并到该根节点的父节点上,返回总节点数
  • 2.总节点数刚好为k,那么这个部分作为一个连通块直接划掉即可,返回0
  • 3.总节点数大于k,说明这个一整棵子树无法分成N/K个联通块,返回 -1,即输出一个标记失败情况的数字
可能出现的疑惑

不知道有同学是否会这样想,以该节点为根节点的总节点个数大于k为什么就能判断整棵数不能分成N/K个联通块呢,为什么不能把部分节点分给其他子树呢
有这个疑惑的同学,认真思考了这个问题,现在做出解答。
因为我们看问题的方式太片面了,我们想一想为什么这个以这个节点为根节点的总节点数会大于k呢,那当然是因为该节点的叶子节点返回了过多的点,这看上去是一句废话,但要好好理解一个通俗的解释方式,该节点的叶子节点解决不了自己的问题,就去向该节点寻求帮助,但是寻求帮助以后,该节点就无法解决自身的问题(因为总节点数超过k),对于叶子节点来说他自己解决不了问题(数量不足k个),向上寻求帮助也解决不了问题,那么这个节点的问题无论如何也解决不了(即无论如何也不能凑出k个节点)
给出一个案例
在这里插入图片描述
假设我们的目标k是4,绿色框内的每一个叶子节点都不能满足4,只能加到父节点,但是这四个点都加入父节点以后,紫色框内节点总数就超过4,这说明整棵数是不能分成N/4个联通块的

代码展示
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>

using namespace std;

const int N=101000;
int T,n,k;
vector<int> branch[N];

int dfs(int u,int last){
    int ans=1;//本身就是一个节点
    for(int i=0;i<branch[u].size();i++){
        int next=branch[u][i];
        if(next==last) continue;
        
        int cnt=dfs(next,u);//看看叶子节点的总节点数
        if(cnt==-1) return -1;
        if(cnt==k) cnt=0;
        ans+=cnt;
        
        if(ans>k) return -1;
    }
    return ans;
}

int main(){
    cin>>T;
    while(T--){
        cin>>n>>k;
        for(int i=1;i<=n;i++)
            branch[i].clear();//多测,每组要清理数据
            
        int x,y;
        for(int i=1;i<n;i++){
            cin>>x>>y;
            branch[x].push_back(y);
            branch[y].push_back(x);
        }
        
        int cnt=dfs(1,1);
        if(cnt==k) cout<<"YES"<<endl;
        else cout<<"NO"<<endl;
    }
    return 0;
}

细节讲解

注意多测要清理之前的数据,dfs中for循环内的几个if语句的关系是环环相扣的,要仔细思考

AI详细注释代码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>

using namespace std;

const int N=101000;
int T,n,k;          // T:测试用例数 n:节点数 k:目标分组大小
vector<int> branch[N]; // 邻接表存储树的边(branch[u]存u的所有邻接节点)

// 递归DFS:计算以u为根的子树可分组的节点数(last是父节点,避免回走)
int dfs(int u,int last){
    int ans=1; // 初始:当前节点自身算1个
    // 遍历u的所有邻接节点
    for(int i=0;i<branch[u].size();i++){
        int next=branch[u][i];
        if(next==last) continue; // 跳过父节点,避免重复遍历
        
        int cnt=dfs(next,u); // 递归计算子节点next的子树节点数
        if(cnt==-1) return -1; // 子树不满足条件,直接返回-1
        if(cnt==k) cnt=0;     // 子树节点数刚好凑够k,分组后清零
        ans+=cnt;             // 累加当前子树的有效节点数
        
        if(ans>k) return -1;  // 节点数超过k,无法分组,返回-1
    }
    return ans; // 返回当前子树累计的节点数
}

int main(){
    cin>>T;
    while(T--){
        cin>>n>>k;
        // 多组测试用例,清空邻接表
        for(int i=1;i<=n;i++)
            branch[i].clear();
            
        // 输入n-1条树的边,构建邻接表
        int x,y;
        for(int i=1;i<n;i++){
            cin>>x>>y;
            branch[x].push_back(y);
            branch[y].push_back(x);
        }
        
        // 从根节点1开始DFS(父节点设为1,避免回走)
        int cnt=dfs(1,1);
        // 最终累计节点数等于k则符合条件,否则不符合
        if(cnt==k) cout<<"YES"<<endl;
        else cout<<"NO"<<endl;
    }
    return 0;
}

第二篇题解

题目描述

P1144 最短路计数

题目描述

给出一个 N N N 个顶点 M M M 条边的无向无权图,顶点编号为 1 ∼ N 1\sim N 1N。问从顶点 1 1 1 开始,到其他每个点的最短路有几条。

输入格式

第一行包含 2 2 2 个正整数 N , M N,M N,M,为图的顶点数与边数。

接下来 M M M 行,每行 2 2 2 个正整数 x , y x,y x,y,表示有一条连接顶点 x x x 和顶点 y y y 的边,请注意可能有自环与重边。

输出格式

N N N 行,每行一个非负整数,第 i i i 行输出从顶点 1 1 1 到顶点 i i i 有多少条不同的最短路,由于答案有可能会很大,你只需要输出 $ ans \bmod 100003$ 后的结果即可。如果无法到达顶点 i i i 则输出 0 0 0

输入输出样例 #1

输入 #1

5 7
1 2
1 3
2 4
3 4
2 3
4 5
4 5

输出 #1

1
1
1
2
4

说明/提示

1 1 1 5 5 5 的最短路有 4 4 4 条,分别为 2 2 2 1 → 2 → 4 → 5 1\to 2\to 4\to 5 1245 2 2 2 1 → 3 → 4 → 5 1\to 3\to 4\to 5 1345(由于 4 → 5 4\to 5 45 的边有 2 2 2 条)。

对于 20 % 20\% 20% 的数据, 1 ≤ N ≤ 100 1\le N \le 100 1N100
对于 60 % 60\% 60% 的数据, 1 ≤ N ≤ 1 0 3 1\le N \le 10^3 1N103
对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 1 0 6 1\le N\le10^6 1N106 1 ≤ M ≤ 2 × 1 0 6 1\le M\le 2\times 10^6 1M2×106

思路分析

最短路问题采用bfs,注意到题目中有重边和自环,自环不用读入,因为你如果已经走到某个点了,再通过自环走一次,只会增加距离肯定不是最短路,由于统计的是不同的路径,所以重边是需要读入的。通过bfs扩展到的点,此时到达该点的路径一定是最短的,又因为我们初始化的距离为正无穷,所以第一次达到以后就会重新更新距离,这里是继承上一个路径数量,跟dijkstra类似,标记为true以后表明到这个点的最短距离已经找到,(虽然这里是为了不走回头路),而之后再次到达该点且距离跟之前相同的话,需要加上上一个路径的数量

代码展示
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>

using namespace std;

const int N=1e6+10,mod=100003;
int n,m;
bool st[N];//标记已经走过的点
int dist[N],path[N];
vector<int> branch[N];

void bfs(){
    queue<int> q;
    q.push(1);
    st[1]=true;
    dist[1]=0;
    path[1]=1;
    
    while(!q.empty()){
        int t=q.front();
        q.pop();
        for(int i=0;i<branch[t].size();i++){
            int v=branch[t][i];
            if(dist[t]+1<dist[v]){
                dist[v]=dist[t]+1;
                path[v]=path[t];
                if(!st[v]){
                    st[v]=true;
                    q.push(v);
                }
            }
            else if(dist[t]+1==dist[v]){
                path[v]=(path[v]+path[t])%mod;
            }
            else continue;
        }
    }
}

int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    
    cin>>n>>m;
    while(m--){
        int x,y;
        cin>>x>>y;
        if(x==y) continue;
        branch[x].push_back(y);
        branch[y].push_back(x);
    }
    
    memset(dist,0x3f,sizeof dist);
    bfs();
    
    for(int i=1;i<=n;i++)
        cout<<path[i]<<endl;
    return 0;
} 
细节讲解

距离刚开始要初始化为正无穷

AI详细注释代码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>

using namespace std;

const int N=1e6+10,mod=100003; // N:节点最大数量 mod:路径数取模值
int n,m;                       // n:节点数 m:边数
bool st[N];                    // 标记节点是否入过队列
int dist[N];                   // 存储节点到起点1的最短距离
int path[N];                   // 存储节点到起点1的最短路径数量
vector<int> branch[N];         // 邻接表存储图的边

// BFS求解最短距离和最短路径数
void bfs(){
    queue<int> q;
    q.push(1);                 // 起点1入队
    st[1]=true;                // 标记起点已入队
    dist[1]=0;                 // 起点到自身距离为0
    path[1]=1;                 // 起点到自身路径数为1
    
    while(!q.empty()){
        int t=q.front();       // 取出队头节点
        q.pop();
        // 遍历当前节点的所有邻接节点
        for(int i=0;i<branch[t].size();i++){
            int v=branch[t][i];// 邻接节点v
            // 情况1:找到更短的路径
            if(dist[t]+1<dist[v]){
                dist[v]=dist[t]+1; // 更新最短距离
                path[v]=path[t];   // 更新路径数(继承父节点)
                if(!st[v]){        // 未入队则标记并入队
                    st[v]=true;
                    q.push(v);
                }
            }
            // 情况2:找到等长的最短路径
            else if(dist[t]+1==dist[v]){
                path[v]=(path[v]+path[t])%mod; // 累加路径数并取模
            }
            // 情况3:路径更长,直接跳过
            else continue;
        }
    }
}

int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); // 加速输入输出
    
    cin>>n>>m;
    // 输入m条边,构建无向图邻接表
    while(m--){
        int x,y;
        cin>>x>>y;
        if(x==y) continue;     // 跳过自环边
        branch[x].push_back(y);
        branch[y].push_back(x);
    }
    
    memset(dist,0x3f,sizeof dist); // 初始化最短距离为无穷大
    bfs();
    
    // 输出每个节点到起点1的最短路径数
    for(int i=1;i<=n;i++)
        cout<<path[i]<<endl;
    return 0;
} 
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值