• @loj



    @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
    ", 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
    ", 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
    ", ans);
    }
    

    @details@

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

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

  • 相关阅读:
    vim 显示行号 临时&永久
    ubuntu redis config IP password
    Vulnerability Scanning Tools
    Tomcat清理无用应用防止漏洞
    XSS平台-学习
    Amazon aws s3 加速
    XSS的DOS攻击之 server limit dos
    Linux Nginx naxsi
    Acunetix Web Vulnerability Scanner abbr. AWVS
    nginx: [emerg] open() "/var/run/nginx.pid" failed (13: Permission denied)
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11716779.html
Copyright © 2020-2023  润新知