@loj - 2496@ 「AHOI / HNOI2018」毒瘤
@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」毒瘤的更多相关文章
- 【LOJ】#2496. 「AHOI / HNOI2018」毒瘤
题面 还有这么诚实的出题人! 我们最多影响20个点,然后把这20个点的虚树建出来,并且枚举每个点的选举状态,如果一个点选或不选可以通过改\(dp[u][0] = 0\)或\(dp[u][1] = 0\ ...
- Loj #2495. 「AHOI / HNOI2018」转盘
Loj #2495. 「AHOI / HNOI2018」转盘 题目描述 一次小 G 和小 H 原本准备去聚餐,但由于太麻烦了于是题面简化如下: 一个转盘上有摆成一圈的 \(n\) 个物品(编号 \(1 ...
- Loj #2494. 「AHOI / HNOI2018」寻宝游戏
Loj #2494. 「AHOI / HNOI2018」寻宝游戏 题目描述 某大学每年都会有一次 Mystery Hunt 的活动,玩家需要根据设置的线索解谜,找到宝藏的位置,前一年获胜的队伍可以获得 ...
- loj #2510. 「AHOI / HNOI2018」道路
#2510. 「AHOI / HNOI2018」道路 题目描述 W 国的交通呈一棵树的形状.W 国一共有 n−1 个城市和 nnn 个乡村,其中城市从 111 到 n−1 编号,乡村从 111 到 n ...
- loj #2509. 「AHOI / HNOI2018」排列
#2509. 「AHOI / HNOI2018」排列 题目描述 给定 nnn 个整数 a1,a2,…,an(0≤ai≤n),以及 nnn 个整数 w1,w2,…,wn.称 a1,a2,…,an 的 ...
- loj #2508. 「AHOI / HNOI2018」游戏
#2508. 「AHOI / HNOI2018」游戏 题目描述 一次小 G 和小 H 在玩寻宝游戏,有 nnn 个房间排成一列,编号为 1,2,…,n,相邻房间之间都有 111 道门.其中一部分门上有 ...
- loj#2510. 「AHOI / HNOI2018」道路 记忆化,dp
题目链接 https://loj.ac/problem/2510 思路 f[i][a][b]表示到i时,公路个数a,铁路个数b 记忆化 复杂度=状态数=\(nlog^2n\) 代码 #include ...
- loj#2509. 「AHOI / HNOI2018」排列(思维题 set)
题意 题目链接 Sol 神仙题Orz 首先不难看出如果我们从\(a_i\)向\(i\)连一条边,我们会得到以\(0\)为根的树(因为每个点一定都有一个入度,出现环说明无解),同时在进行排列的时候需要保 ...
- loj#2020 「AHOI / HNOI2017」礼物 ntt
loj#2020 「AHOI / HNOI2017」礼物 链接 bzoj没\(letex\),差评 loj luogu 思路 最小化\(\sum\limits_1^n(a_i-b_i)^2\) 设改变 ...
随机推荐
- 【洛谷3295】[SCOI2016]萌萌哒
传送门 倍增并查集. //Twenty #include<algorithm> #include<iostream> #include<cstdlib> #incl ...
- JS简单实现:根据奖品权重计算中奖概率实现抽奖的方法
本文主要介绍:使用 JS 根据奖品权重计算中奖概率实现抽奖的方法. 一.示例场景 1.1.设置抽奖活动的奖项名称 奖项名称:["一等奖", "二等奖", &qu ...
- springmvc 使用poi解析excel并通过hibernate连续插入多条数据 实际数据库只能保存最后一条
有一个原始数据的excel表 用poi解析之后通过hibernate插数据库 结果 后来发现,有人说 果断尝试 问题解决 但是这好像并不是真正解决问题,只是解决了一个现象 因为有人说 https:// ...
- 初识zookeeper以及安装和集群部署
初识zookeeper以及安装和集群部署 一.Zookeeper单体版安装 在安装zookeeper之前要先安装jdk环境,具体在linux环境安装jdk1.8请参照linux笔记. ...
- 学习Python笔记---操作列表
1.for循环: 编写for循环时,对于用语存储列表中每个值的临时变量,可指定任何名称. 在for循环中,想包含多少行代码都可以,每个缩进的代码行都是循环的一部分,且将针对列表中的每个值都执行一次. ...
- The web application [ROOT] appears to have started a thread named [spring.cloud.inetutils] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
前景提要:启动SpringBoot项目报错 原因: DeliveryPointServiceFallBack上面没有加 @Component-_-!
- mysql连接出现Unknown system variable 'tx_isolation'异常
出现这个异常,是因为mysql-connector-java.jar的版本太低,数据库的版本太高,不匹配导致的. 因此将mysql-connector-java升级到最新版本就解决了问题. 最新的三个 ...
- CenOS SSH无密码登录
系统环境:CentOS6.8 软件环境:SSH(yum -y install openssh-clients) IP 地址:192.168.0.188 用户环境:root.xiaoming 系统 ...
- JavaScript-JQ初探实现自定义滚动条
这是一个基本实现思路,如果有新手和我一样没什么事,喜欢瞎研究话,可以参考下. 一.Html <div class="scroll_con"> <div class ...
- Ionic.Zip
1.Ionic.zIP 实现文件压缩和解压 2.压缩: /// <summary> /// 压缩文件 /// </summary> / ...