2018 杭电多校 - 图论题目

2018 杭电多校 - 图论题目

这些题是真的硬核啊!

1. HDU - 6321 - Dynamic Graph Matching(状压 dp)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6321

题目来源:2018 HDU 多校第三场 - C 题

出题人:Claris(%%%)

1.1 题意

给你一个 n ( 1 ≤ n ≤ n , 2 ∣ n ) n(1 \le n \le n, 2|n) n(1nn,2n) 个点的无向图,最初无任何边。

一共有 m ( 1 ≤ m ≤ 3 ⋅ 1 0 4 ) m(1 \le m \le 3 \cdot 10^4) m(1m3104) 次查询,查询共有两种:

  1. +   u   v \texttt{+ u v} + u v,表示增加一条无向边 ( u , v ) (u,v) (u,v),需要注意的是,本题支持添加无向边;
  2. -   u   v \texttt{- u v} - u v,表示删除一条无向边 ( u , v ) (u,v) (u,v),题目保证删掉的是已经存在的边。

每次操作之后,输出 n 2 \frac{n}{2} 2n 个数 a n s k ( 1 ≤ k ≤ n 2 ) ans_k(1 \le k \le \frac{n}{2}) ansk(1k2n),表示有 k k k 条边的独立集有多少。

本题有多组输入,共有 T ( 1 ≤ T ≤ 10 ) T(1 \le T \le 10) T(1T10) 组输入。

1.2 解题过程

通过 1 ≤ n ≤ 10 1 \le n \le 10 1n10 这个范围,应该立刻想到状态压缩!

将这 n n n 个点用二进制数 S S S 表示, 1 1 1 表示这个点被匹配, 0 0 0 表示这个点未被匹配。

d p [ S ] dp[S] dp[S] 表示在当前的边状态下,点的匹配状态为 S S S 的方案数。

先考虑加边的情况:

加边 ( u , v ) (u,v) (u,v) 时,将 d p [ 2 u − 1 + 2 v − 1 ] dp[2 ^ {u-1} + 2 ^ {v-1}] dp[2u1+2v1] 1 1 1,之后从小到大跑一遍状压 dp 即可得到所有状态的 dp 值。

之后开始统计答案,设 c n t ( S ) cnt(S) cnt(S) S S S 1 1 1 的数量,则答案 a n s k = ∑ c n t ( S ) = 2 ⋅ k d p [ S ] ans_k=\sum_{cnt(S) = 2 \cdot k} dp[S] ansk=cnt(S)=2kdp[S]

删边时,考虑时光倒流即可。

时间复杂度: O ( T ⋅ 2 n ⋅ m ) O(T \cdot 2 ^ n \cdot m) O(T2nm)

1.3 代码

ll dp[1 << 11];
ll ans[10];
int cnt[1 << 11];
void init()
{
    memset(dp, 0, sizeof(dp));
}
int main()
{
    int t, n, m;
    int u, v;
    char op[2];

    for(int i = 0; i < (1 << 10); i++)
    {
        int S = i;
        while(S)
        {
            cnt[i] += (S % 2);
            S /= 2;
        }
    }
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d%d", &n, &m);
        init();
        while(m--)
        {
            scanf("%s", op);
            scanf("%d%d", &u, &v);
            if(op[0] == '+')
            {
                int S = 0;
                S |= (1 << (u - 1));
                S |= (1 << (v - 1));
                dp[S] = (dp[S] + 1) % Mod;
                for(int i = 0; i < (1 << n); i++)
                {
                    if((i >> (u - 1)) & 1)  continue;
                    if((i >> (v - 1)) & 1)  continue;
                    S = i;
                    S |= (1 << (u - 1));
                    S |= (1 << (v - 1));
                    dp[S] = (dp[S] + dp[i]) % Mod;
                }
            }
            else
            {
                int S;
                for(int i = 0; i < (1 << n); i++)
                {
                    if((i >> (u - 1)) & 1)  continue;
                    if((i >> (v - 1)) & 1)  continue;
                    S = i;
                    S |= (1 << (u - 1));
                    S |= (1 << (v - 1));
                    dp[S] = (dp[S] - dp[i] + Mod) % Mod;
                }
                S = 0;
                S |= (1 << (u - 1));
                S |= (1 << (v - 1));
                dp[S] = (dp[S] - 1 + Mod) % Mod;
            }
            memset(ans, 0, sizeof(ans));
            for(int i = 0; i < (1 << n); i++)
            {
                if(cnt[i] & 1) continue;
                ans[cnt[i] / 2] = (ans[cnt[i] / 2] + dp[i]) % Mod;
            }
            for(int k = 1; k <= n / 2; k++)
            {
                if(k > 1)   printf(" ");
                printf("%lld", ans[k]);
            }
            printf("\n");
        }
    }
    return 0;
}

