ZOJ Monthly, June 2014——Grouping

题目连接

  • 题意:

    n个点,m条边

    每条边两个整数a、b,表示a到b的有向边

    求,至少需要几个集合,使得:每个集合中的元素互相不能到达

    N(1≤ N
    100000), M(1≤ M
    300000)

  • 分析:

    相连的两个点不能在同一个集合中,那么,对于一个长度为n的链,至少需要n个集合;如果链中有环,相当于把环展开,这个就需要缩点处理

    就是缩点之后求点权最长路

注意:模板中scc_cnt是从1开始的,如果使用缩点后的图,初始化时需要初始化总点数加一

因为总点数有限,用拓扑排序每次删除所有入度为零的点(缩后的点每次数量减一)

const int MAXN = 110000;

//使用时只更新G完成构图
//scc_cnt从1开始计数

//pre[]表示点在DFS树中的先序时间戳
//lowlink[]表示当前点和后代能追溯到的最早祖先的pre值
//sccno[]表示点所在的双连通分量编号
//vector<int> G保存每个点相邻的下一个点序号
//stack<Edge> S是算法用到的栈
const int MAXV = MAXN;

vector<int> G[MAXV];
int pre[MAXV], lowlink[MAXV], sccno[MAXV], dfs_clock, scc_cnt;
stack<int> S;

void init(int n)
{
    REP(i, n) G[i].clear();
}

void dfs(int u)
{
    pre[u] = lowlink[u] = ++dfs_clock;
    S.push(u);
    for(int i = 0; i < G[u].size(); i++)
    {
        int v = G[u][i];
        if(!pre[v])
        {
            dfs(v);
            lowlink[u] = min(lowlink[u], lowlink[v]);
        }
        else if(!sccno[v])
        {
            lowlink[u] = min(lowlink[u], pre[v]);
        }
    }
    if(lowlink[u] == pre[u])
    {
        scc_cnt++;
        for(;;)
        {
            int x = S.top();
            S.pop();
            sccno[x] = scc_cnt;
            if(x == u) break;
        }
    }
}

void find_scc(int n)
{
    dfs_clock = scc_cnt = 0;
    memset(sccno, 0, sizeof(sccno));
    memset(pre, 0, sizeof(pre));
    for(int i = 0; i < n; i++)
        if(!pre[i]) dfs(i);
};

int ideg[MAXN];
int now;
queue<int> q[2];
vector<int> g[MAXN];
int a[3 * MAXN], b[3 * MAXN], num[MAXN];

int main()
{
//    freopen("in.txt", "r", stdin);
    int n, m;
    while (~RII(n, m))
    {
        CLR(ideg, 0);
        CLR(num, 0);
        now = 0;
        init(n);
        REP(i, n + 1) g[i].clear();

        REP(i, m)
        {
            RII(a[i], b[i]);
            G[a[i] - 1].push_back(b[i] - 1);
        }
        find_scc(n);
        REP(i, m)
        {
            int u = sccno[a[i] - 1], v = sccno[b[i] - 1];
            if (u == v)
                continue;
            g[u].push_back(v);
            ideg[v]++;
        }
        REP(i, n)
            num[sccno[i]]++;
        FE(i, 1, scc_cnt)
        {
            if (ideg[i] == 0)
                q[now].push(i);
        }
        int ans = 0;
        while (!q[now].empty())
        {
            ans++;
            while (!q[now].empty())
            {
                int t = q[now].front();
                q[now].pop();
                if (--num[t] == 0)
                {
                    REP(i, g[t].size())
                    {
                        int v = g[t][i];
                        if (--ideg[v] == 0)
                            q[now ^ 1].push(v);
                    }
                }
                else
                    q[now ^ 1].push(t);
            }
            now ^= 1;
        }
        WI(ans);
    }
    return 0;
}

正常的思路,DAG上的DP(记忆化搜索):

const int MAXN = 110000;

