头像

小张同学

DB大学




离线:5天前


新鲜事 原文

新的一年,小张同学加油!


新鲜事 原文

小张同学
7个月前
心态爆炸


活动打卡代码 AcWing 1475. 紧急情况

小张同学
10个月前

仔细审题,刚开始把输出的最短路有多少种理解错了

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 510, M = 610;

int g[N][N];  // 边权
int n, m, c1, c2;
int num[N];  // 存储救援队
int cnt[N];  // 最短路的数量,有几条最短路
int sum[N];  // 最短路的情况中,救援队最大数量
int dist[N];
bool st[N];


void dijkstra()
{
    memset(dist, 0x3f, sizeof dist);

    dist[c1] = 0;
    cnt[c1] = 1;
    sum[c1] = num[c1];

    for (int i = 0; i < n ; i ++ )
    {
        int t = -1;
        for (int j = 0; j < n; j ++ )
        {
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;
        }
        st[t] = true;

        for (int j = 0; j < n; j ++ )
        {
            if (dist[j] > dist[t] + g[t][j])
            {
                dist[j] = dist[t] + g[t][j];
                cnt[j] = cnt[t];
                sum[j] = sum[t] + num[j];
            }
            else if (dist[j] == dist[t] + g[t][j])  // 如果两条路一样进,就合并
            {
                cnt[j] += cnt[t];
                sum[j] = max(sum[j], sum[t] + num[j]);
            }
        }
    }
}

int main()
{
    cin >> n >> m >> c1 >> c2;

    for (int i = 0; i < n ; i ++ ) cin >> num[i];


    memset(g, 0x3f, sizeof g);
    for (int i = 0; i < m ; i ++ )
    {
        int a, b, c;
        cin >> a >> b >> c;

        g[a][b] = g[b][a] = min(g[a][b], c);
    }

    dijkstra();

    cout << cnt[c2] << ' ' << sum[c2] << endl; 

    return 0;
}



小张同学
10个月前

思路:

拓扑排序流程(bfs):
1. 遍历所有点找到入度为0的点,把它们入队
2. 开始从入度为0的点找:

1. 队头出队
2. 遍历队头的临边
3. 临边的度 - 1 
4. 判断此时度是否为0:为0说明它可以做新的起点,入队

return tt == n - 1 (队尾下标是n-1,说明所有点都已经入队)

Note

模拟队列的好处就是,只是移动队头队尾指针,实际上拓扑序还在队列中,所以输出时直接遍历就好。

模拟队列

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 100010;

int n, m;
int h[N], e[N], ne[N], idx;
int d[N];
int q[N];

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

bool topsort()
{
    int hh = 0, tt = -1;

    for (int i = 1; i <= n; i ++ )
        if (d[i] == 0) //  等同于 if (!d[i])
            q[ ++ tt ] = i;

    while(hh <= tt)
    {
        int t = q[hh ++ ];

        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];  // 找到出边!
            d[j] -- ;
            if (d[j] == 0)
            {
                q[ ++ tt] = j;
            }

        }
    }


    return tt == n - 1;
}

int main()
{
    cin >> n >> m;

    memset(h, -1, sizeof h);

    while(m -- )
    {
        int a, b;
        cin >> a >> b;

        add(a, b);
        d[b] ++ ;
    }

    if (!topsort()) puts("-1");
    else
    {
        for (int i = 0; i < n; i ++ )
            cout << q[i] << ' ';
    }

    return 0;
}

stl queue

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>

using namespace std;

const int N = 100010;

int n, m;
int h[N], e[N], ne[N], idx;
int d[N];

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

int tmp[N];
queue<int> q;

int pos = 0;
bool topsort()
{
    //int hh = 0, tt = -1;

    for (int i = 1; i <= n; i ++ )
        if (d[i] == 0)  //  等同于 if (!d[i])
            q.push(i);  // q[ ++ tt ] = i;


    while(q.size())  // while(hh <= tt)
    {
        int t = q.front();  //int t = q[hh ++ ];
        tmp[pos ++ ] = t;  
        q.pop();

        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];  // 找到出边!
            d[j] -- ;
            if (d[j] == 0)
            {
                q.push(j);// q[ ++ tt] = j;
            }

        }
    }

    if (pos == n) return true;
    else return false;
}

int main()
{
    cin >> n >> m;

    memset(h, -1, sizeof h);

    while(m -- )
    {
        int a, b;
        cin >> a >> b;

        add(a, b);
        d[b] ++ ;
    }

    if (!topsort()) puts("-1");
    else
    {
        for (int i = 0; i < pos; i ++ )
        {
            cout << tmp[i] << ' ';
        }

    }

    return 0;
}