2. HDU - 6331 - Walking Plan(最短路 + 分块)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6331

题目来源:2018 HDU 多校第三场 - M 题

出题人:Claris(%%%)

2.1 题意

给你一张 n ( 2 ≤ n ≤ 50 ) n(2 \le n \le 50) n(2n50) 个点和 m ( 1 ≤ m ≤ 10000 ) m(1 \le m \le 10000) m(1m10000) 条边的无向图,每条边都有权值 w i ( 1 ≤ w i ≤ 10000 ) w_i(1 \le w_i \le 10000) wi(1wi10000)

q ( 1 ≤ q ≤ 1 0 5 ) q(1 \le q \le 10^5) q(1q105) 次询问,每次询问给出 s i , t i , k i ( 1 ≤ s i , t i ≤ n , 1 ≤ k i ≤ 10000 ) s_i,t_i,k_i(1\leq s_i,t_i\leq n,1\leq k_i\leq 10000) si,ti,ki(1si,tin,1ki10000),表示询问从 s i s_i si t i t_i ti 至少经过 k i k_i ki 条边的最短路。

题目有 T ( 1 ≤ T ≤ 10 ) T(1 \le T \le 10) T(1T10) 组读入。

2.2 解题过程

首先回忆 Floyd 算法求恰好经过 m m m 条边的最短路的过程,

一般地,设矩阵 A m A^m Am 表示任意两点之间恰好经过 m m m 条边的最短路,则 A 1 A^1 A1 矩阵相当于初始的邻接矩阵,我们有转移
( A r + m ) [ i ] [ j ] = min ⁡ 1 ≤ k ≤ n { ( A r ) [ i ] [ k ] + ( A m ) [ k ] [ j ] } (A^{r+m})[i][j]=\min_{1 \le k \le n} \{ (A^r)[i][k] + (A^m)[k][j]\} (Ar+m)[i][j]=1knmin{(Ar)[i][k]+(Am)[k][j]}
我们注意到,这其实是对矩阵乘法的改造,因此求解恰好经过 m m m 条边的最短路时,如果采用矩阵快速幂的话,时间复杂度为 O ( n 3 log ⁡ m ) O(n^3\log m) O(n3logm)

那么最终的答案为
min ⁡ 1 ≤ k ≤ n { A m [ s i ] [ k ] + G [ k ] [ t i ] } \min_{1\le k \le n} \{ A^m[s_i][k] + G[k][t_i] \} 1knmin{Am[si][k]+G[k][ti]}
其中 G [ i ] [ j ] G[i][j] G[i][j] 表示传统 Floyd 算法计算出的从 i i i j j j 的最短路长度。

这样做,对于 1 0 5 10^5 105 组查询,显然会超时!

所以,我们必须对算法进行优化。

