首页 > 其他 > 详细

@loj - 2496@ 「AHOI / HNOI2018」毒瘤

时间:2019-10-22 10:14:15      阅读:76      评论:0      收藏:0      [点我收藏+]

@description@

从前有一名毒瘤。

毒瘤最近发现了量产毒瘤题的奥秘。考虑如下类型的数据结构题:给出一个数组,要求支持若干种奇奇怪怪的修改操作(例如给一个区间内的数同时加上 c,或者将一个区间内的数同时开平方根),并且支持询问区间的和。毒瘤考虑了 n 个这样的修改操作,并将它们编号为 1...n。当毒瘤要出数据结构题的时候,他就将这些修改操作中选若干个出来,然后出成一道题。

当然了,这样出的题有可能不可做。通过精妙的数学推理,毒瘤揭露了这些修改操作之间的关系:有
m 对「互相排斥」的修改操作,第 i 对是第 ui 操作和第 vi 个操作。当一道题中同时含有 ui 和 vi 这两个操作时,这道题就会变得不可做。另一方面,当一道题中不包含任何「互相排斥」的操作时,这个题就是可做的。
此外,毒瘤还发现了一个规律: m - n 是一个很小的数字(参见「数据范围」中的说明),且任意两个修改操作都是连通的。两个修改操作 a, b 是连通的,当且仅当存在若干操作 t0, t1, ..., tl,使得 t0 = a, tl = b,且对任意 1 <= i <= l,t[i] 与 t[i-1] 都是「互相排斥」的修改操作。

一对「互相排斥」的修改操作称为互斥对。现在毒瘤想知道,给定值 n 和 m 个互斥对,他一共能出出多少道可做的不同的数据结构题。两个数据结构题是不同的,当且仅当其中某个操作出现在了其中一个题中,但是没有出现在另一个题中。

输入格式
第一行为正整数 n,m。
接下来 m 行,每行两个正整数 u, v,代表一对「互相排斥」的修改操作。

输出格式
输出一行一个整数,表示毒瘤可以出的可做的不同的数据结构题的个数。这个数可能很大,所以只输出模 998244353 后的值。

样例输入 1
3 2
1 2
2 3
样例输出 1
5
样例输入 2
6 8
1 2
1 3
1 4
2 4
3 5
4 5
4 6
1 6
样例输出 2
16

数据范围与提示
n <= 10^5, m <= n + 10。

@solution@

众所周知图的独立集问题是不可做的,所以我们需要对问题进行合理的暴力搜索。

注意到当 m = n - 1(即一棵树)时用 dp 随便做。
而 m - n 很小,这意味着整张图是一棵树 + 很少的非树边。
算了一下大概非树边最多 11 条,这 11 条边连着最多 22 个特殊点。

于是就有一个大胆的想法:暴力枚举特殊点是否被选中,然后这棵树再 O(n) 做一遍 dp。
暴力枚举的部分看上去是 2^22 种状态,实际上每条边只会对应 3 种状态(不可能一条边连着的两个点同时选),于是只会暴力枚举 3^11 种状态。这个范围小很多。
于是你就可以 O(3^11*n) 写出本题的暴力,约 70 分的好成绩。

要是我每次可以不重新算整棵树的 dp 就好了。
如果特殊点将原树分成了互不相关的若干连通块,且每个连通块只会受 1 或 2 个特殊点影响就好了。
这样我就可以预处理,就不用每次枚举完再重新做一遍 dp。

那我们就通过一些手段将这棵树分成若干连通块:使用虚树。
建出特殊点之间的虚树,虚树上的点将原图分成若干连通块。这样的话,要么是虚树上一条边对应一个连通块,要么一个连通块属于虚树上的某个点管辖。
这样只需要再在虚树上做一遍树形 dp,将预处理出来的连通块信息当作边权/点权即可。
虚树上只有最多 22*2 个点,所以可以轻松过。

@accepted code@