写queue时老是逻辑有点问题,最后用了y总的printf方法debug,真好用啊!printf大法好!!笔芯yxc




小张同学
10个月前

思路:

拓扑排序流程(bfs):
1. 遍历所有点找到入度为0的点,把它们入队
2. 开始从入度为0的点找:

1. 队头出队
2. 遍历队头的临边
3. 临边的度 - 1 
4. 判断此时度是否为0:为0说明它可以做新的起点,入队

return tt == n - 1 (队尾下标是n-1,说明所有点都已经入队)

Note

模拟队列的好处就是,只是移动队头队尾指针,实际上拓扑序还在队列中,所以输出时直接遍历就好。

模拟队列

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 100010;

int n, m;
int h[N], e[N], ne[N], idx;
int d[N];
int q[N];

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

bool topsort()
{
    int hh = 0, tt = -1;

    for (int i = 1; i <= n; i ++ )
        if (d[i] == 0) //  等同于 if (!d[i])
            q[ ++ tt ] = i;

    while(hh <= tt)
    {
        int t = q[hh ++ ];

        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];  // 找到出边!
            d[j] -- ;
            if (d[j] == 0)
            {
                q[ ++ tt] = j;
            }

        }
    }


    return tt == n - 1;
}

int main()
{
    cin >> n >> m;

    memset(h, -1, sizeof h);

    while(m -- )
    {
        int a, b;
        cin >> a >> b;

        add(a, b);
        d[b] ++ ;
    }

    if (!topsort()) puts("-1");
    else
    {
        for (int i = 0; i < n; i ++ )
            cout << q[i] << ' ';
    }

    return 0;
}

stl queue

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>

using namespace std;

const int N = 100010;

int n, m;
int h[N], e[N], ne[N], idx;
int d[N];

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

int tmp[N];
queue<int> q;

int pos = 0;
bool topsort()
{
    //int hh = 0, tt = -1;

    for (int i = 1; i <= n; i ++ )
        if (d[i] == 0)  //  等同于 if (!d[i])
            q.push(i);  // q[ ++ tt ] = i;


    while(q.size())  // while(hh <= tt)
    {
        int t = q.front();  //int t = q[hh ++ ];
        tmp[pos ++ ] = t;  
        q.pop();

        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];  // 找到出边!
            d[j] -- ;
            if (d[j] == 0)
            {
                q.push(j);// q[ ++ tt] = j;
            }

        }
    }

    if (pos == n) return true;
    else return false;
}

int main()
{
    cin >> n >> m;

    memset(h, -1, sizeof h);

    while(m -- )
    {
        int a, b;
        cin >> a >> b;

        add(a, b);
        d[b] ++ ;
    }

    if (!topsort()) puts("-1");
    else
    {
        for (int i = 0; i < pos; i ++ )
        {
            cout << tmp[i] << ' ';
        }

    }

    return 0;
}

写queue时老是逻辑有点问题,最后用了y总的printf方法debug,真好用啊!printf大法好!!笔芯yxc



活动打卡代码 AcWing 847. 图中点的层次

小张同学
10个月前

模拟队列

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 100010;

int n, m;
int h[N], ne[N], e[N], idx;
int d[N];
int q[N];  // queue<int> q;

void add (int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

int bfs()
{
    memset(d, -1, sizeof d);
    d[1] = 0;

    q[0] = 1;  // q.push(1);

    int hh = 0, tt = 0;
    while(hh <= tt)
    {
        int t = q[hh ++ ];  // auto t = q.front();  q.pop();

        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (d[j] == -1)  // j点还没被扩展
            {
                d[j] = d[t] + 1;
                q[ ++ tt] = j;  // q,push(j);
            }
        }

    }

    return d[n];
}

int main()
{
    cin >> n >> m;
    memset(h, -1, sizeof h);

    while(m -- )
    {
        int a, b;
        cin >> a >> b;

        add(a, b);
    }

    cout << bfs() << endl;

    return 0;
}


stl队列

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>

using namespace std;

const int N = 100010;

int n, m;
int h[N], ne[N], e[N], idx;
int d[N];

void add (int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

int bfs()
{
    memset(d, -1, sizeof d);

    queue<int> q;
    d[1] = 0;
    q.push(1);

    while(q.size())
    {
        auto t = q.front();
        q.pop();


        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (d[j] == -1)  // j点还没被扩展
            {
                d[j] = d[t] + 1;
                q.push(j);
            }
        }

    }

    return d[n];
}