注意到, 10 0 2 = 10000 100^2=10000 1002=10000,所以我们可以对上述矩阵进行分块:

  1. d p 0 [ k ] [ i ] [ j ] dp_0[k][i][j] dp0[k][i][j] 表示 i i i j j j 之间恰好经过 k k k 条边的最短路;
  2. d p 1 [ k ] [ i ] [ j ] dp_1[k][i][j] dp1[k][i][j] 表示 i i i j j j 之间恰好经过 100 ⋅ k 100 \cdot k 100k 条边的最短路;
  3. d p 2 [ k ] [ i ] [ j ] dp_2[k][i][j] dp2[k][i][j] 表示 i i i j j j 之间至少经过 k k k 条边的最短路。
  4. G [ i ] [ j ] G[i][j] G[i][j] 表示传统 Floyd 算法计算出的从 i i i j j j 的最短路长度。

所以有转移
d p 0 [ k ] [ i ] [ j ] = min ⁡ 1 ≤ l ≤ 100 { d p 0 [ k − 1 ] [ i ] [ l ] + d p 0 [ 1 ] [ l ] [ j ] } d p 1 [ k ] [ i ] [ j ] = min ⁡ 1 ≤ l ≤ 100 { d p 1 [ k − 1 ] [ i ] [ l ] + d p 0 [ 100 ] [ l ] [ j ] } d p 2 [ k ] [ i ] [ j ] = min ⁡ 0 ≤ l ≤ 100 { d p 0 [ k ] [ i ] [ l ] + G [ l ] [ j ] } dp_0[k][i][j] = \min_{1\le l \le 100} \{dp_0[k - 1][i][l] + dp_0[1][l][j] \}\\ dp_1[k][i][j] = \min_{1\le l \le 100} \{dp_1[k - 1][i][l] + dp_0[100][l][j] \}\\ dp_2[k][i][j] = \min_{0\le l \le 100} \{dp_0[k][i][l] + G[l][j] \}\\ dp0[k][i][j]=1l100min{dp0[k1][i][l]+dp0[1][l][j]}dp1[k][i][j]=1l100min{dp1[k1][i][l]+dp0[100][l][j]}dp2[k][i][j]=0l100min{dp0[k][i][l]+G[l][j]}

转移时注意边界条件的设置,例如在处理 d p 0 dp_0 dp0 d p 1 dp_1 dp1 时,自身和自身的距离应该为无穷大;但是在处理 G G G 时,自身和自身的距离应该为 0 0 0

设查询为 s i , t i , k i s_i,t_i,k_i si,ti,ki,则当前查询的答案为
min ⁡ 1 ≤ j ≤ n { d p 1 [ ⌊ k i 100 ⌋ ] [ s i ] [ j ] + d p 2 [ k  mod  100 ] [ j ] [ t i ] } \min_{1 \le j \le n} \{ dp_1[\lfloor \frac{k_i}{100} \rfloor][s_i][j] + dp_2[k \text{ mod } 100][j][t_i] \} 1jnmin{dp1[100ki][si][j]+dp2[k mod 100][j][ti]}

总的时间复杂度为 O ( T ⋅ ( 100 ⋅ n 3 + q ⋅ n ) ) O(T \cdot (100 \cdot n^3 + q \cdot n)) O(T(100n3+qn))

2.3 代码