//使用时只更新G完成构图
//scc_cnt从1开始计数

//pre[]表示点在DFS树中的先序时间戳
//lowlink[]表示当前点和后代能追溯到的最早祖先的pre值
//sccno[]表示点所在的双连通分量编号
//vector<int> G保存每个点相邻的下一个点序号
//stack<Edge> S是算法用到的栈
const int MAXV = MAXN;

vector<int> G[MAXV];
int pre[MAXV], lowlink[MAXV], sccno[MAXV], dfs_clock, scc_cnt;
stack<int> S;

void init(int n)
{
    REP(i, n) G[i].clear();
}

void dfs(int u)
{
    pre[u] = lowlink[u] = ++dfs_clock;
    S.push(u);
    for(int i = 0; i < G[u].size(); i++)
    {
        int v = G[u][i];
        if(!pre[v])
        {
            dfs(v);
            lowlink[u] = min(lowlink[u], lowlink[v]);
        }
        else if(!sccno[v])
        {
            lowlink[u] = min(lowlink[u], pre[v]);
        }
    }
    if(lowlink[u] == pre[u])
    {
        scc_cnt++;
        for(;;)
        {
            int x = S.top();
            S.pop();
            sccno[x] = scc_cnt;
            if(x == u) break;
        }
    }
}

void find_scc(int n)
{
    dfs_clock = scc_cnt = 0;
    memset(sccno, 0, sizeof(sccno));
    memset(pre, 0, sizeof(pre));
    for(int i = 0; i < n; i++)
        if(!pre[i]) dfs(i);
};

int ideg[MAXN];
int now;
queue<int> q[2];
vector<int> g[MAXN];
int a[3 * MAXN], b[3 * MAXN], num[MAXN];
int val[MAXN];

int d(int u)
{
    if (val[u])
        return val[u];
    int ret = 0;
    REP(i, g[u].size())
    {
        int v = g[u][i];
        ret = max(ret, d(v));
    }
    return val[u] = ret + num[u];
}

int main()
{
//    freopen("in.txt", "r", stdin);
    int n, m;
    while (~RII(n, m))
    {
        CLR(ideg, 0);
        CLR(val, 0);
        CLR(num, 0);
        now = 0;
        init(n);
        REP(i, n + 1) g[i].clear();

        REP(i, m)
        {
            RII(a[i], b[i]);
            G[a[i] - 1].push_back(b[i] - 1);
        }
        find_scc(n);
        REP(i, m)
        {
            int u = sccno[a[i] - 1], v = sccno[b[i] - 1];
            if (u == v)
                continue;
            g[u].push_back(v);
            ideg[v]++;
        }
        REP(i, n)
            num[sccno[i]]++;
        FE(i, 1, scc_cnt)
        {
            if (ideg[i] == 0)
                g[0].push_back(i);
        }
        WI(d(0));
    }
    return 0;
}

ZOJ Monthly, June 2014——Grouping

时间: 2024-10-20 20:16:28

ZOJ Monthly, June 2014——Grouping的相关文章

ZOJ Monthly, June 2014 解题报告

A.Another Recurrence Sequence B.Gears 题目大意:有n个齿轮,一开始各自为一组,之后进行m次操作,包括以下4种类型: 1.合并两组齿轮,合并的两个应该反向旋转 2.把某个齿轮从所在组删除,自为一组,但不影响同组其它齿轮的状态与关系 3.询问两个齿轮是同向.反向或无关系(即不在同一组) 4.询问某个齿轮所在组的齿轮总数 分析:典型的并查集操作,但是注意两点: 1.由于操作3要询问两个齿轮的相对状态,因此对并查集中每个元素应当保存它的状态信息.状态是相对的,只需要

记次浙大月赛 134 - ZOJ Monthly, June 2014

