zoukankan      html  css  js  c++  java
  • Hdu第八场 树形dp+基环树

    Card Game

    每个牌背面的数字朝正面的数字连一条有向边

    则题目变为问你最少翻转多少次 能使得每个数字的入度不超过1

    首先判断图中每个连通块是不是树或者基环树 因为只有树或者基环树能使得每个点的入度不超过1

    判的话就直接判断边的数量与点的数量之间的大小关系如果边数<=点数则可行

    对于树 我们进行两次dp 第一次dp出以一个点为根需要翻转的次数 第二次就可以转移出以每个点为根需要翻转的次数

    对于基环树 我们先看环里面需要翻转的次数 再判断环之外需要翻转的次数 环之外的方向是确定的 很好判断

    #include <bits/stdc++.h>
    using namespace std;
    #define rep(i,a,n) for (int i=a;i<n;i++)
    #define per(i,a,n) for (int i=n-1;i>=a;i--)
    #define pb push_back
    #define mp make_pair
    #define all(x) (x).begin(),(x).end()
    #define fi first
    #define se second
    #define SZ(x) ((int)(x).size())
    typedef vector<int> VI;
    typedef long long ll;
    typedef pair<int, int> PII;
    const ll mod = 998244353;
    ll powmod(ll a, ll b)
    {
            ll res = 1;
            a %= mod;
            assert(b >= 0);
            for (; b; b >>= 1)
            {
                    if (b & 1)
                    {
                            res = res * a % mod;
                    }
                    a = a * a % mod;
            }
            return res;
    }
    ll gcd(ll a, ll b)
    {
            return b ? gcd(b, a % b) : a;
    }
    // head
    
    const int N = 401000;
    int n, m, u, v, T, mt[N], hs[N], _;
    vector<PII> e[N];
    VI vec[N];
    int vis[N], f[N], se[N], q[N], dp[N], pre[N], deg[N], fa[N];
    PII chke[N];
    int find(int u)
    {
            return f[u] == u ? u : f[u] = find(f[u]);
    }
    void solve()
    {
            n = 0;
            T++;
            rep(i, 1, m + 1)
            {
                    scanf("%d%d", &u, &v);
                    if (mt[u] != T)  //离散化
                    {
                            mt[u] = T, hs[u] = n++;
                    }
                    if (mt[v] != T)
                    {
                            mt[v] = T, hs[v] = n++;
                    }
                    u = hs[u];
                    v = hs[v];
                    chke[i] = mp(u, v);  //记录每条边
            }
            rep(i, 0, n)  //初始化
            e[i].clear(), vis[i] = 0, f[i] = i, vec[i].clear(), se[i] = 0;
            rep(i, 1, m + 1)
            {
                    u = chke[i].fi, v = chke[i].se;
                    f[find(u)] = find(v); //一个连通块的点属于一个father
                    e[u].pb(mp(v, i));  //建边 i为正表示该边是反向与原来相反
                    e[v].pb(mp(u, -i));
            }
            rep(i, 0, n)   //把一个连通块的点放到father的vector中
            vec[find(i)].pb(i);
            rep(i, 1, m + 1)
            {
                    u = chke[i].fi, v = chke[i].se;
                    se[find(u)]++;  //统计每个连通块中边的数量
            }
            int ans = 0, ans2 = 1;
            rep(i, 0, n)
            {
                    if (find(i) != i)  //每个连通块只会枚举一次
                    {
                            continue;
                    }
                    if (se[i] > SZ(vec[i]))  //如果边数大于点数 则不是基环树也不是树 答案不存在
                    {
                            puts("-1 -1");
                            return;
                    }
                    if (se[i] < SZ(vec[i]))
                    {
                            //如果是树的话 dp两次 第一次dp出以0为根的翻转次数  第二次dp出全部点的翻转次数
                            int s = 1e9, s2 = 0;
                            dp[i] = 0;
                            int t = 1;
                            q[0] = i;
                            fa[i] = -1;
                            rep(j, 0, t)
                            {
                                    u = q[j];
                                    for (auto p : e[u])
                                    {
                                            int v = p.fi;
                                            if (v != fa[u])
                                            {
                                                    fa[q[t++] = v] = u, pre[v] = p.se > 0, dp[i] += pre[v];
                                            }
                                    }
                            }
                            rep(j, 1, t)
                            {
                                    u = q[j];
                                    if (pre[u] == 1)
                                    {
                                            dp[u] = dp[fa[u]] - 1;
                                    }
                                    else
                                    {
                                            dp[u] = dp[fa[u]] + 1;
                                    }
                            }
                            rep(j, 0, t)
                            s = min(s, dp[q[j]]); //当前树所需要的最少翻转次数
                            rep(j, 0, t)
                            if (dp[q[j]] == s)
                            {
                                    s2++; //统计有多少个方案数
                            }
                            ans = ans + s;
                            ans2 = (ll)ans2 * s2 % mod;
                    }
                    if (se[i] == SZ(vec[i]))  //基环树
                    {
                            int s = 0;
                            for (auto u : vec[i])
                            {
                                    deg[u] = SZ(e[u]);  //统计每一个点的出度
                            }
                            int t = 0;
                            for (auto u : vec[i])
                                    if (deg[u] == 1)
                                    {
                                            q[t++] = u;
                                    }
                            rep(j, 0, t)
                            {
                                    u = q[j];
                                    for (auto p : e[u])
                                    {
                                            int v = p.fi;
                                            if (deg[v] <= 1)
                                            {
                                                    continue;
                                            }
                                            if (p.se < 0)
                                            {
                                                    s++;
                                            }
                                            --deg[v];
                                            if (deg[v] == 1)
                                            {
                                                    q[t++] = v;
                                            }
                                    }
                            }
                            int rt = -1;
                            for (auto u : vec[i])
                                    if (deg[u] == 2)
                                    {
                                            rt = u;
                                            break;
                                    }
                            int pree = 1e9, cnt = 0, u = rt, s3 = 0;
                            while (1)
                            {
                                    for (auto p : e[u])
                                            if (deg[p.fi] == 2 && p.se + pree != 0)
                                            {
                                                    s3 += p.se < 0;
                                                    cnt++;
                                                    pree = p.se;
                                                    u = p.fi;
                                                    break;
                                            }
                                    if (u == rt)
                                    {
                                            break;
                                    }
                            }
                            s3 = min(s3, cnt - s3); //环中的翻转最少次数是确定的
                            s += s3;  //环外的方向是确定的 环外需要翻转的次数加上环中翻转的次数
                            ans += s;
                            if (s3 == cnt - s3)
                            {
                                    ans2 = ans2 * 2 % mod;
                            }
                    }
            }
            printf("%d %d
    ", ans, ans2);
    }
    int main()
    {
            for (scanf("%d", &_); _; _--)
            {
                    scanf("%d", &m);
                    solve();
            }
    }
    //Card Game
  • 相关阅读:
    SQL Server数据库新建拥有部分查看操作权限的用户
    Asp.net导入Excel数据文件
    前台页面下载服务器端文件
    页面开机自启动,页面置顶显示,页面持续获得焦点,鼠标点击器源码
    asp.net DataGrid GridView 表格之分页显示与翻页功能及自定义翻页页码样式
    asp.net DataGrid GridView 表格之取消设计最初显示的绑定列
    asp.net DataGrid GridView 表格之选中行与获取选中行数据
    Winform 、asp.net TreeView 树形控件
    Torrent种子下载下来的文件,如何校验其完整性?
    在与 SQL Server 建立连接时出现与网络相关的或特定于实例的错误
  • 原文地址:https://www.cnblogs.com/Aragaki/p/9489908.html
Copyright © 2011-2022 走看看