ll G[55][55];
ll dp[3][105][55][55];
int main()
{
    int T, n, m, u, v, q, s, t, k;
    ll w;
    scanf("%d", &T);

    while (T--)
    {
        scanf("%d%d", &n, &m);
        memset(dp, 0x3f, sizeof(dp));
        memset(G, 0x3f, sizeof(G));
        for (int k = 0; k < 2; k++)
        {
            for (int i = 1; i <= n; i++)
            {
                dp[k][0][i][i] = 0;
            }
        }
        for (int i = 1; i <= m; i++)
        {
            scanf("%d%d%lld", &u, &v, &w);
            G[u][v] = min(G[u][v], w);
        }
        for (int l = 1; l <= 100; l++)
        {
            for (int i = 1; i <= n; i++)
            {
                for (int j = 1; j <= n; j++)
                {
                    for (int k = 1; k <= n; k++)
                    {
                        dp[0][l][i][j] = min(dp[0][l][i][j], dp[0][l - 1][i][k] + G[k][j]);
                    }
                }
            }
        }
        for (int l = 1; l <= 100; l++)
        {
            for (int i = 1; i <= n; i++)
            {
                for (int j = 1; j <= n; j++)
                {
                    for (int k = 1; k <= n; k++)
                    {
                        dp[1][l][i][j] = min(dp[1][l][i][j], dp[1][l - 1][i][k] + dp[0][100][k][j]);
                    }
                }
            }
        }
        for (int i = 1; i <= n; i++)
        {
            G[i][i] = 0;
        }
        for (int k = 1; k <= n; k++)
        {
            for (int i = 1; i <= n; i++)
            {
                for (int j = 1; j <= n; j++)
                {
                    G[i][j] = min(G[i][j], G[i][k] + G[k][j]);
                }
            }
        }
        for (int l = 0; l <= 100; l++)
        {
            for (int i = 1; i <= n; i++)
            {
                for (int j = 1; j <= n; j++)
                {
                    for (int k = 1; k <= n; k++)
                    {
                        dp[2][l][i][j] = min(dp[2][l][i][j], dp[0][l][i][k] + G[k][j]);
                    }
                }
            }
        }
        scanf("%d", &q);
        while (q--)
        {
            scanf("%d%d%d", &s, &t, &k);
            ll ans = 0x3f3f3f3f3f3f3f3f;
            for (int i = 1; i <= n; i++)
            {
                ans = min(ans, dp[1][k / 100][s][i] + dp[2][k % 100][i][t]);
            }
            if (ans == 0x3f3f3f3f3f3f3f3f)  ans = -1;
            printf("%lld\n", ans);
        }
    }
    return 0;
}

3. HDU - 6370 - Werewolf(Tarjan + 乱搞)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6370

题目来源:2018 HDU 多校第六场

3.1 题意

n ( 1 ≤ n ≤ 1 0 5 ) n(1 \le n \le 10^5) n(1n105) 个人,每个人可能是狼人,也可能是村民。

每个人 i i i 都会说一句话,表明 x x x 是狼人或村民。

已知下列事实:

  1. 狼人说的话不知道是真是假;
  2. 村民说的话一定是真的。

我们可以将这些人分成三类:

  1. 在所有情况下都是村民;
  2. 在所有情况下都是狼人;
  3. 在某些情况下是村民,在某些情况下是狼人。

现在需要你输出第 1 1 1 类和第 2 2 2 类人的数量。

共有 T ( 1 ≤ T ≤ 10 ) T(1 \le T \le 10) T(1T10) 组读入。

3.2 解题过程

根据题意,很显然需要建图。

每个人 i i i 都会说一句话,表明 x x x 是狼人或村民。

因此对于第 i i i 个人,如果它认为 x x x 时村民,建有向边 < i , x , 0 > <i, x, 0> <i,x,0>;否则建有向边 < i , x , 1 > <i, x, 1> <i,x,1>

稍加分析即可发现任何人都可以是狼人,因此第 1 1 1 类人的数量一定为 0 0 0

根据题意,我们建立的图中,每个人的出度一定为 1 1 1。因此一个强连通分量一定为一个简单环

因此,我们跑一遍 Tarjan 即可确定所有的简单环。

仔细观察和推导,会发现对于一个简单环,如果其恰好只有一条边的权值为 1 1 1,则边权为 1 1 1 的那条边指向的点一定为狼人,其他人都可以为村民,否则会发生冲突。(可以假设以每个点为起点进行遍历,并假设起点为村民)
对于一个简单环的其他情况,这个简单环中的所有人均可为村民。

考虑完环,我们再来考虑链。

链中只存在一种特殊情况,就是如果链上某一一条边的权值为 0 0 0,并且指向了一定为狼人的点,那么这条边的起始点一定也为狼人,否则会发生矛盾。不满足上述情况的点,可以为村民。