#include<map>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define rep(G, x) for(Graph::edge *p=G.adj[x];p;p=p->nxt)
const int MAXN = 100000;
const int MOD = 998244353;
inline int add(int x, int y) {return (x + y)%MOD;}
inline int mul(int x, int y) {return 1LL*x*y%MOD;}
struct Graph{
    struct edge{
        int to, f[2][2];
        edge *nxt;
    }edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt;
    Graph() {ecnt = &edges[0];}
    void addedge(int u, int v) {
        edge *p = (++ecnt);
        p->to = v, p->nxt = adj[u], adj[u] = p;
        p = (++ecnt);
        p->to = u, p->nxt = adj[v], adj[v] = p;
//      printf("! %d %d\n", u, v);
    }
}G1, G2;
int fa[20][MAXN + 5], dep[MAXN + 5], tid[MAXN + 5], dcnt = 0;
void dfs(int x, int f) {
    fa[0][x] = f, tid[x] = (++dcnt);
    for(int i=1;i<20;i++)
        fa[i][x] = fa[i-1][fa[i-1][x]];
    dep[x] = dep[f] + 1;
    rep(G1, x) {
        if( p->to == f ) continue;
        dfs(p->to, x);
    }
}
int lca(int u, int v) {
    if( dep[u] < dep[v] ) swap(u, v);
    for(int i=19;i>=0;i--)
        if( dep[fa[i][u]] >= dep[v] )
            u = fa[i][u];
    if( u == v ) return u;
    for(int i=19;i>=0;i--)
        if( fa[i][u] != fa[i][v] )
            u = fa[i][u], v = fa[i][v];
    return fa[0][u];
}
int sfa[MAXN + 5];
int find(int x) {
    return sfa[x] = (sfa[x] == x ? x : find(sfa[x]));
}
bool unite(int x, int y) {
    int fx = find(x), fy = find(y);
    if( fx == fy ) return false;
    else {
        sfa[fx] = fy;
        return true;
    }
}
bool tag[MAXN + 5];
bool cmp(int x, int y) {return tid[x] < tid[y];}
vector<int>arr;
int stk[MAXN + 5], tp;
void insert(int x) {
    if( tp ) {
        int z = lca(stk[tp], x);
        while( tp ) {
            int y = stk[tp--]; tag[y] = true;
            if( !tp || dep[stk[tp]] < dep[z] ) {
                if( y != z ) G2.addedge(z, y);
                break;
            }
            else G2.addedge(stk[tp], y);
        }
        stk[++tp] = z;
    }
    stk[++tp] = x;
}
int build_vtree() {
    sort(arr.begin(), arr.end(), cmp);
    for(int i=0;i<arr.size();i++)
        insert(arr[i]);
    int ret;
    while( tp ) {
        ret = stk[tp--], tag[ret] = true;
        if( tp ) G2.addedge(stk[tp], ret);
    }
    return ret;
}
int dp[2][MAXN + 5];
void dfs2(int x) {
    tag[x] = true, dp[0][x] = dp[1][x] = 1;
    rep(G1, x) {
        if( !tag[p->to] ) {
            dfs2(p->to);
            dp[0][x] = mul(dp[0][x], add(dp[0][p->to], dp[1][p->to]));
            dp[1][x] = mul(dp[1][x], dp[0][p->to]);
        }
    }
}
void dfs3(int x, int f) {
    dp[0][x] = dp[1][x] = 1;
    rep(G1, x) {
        if( p->to != f ) {
            if( !tag[p->to] ) dfs3(p->to, x);
            dp[0][x] = mul(dp[0][x], add(dp[0][p->to], dp[1][p->to]));
            dp[1][x] = mul(dp[1][x], dp[0][p->to]);
        }
    }
}
void func1(int x, int y, int f[][2]) {
    int p = fa[0][y];
    if( p == x ) {
        f[0][0] = f[0][1] = f[1][0] = 1;
        return ;
    }
    dp[0][x] = 1, dp[1][x] = 0;
    dfs3(p, y), f[0][0] = add(dp[0][p], dp[1][p]), f[0][1] = dp[0][p];
    dp[0][x] = 0, dp[1][x] = 1;
    dfs3(p, y), f[1][0] = add(dp[0][p], dp[1][p]), f[1][1] = dp[0][p];
    dfs2(p);
}
int g[2][MAXN + 5];
void get_value(int x, int f) {
    rep(G2, x) {
        if( p->to == f ) continue;
        func1(x, p->to, p->f), get_value(p->to, x);
    }
    g[0][x] = g[1][x] = 1;
    rep(G1, x) {
        if( !tag[p->to] ) {
            dfs2(p->to);
            g[0][x] = mul(g[0][x], add(dp[0][p->to], dp[1][p->to]));
            g[1][x] = mul(g[1][x], dp[0][p->to]);
        }
    }
}
int clr[MAXN + 5], c[MAXN + 5], root, ans;
vector<int>vec[MAXN + 5];
void check(int x, int fa) {
    rep(G2, x) {
        if( p->to != fa )
            check(p->to, x);
    }
    if( clr[x] != -1 )
        dp[clr[x]][x] = g[clr[x]][x], dp[!clr[x]][x] = 0;
    else dp[0][x] = g[0][x], dp[1][x] = g[1][x];
    rep(G2, x) {
        if( p->to != fa ) {
            dp[0][x] = mul(dp[0][x], add(mul(p->f[0][0], dp[0][p->to]), mul(p->f[0][1], dp[1][p->to])));
            dp[1][x] = mul(dp[1][x], add(mul(p->f[1][0], dp[0][p->to]), mul(p->f[1][1], dp[1][p->to])));
        }
    }
}
void search(int d) {
    if( d == arr.size() ) {
        check(root, 0);
        ans = add(ans, add(dp[0][root], dp[1][root]));
        return ;
    }
    clr[arr[d]] = 0, search(d + 1);
    if( !c[arr[d]] ) {
        for(int i=0;i<vec[arr[d]].size();i++)
            c[vec[arr[d]][i]]++;
        clr[arr[d]] = 1, search(d + 1);
        for(int i=0;i<vec[arr[d]].size();i++)
            c[vec[arr[d]][i]]--;
    }
}
map<int, int>mp;
int index(int x) {
    if( mp.count(x) ) return mp[x];
    else {
        arr.push_back(x);
        return mp[x] = arr.size() - 1;
    }
}
int main() {
    int n, m; scanf("%d%d", &n, &m);
    for(int i=1;i<=n;i++) sfa[i] = i;
    for(int i=1;i<=m;i++) {
        int u, v; scanf("%d%d", &u, &v);
        if( unite(u, v) ) G1.addedge(u, v);
        else {
            index(u), index(v);
            vec[u].push_back(v);
            vec[v].push_back(u);
        }
    }
    if( m == n - 1 ) {
        dfs2(1), printf("%d\n", add(dp[0][1], dp[1][1]));
        return 0;
    }
    dfs(1, 0), root = build_vtree(), get_value(root, 0);
    for(int i=1;i<=n;i++) c[i] = 0, clr[i] = -1;
    search(0);
    printf("%d\n", ans);
}

@details@

虽然说着挺简单,但还是写了 190+ 行的代码。
所以写暴力大概是最划算的选择。

所以注意区分 连通块属于虚树上的一条边 与 连通块属于虚树上一个点。

@loj - 2496@ 「AHOI / HNOI2018」毒瘤

原文:https://www.cnblogs.com/Tiw-Air-OAO/p/11716779.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!