链接 虽做出的很少,也记录下来,留着以后来补..浙大题目质量还是很高的 B 并查集的一些操作,同类和不同类我是根据到根节点距离的奇偶判断的,删点是直接新加一个点,记得福大月赛也做过类似的,并差集的这类关系题目还是比较常见的,有空深究一下. 1 #include <iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<stdlib.h> 6

ZOJ Monthly, June 2014 月赛BCDEFGH题题解

比赛链接:点击打开链接 上来先搞了f.c,,然后发现状态不正确,一下午都是脑洞大开,, 无脑wa,无脑ce...一样的错犯2次.. 硬着头皮搞了几发,最后20分钟码了一下G,不知道为什么把1直接当成不能加油的站就会wa..太弱.. 唔···太懒第二天才发题解.. B:Gears 并查集 题解:点击打开链接 C:Consecutive Blocks 离散化一下然后模拟 题解:点击打开链接 D:An Easy Game 设dp[i][j]为前i个位置已经匹配了j个位置的方法数. #include <

135 - ZOJ Monthly, August 2014

135 - ZOJ Monthly, August 2014 A:构造问题,推断序列奇偶性.非常easy发现最小值不是1就是0.最大值不是n就是n - 1,注意细节去构造就可以 E:dp.dp[i][j]表示长度i,末尾状态为j的最大值,然后每一个位置数字取与不取,不断状态转移就可以 G:就一个模拟题没什么好说的 H:dfs,每次dfs下去,把子树宽度保存下来,然后找最大值,假设有多个.就是最大值+cnt宽度 I:构造,假设r * 2 > R,肯定无法构造.剩下的就二分底边.按等腰三角形去构造就

ZOJ Monthly, November 2014

做了一次月赛,没想到这么难,加上后来补上的题目也只有3个题.第一名也只有4个题啊啊啊啊~.其中两道还是水题.留坑慢慢补上来. 3832 Tilt Cylinder 给定如图所示有盖圆柱体,R,H,水面高度h,倾角a,求水得体积. 分析:明显的数值积分题,这样考虑.圆下底面即A点与地面高度lim1, 圆上底面一点B与地面高度lim2,h所处的范围进行讨论从而确定积分几何体的两边的高度.我们积分的几何体应该是一个圆柱体被削掉一部分了. h>lim1时,几何体左半部分可以减掉一个圆柱,对剩下部分积分,

ZOJ Monthly, August 2014

H Machine http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5337 树的深搜.邻接表快一些 1 #include<cstdio> 2 #include<cstring> 3 #include<vector> 4 #include<algorithm> 5 #define mt(a,b) memset(a,b,sizeof(a)) 6 using namespace std; 7

浙大月赛ZOJ Monthly, August 2014

Abs Problem Time Limit: 2 Seconds Memory Limit: 65536 KB Special Judge Alice and Bob is playing a game, and this time the game is all about the absolute value! Alice has N different positive integers, and each number is not greater than N. Bob has a

ZOJ Monthly, September 2003【部分题解】

今天比赛做了一下这套题目.出了四道.两道水题,两道DP 比赛链接:http://vjudge.net/contest/view.action?cid=51404#problem/B 上来搞了一道水题之后就搞B题 题意很好理解,上来看了一下就懂了.以为是规律有循环节,没看wa那么多毅然决然提交,wa了一发. A = "^__^" and B = "T.T",C = BA = "T.T^__^".然后A=B,B=C,一直重复这个操作,问最后第n位的字

ZOJ 4009 And Another Data Structure Problem(ZOJ Monthly, March 2018 Problem F,发现循环节 + 线段树)

题目链接  ZOJ Monthly, March 2018 Problem F 题意很明确 这个模数很奇妙,在$[0, mod)$的所有数满足任意一个数立方$48$次对$mod$取模之后会回到本身. 所以开$48$棵线段树,和一个永久标记.当对某个区间操作时对这个区间加一层永久标记. 即当前我要查找的第$x$层,实际找的是第$up[i] + x$层. 时间复杂度$O(48nlogn)$ #include <bits/stdc++.h> using namespace std; #define