其他情况下,链上所有点都可以为村民。

据此,我们只需一遍 DFS 即可计算出答案。

时间复杂度: O ( T ⋅ n ) O(T \cdot n) O(Tn)

另外感觉这题可以直接 2-SAT,复杂度跟上面一样,等 HDU 开放之后试一试。

3.3 代码

int n, cnt, tot, top, num;
int head[maxn];
int dfn[maxn], low[maxn], st[maxn], col[maxn];
bool instack[maxn];
int stat[maxn];
bool vis[maxn], mark[maxn];
int ans;
struct edge
{
    int v, w, nxt;
} Edge[2 * maxn];
void init()
{
    for(int i = 0; i <= n; i++) head[i] = -1;
    cnt = tot = top = num = ans = 0;
    memset(instack, false, sizeof(instack));
    memset(stat, 0, sizeof(stat));
    memset(vis, false, sizeof(vis));
    memset(mark, false, sizeof(mark));
    memset(dfn, 0, sizeof(dfn));
    memset(col, 0, sizeof(col));
}

void addedge(int u, int v, int w)
{
    Edge[cnt].v = v;
    Edge[cnt].w = w;
    Edge[cnt].nxt = head[u];
    head[u] = cnt++;
}

void tarjan(int id)
{
    dfn[id] = low[id] = ++tot;
    st[++top] = id;
    instack[id] = true;
    for(int i = head[id]; i != -1; i = Edge[i].nxt)
    {
        int v = Edge[i].v;
        if(!dfn[v])
        {
            tarjan(v);
            low[id] = min(low[id], low[v]);
        }
        else if(instack[v])
            low[id] = min(low[id], dfn[v]);
    }
    if(dfn[id] == low[id])
    {
        int v;
        num++;
        do
        {
            v = st[top--];
            instack[v] = false;
            col[v] = num;
        } while(v != id);
    }
}
void dfs(int id)
{
    int i = head[id];
    int v = Edge[i].v;
    int w = Edge[i].w;
    vis[id] = true;
    if(col[id] == col[v])   stat[col[id]] += (w == 1);
    if(vis[v])
    {
        if(stat[col[id]] == 1)  ans++;
    }
    else
    {
        dfs(v);
    }
    if(stat[col[id]] == 1 && w == 1)
    {
        mark[v] = true;
    }
    if(col[id] != col[v])
    {
        if(mark[v])
        {
            if(w == 0)
            {
                mark[id] = true;
                ans++;
            }
        }
    }
}
int main()
{
    int t, x;
    char op[15];
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d", &n);
        init();
        for(int i = 1; i <= n; i++)
        {
            scanf("%d", &x);
            scanf("%s", op);
            if(op[0] == 'v')
            {
                addedge(i, x, 0);
            }
            else
            {
                addedge(i, x, 1);
            }
        }
        for(int i = 1; i <= n; i++)
        {
            if(!col[i]) tarjan(i);
        }
        for(int i = 1; i <= n; i++)
        {
            if(!vis[i]) dfs(i);
        }
        printf("0 %d\n", ans);
    }
    return 0;
}

4. HDU- 6386 - Age of Moyu(DFS + BFS)

题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=6386

题目来源:2018 HDU 多校第七场

4.1 题意

给你一张 n ( 2 ≤ n ≤ 1 0 5 ) n(2 \le n \le 10^5) n(2n105) 个点和 m ( 0 ≤ m ≤ 2 ⋅ 1 0 5 ) m(0 \le m \le 2 \cdot 10^5) m(0m2105) 条边的无向图,保证没有自环和重边。

每条边都有一个边权 C i ( 1 ≤ C i ≤ 1 0 6 ) C_i(1 \le C_i \le 10^6) Ci(1Ci106)

现在你从 1 1 1 出发,要走到 n n n,边权变换一次的代价为 1 1 1,问最小代价,或者说明无法走到 n n n