int main()
{
    cin >> n >> m;
    memset(h, -1, sizeof h);

    while(m -- )
    {
        int a, b;
        cin >> a >> b;

        add(a, b);
    }

    cout << bfs() << endl;

    return 0;
}


我的笨蛋思路,堆优化版dijkstra(),边权都是1~

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>

using namespace std;

const int N = 1e6  + 10;

int n, m;
int h[N], ne[N], e[N], idx, w[N];
int dist[N];
bool st[N];

typedef pair<int, int> PII;

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);

    priority_queue<PII, vector<PII>, greater<PII>> heap;

    dist[1] = 0;
    heap.push({0, 1});

    while(heap.size())
    {
        auto t = heap.top();
        heap.pop();

        int ver = t.second;
        st[ver] = true;

        for (int i = h[ver]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[ver] + 1)  // !st[ver]
            {
                dist[j] = dist[ver] + 1;
                heap.push({dist[j], j});
            }
        }

    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    else return dist[n];
}

int main()
{
    scanf("%d%d", &n, &m);

    memset(h, -1, sizeof h);
    memset(w, 1, sizeof w);

    while(m -- )
    {
        int a, b;
        scanf("%d%d", &a, &b);

        add(a, b);
    }

    cout << dijkstra() << endl;

    return 0;
}



小张同学
10个月前

思路:(这个题一定要捋清思路!)

树的重心是:将这个点删除后,剩余各个连通块中点数的最大值最小

所以:

1. 先求每个点的 所有连通块的点数的最大值 (cur_ans)
2. 再求 到底是哪个点的(最大)值最小 (ans)

步骤:

1. 先求每个点的 所有连通块的点数的最大值 (cur_ans)

问题1. 如何求一个点的各个连通块的点数

dfs(int u) 求的是:以u为根的各个子树(删掉了根就是连通块)的点的数量
每求一棵子树就:

int s = dfs(u);
cur_ans = max(cur_ans, s);
sum += s;  // (可以看完问题2再看)

问题2. 除了这棵树对应的连通块,剩下的点怎么求

剩下的点自动成为一个连通块,所以用 n - 树的节点个数 即为剩下的节点个数
那么树的结点个数怎么求呢?

答: 可以在dfs里定义一个 int sum = 1,表示以u为根的树的节点个数,树的初始状态是有一个根节点,所以初始为1。
每求出一个连通块的点数就sum += s(这回在看一下问题1)

然后cur_ans: 得出对于u点的所有连通块中(树+除了树) 点数的最大值。

cur_ans = max(cur_ans, n - sum);

2. 求 到底是哪个点的(最大)值最小 (ans)

ans = min(ans, cur_ans);

Note:

  1. 这个题相当于无向图
    对于无向图,应该是有向图的边数*2,所以这里M = 2 * N
  2. int ans = N 来更新答案
  3. 用过的点标记为st[u] = true;
  4. dfs返回的是 sum

树的重心.png

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 100010, M = N * 2;  // 无向图,存双边

int n;
int h[N], ne[M], idx, e[M];  // 这个也是因为 a->b, b->a 都得存
int ans = N;  // ans就是各个点对应的cur_ans的最小值
bool st[N];


void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

int dfs(int u)
{
    st[u] = true;

    int sum = 1;  // u的这棵子树里的点的个数。记录这个是为了算除了以u为树的剩余节点组成的连通块的点的数量
    int cur_ans = 0;  // cur_ans 记录的是以u为根的子树的连通块中点数的最大值
    for (int i = h[u]; i != -1 ; i = ne[i] )
    {
        int j = e[i];
        if(!st[j])
        {
            int s = dfs(j);
            cur_ans = max(cur_ans, s);  // 每走一次算一次最大值

            sum += s;  
        }        
    }

    cur_ans = max(cur_ans, n - sum);

    ans = min(ans, cur_ans);

    return sum;
}

int main()
{
    cin >> n;

    memset(h, -1, sizeof h);

    for (int i = 0; i < n ; i ++ )
    {
        int a, b;
        cin >> a >> b;

        add(a, b), add(b, a);
    }

    dfs(1);

    cout << ans << endl;

    return 0;
}


活动打卡代码 AcWing 846. 树的重心

小张同学
10个月前

思路:(这个题一定要捋清思路!)

树的重心是:将这个点删除后,剩余各个连通块中点数的最大值最小

所以:

1. 先求每个点的 所有连通块的点数的最大值 (cur_ans)
2. 再求 到底是哪个点的(最大)值最小 (ans)

步骤:

1. 先求每个点的 所有连通块的点数的最大值 (cur_ans)