本题最多有 20 20 20 组输入。

4.2 解题过程

直接最短路的做法是假的!(可以通过造数据的方法,hack 掉这种做法)

正确的做法是 BFS 套 DFS,外层 BFS 正常跑最短路,每遍历到一个边,就通过 DFS 将周围的相同权值的边标记上。

无论是 DFS 还是 BFS,遇到被标记过的边,都直接跳过不走。

时间复杂度: O ( 20 ⋅ ( n + m ) ) O(20 \cdot (n + m)) O(20(n+m))

4.3 代码

int n, m, cnt;
int head[maxn];
ll d[maxn];
bool vis[maxn], vis_edge[4 * maxn];
struct edge
{
    int v, w, nxt;
} Edge[4 * maxn];
void init()
{
    for(int i = 0; i <= n; i++) head[i] = -1;
    cnt = 0;
}
void addedge(int u, int v, int w)
{
    Edge[cnt].v = v;
    Edge[cnt].w = w;
    Edge[cnt].nxt = head[u];
    head[u] = cnt++;
}
queue<int> que;
void dfs(int id, int w, ll val)
{
    if(!vis[id])
    {
        vis[id] = true;
        d[id] = val;
        if(id == n) return;
        que.push(id);
    }
    for(int i = head[id]; i != -1; i = Edge[i].nxt)
    {
        int v = Edge[i].v;
        if(w != Edge[i].w)  continue;
        if(vis_edge[i])  continue;
        vis_edge[i] = vis_edge[i ^ 1] = true;
        dfs(v, w, val);
    }
}
void bfs()
{
    while(!que.empty()) que.pop();
    memset(vis, false, sizeof(vis));
    memset(vis_edge, false, sizeof(vis_edge));
    memset(d, 0x3f, sizeof(d));
    d[1] = 0;
    vis[1] = true;
    que.push(1);
    while(!que.empty())
    {
        int now = que.front();
        que.pop();
        for(int i = head[now]; i != -1; i = Edge[i].nxt)
        {
            if(vis_edge[i])  continue;
            vis_edge[i] = vis_edge[i ^ 1] = true;
            int v = Edge[i].v;
            dfs(v, Edge[i].w, d[now] + 1);
        }
    }
}
int main()
{
    int u, v, w;
    while(~scanf("%d%d", &n, &m))
    {
        init();
        for(int i = 1; i <= m; i++)
        {
            scanf("%d%d%d", &u, &v, &w);
            addedge(u, v, w);
            addedge(v, u, w);
        }
        bfs();
        if(d[n] == 0x3f3f3f3f3f3f3f3f)  printf("-1\n");
        else printf("%lld\n", d[n]);
    }
    return 0;
}

5. HDU - 6311 - Cover(欧拉回路 + 乱搞)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6311

题目来源:2018 HDU 多校第二场

5.1 题意

给你一张 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \le n \le 10^5) n(1n105) 个点和 m ( 1 ≤ m ≤ 1 0 5 ) m(1 \le m \le 10^5) m(1m105) 条边的无向图,保证没有自环和重边。

每个士兵可以选择一个起点,然后沿着图不断走下去,但每条边走过去之后就不能再走。问最少需要多少士兵,使得图中的每条边恰好被走一次

题目有多组读入,最多 20 20 20 组。

5.2 解题过程

首先,每个士兵走的道路形成的子图一定是一个欧拉通路或者欧拉回路。

设图中奇度顶点的个数为 c n t cnt cnt,则士兵的数量为 c n t 2 \frac{cnt}{2} 2cnt

显然如果 c n t > 0 cnt > 0 cnt>0,整个图中是不存在欧拉回路的。

我们可以尝试两两将奇度顶点连边(每个奇度顶点只和一个奇度顶点连边),之后我们优先从原图的奇度顶点开始跑欧拉回路,当这些顶点跑完之后再次遍历所有顶点,如果发现还没有走过的顶点,就从这个没有走过的顶点开始跑新的欧拉回路。

在跑欧拉回路时,将每一条路径所经过的边分别存储。

如何分辨不同的路径呢?很简单,每一次重新开始一轮查找欧拉回路时或者遇到新建的虚边时,相当于走到了新的路径上。而且这种实现方法无需考虑原图的连通性。

最后根据所存储的每一条路径中的边,按照要求将答案输出即可。

时间复杂度: O ( 20 ⋅ n ) O(20 \cdot n) O(20n)

5.3 代码

int n, m, cnt, tot, num2;
int head[maxn], deg[maxn], col[maxn];
bool vis[4 * maxn], mark[4 * maxn], visp[maxn];
vector<int> ans[maxn], final_ans[maxn];
struct edge
{
    int v, nxt;
} Edge[4 * maxn];
void init()
{
    for(int i = 0; i <= n; i++) head[i] = -1;
    cnt = 0, tot = 0, num2 = 0;
    memset(col, 0, sizeof(col));
    memset(deg, 0, sizeof(deg));
    memset(vis, false, sizeof(vis));
    memset(mark, false, sizeof(mark));
    memset(visp, false, sizeof(visp));
    for(int i = 0; i <= n; i++) ans[i].clear();
    for(int i = 0; i <= n; i++) final_ans[i].clear();
}
void addedge(int u, int v)
{
    Edge[cnt].v = v;
    Edge[cnt].nxt = head[u];
    head[u] = cnt++;
}
void euler(int id)
{
    visp[id] = true;
    for(int i = head[id]; i != -1; i = Edge[i].nxt)
    {
        int v = Edge[i].v;
        if(!vis[i])
        {
            vis[i] = vis[i ^ 1] = true;
            euler(v);
            if(i >= 2 * m) num2++;
            else if(i != -1)
            {
               ans[num2].pb(i);
            }
        }
    }
}
void dfs(int id, int color)
{
    if(col[id]) return;
    col[id] = color;
    for(int i = head[id]; i != -1; i = Edge[i].nxt)
    {
        int v = Edge[i].v;
        if(col[v])  continue;
        dfs(v, color);
    }
}
int main()
{
    int u, v;
    while(~scanf("%d%d", &n, &m))
    {
        init();
        for(int i = 1; i <= m; i++)
        {
            scanf("%d%d", &u, &v);
            addedge(u, v);
            addedge(v, u);
            deg[u]++;
            deg[v]++;
        }
        int num = 0;
        for(int i = 1; i <= n; i++)
        {
            if(!col[i]) dfs(i, ++num);
        }
        int point = -1;
        for(int i = 1; i <= n; i++)
        {
            if(deg[i] & 1)
            {
                if(point != -1)
                {
                    addedge(point, i);
                    addedge(i, point);
                    point = -1;
                }
                else point = i;
            }
        }
        for(int i = 1; i <= n; i++)
        {
            if(!visp[i] && (deg[i] & 1))
            {
                ++num2;
                euler(i);
                //--num2;
            }
        }
        for(int i = 1; i <= n; i++)
        {
            if(!visp[i])
            {
                ++num2;
                euler(i);
            }
        }
        int num3 = 0;
        for(int i = 0; i <= num2; i++)
        {
            if(ans[i].size() == 0)   continue;
            ++num3;
            int size = ans[i].size();
            for(int j = size - 1; j >= 0; j--)  final_ans[num3].pb(ans[i][j]);
        }
        printf("%d\n", num3);
        for(int i = 1; i <= num3; i++)
        {
            printf("%d", (int)final_ans[i].size());
            for(int j = 0; j < final_ans[i].size(); j++)
            {
                int id = final_ans[i][j];
                if(id & 1)
                {
                    id ^= 1;
                    printf(" -%d", id / 2 + 1);
                }
                else printf(" %d", id / 2 + 1);
            }
            printf("\n");
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值