问题1. 如何求一个点的各个连通块的点数

dfs(int u) 求的是:以u为根的各个子树(删掉了根就是连通块)的点的数量
每求一棵子树就:

int s = dfs(u);
cur_ans = max(cur_ans, s);
sum += s;  // (可以看完问题2再看)

问题2. 除了这棵树对应的连通块,剩下的点怎么求

剩下的点自动成为一个连通块,所以用 n - 树的节点个数 即为剩下的节点个数
那么树的结点个数怎么求呢?

答: 可以在dfs里定义一个 int sum = 1,表示以u为根的树的节点个数,树的初始状态是有一个根节点,所以初始为1。
每求出一个连通块的点数就sum += s(这回在看一下问题1)

然后cur_ans: 得出对于u点的所有连通块中(树+除了树) 点数的最大值。

cur_ans = max(cur_ans, n - sum);

2. 求 到底是哪个点的(最大)值最小 (ans)

ans = min(ans, cur_ans);

Note:

  1. 这个题相当于无向图
    对于无向图,应该是有向图的边数*2,所以这里M = 2 * N
  2. int ans = N 来更新答案
  3. 用过的点标记为st[u] = true;
  4. dfs返回的是 sum

树的重心.png

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 100010, M = N * 2;  // 无向图,存双边

int n;
int h[N], ne[M], idx, e[M];  // 这个也是因为 a->b, b->a 都得存
int ans = N;  // ans就是各个点对应的cur_ans的最小值
bool st[N];


void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

int dfs(int u)
{
    st[u] = true;

    int sum = 1;  // u的这棵子树里的点的个数。记录这个是为了算除了以u为树的剩余节点组成的连通块的点的数量
    int cur_ans = 0;  // cur_ans 记录的是以u为根的子树的连通块中点数的最大值
    for (int i = h[u]; i != -1 ; i = ne[i] )
    {
        int j = e[i];
        if(!st[j])
        {
            int s = dfs(j);
            cur_ans = max(cur_ans, s);  // 每走一次算一次最大值

            sum += s;  
        }        
    }

    cur_ans = max(cur_ans, n - sum);

    ans = min(ans, cur_ans);

    return sum;
}

int main()
{
    cin >> n;

    memset(h, -1, sizeof h);

    for (int i = 0; i < n ; i ++ )
    {
        int a, b;
        cin >> a >> b;

        add(a, b), add(b, a);
    }

    dfs(1);

    cout << ans << endl;

    return 0;
}



小张同学
10个月前

floyd.PNG

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 210, INF = 0x3f3f3f3f;

int dist[N][N];
int n, m, q;  // q是询问次数:询问从i->j的最短路。(所以这是多元最短路问题!!!)

void floyd()
{
    for (int k = 1; k <= n; k ++ )
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= n; j ++ )
                dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
}

int main()
{
    cin >> n >> m >> q;

    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            if (i == j) dist[i][j] = 0;
            else dist[i][j] = INF;

    while(m -- )
    {
        int a, b, c;
        cin >> a >> b>> c;

        dist[a][b] = min(dist[a][b], c);
    }

    floyd();

    while(q -- )
    {
        int i, j;
        cin >> i >> j;

        int t = dist[i][j];

        // 不能走到终点,但由于负数边权的存在,终点的距离可能被其他长度是正无穷的距离更新。
        // 跟 INF / 2比较可以处理这种情况。
        if (t > INF / 2) puts("impossible");
        else cout << t << endl;
    }


    return 0;

}


活动打卡代码 AcWing 854. Floyd求最短路

小张同学
10个月前

floyd.PNG

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 210, INF = 0x3f3f3f3f;

int dist[N][N];
int n, m, q;  // q是询问次数:询问从i->j的最短路。(所以这是多元最短路问题!!!)

void floyd()
{
    for (int k = 1; k <= n; k ++ )
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= n; j ++ )
                dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
}

int main()
{
    cin >> n >> m >> q;

    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            if (i == j) dist[i][j] = 0;
            else dist[i][j] = INF;

    while(m -- )
    {
        int a, b, c;
        cin >> a >> b>> c;

        dist[a][b] = min(dist[a][b], c);
    }

    floyd();

    while(q -- )
    {
        int i, j;
        cin >> i >> j;

        int t = dist[i][j];

        // 不能走到终点,但由于负数边权的存在,终点的距离可能被其他长度是正无穷的距离更新。
        // 跟 INF / 2比较可以处理这种情况。
        if (t > INF / 2) puts("impossible");
        else cout << t << endl;
    }


    return 0;

}