概述
毒瘤
Description
从前有一名毒瘤。
毒瘤最近发现了量产毒瘤题的奥秘。考虑如下类型的数据结构题:给出一个数组,要求支持若干种奇奇怪怪的修改操作(比如区间加一个数,或者区间开平方),并支持询问区间和。毒瘤考虑了 n n 个这样的修改操作,并编号为 ~ n n 。当毒瘤要出数据结构题的时候,他就将这些修改操作中选若干个出来,然后出成一道题。
当然了,这样出的题有可能不可做。通过精妙的数学推理,毒瘤揭露了这些修改操作的关系:有 m m 对“互相排斥”的修改操作,第 对是第 ui u i 个操作和第 vi v i 个操作。当一道题同时含有 ui u i 和 vi v i 这两个操作时,这道题就会变得不可做。另一方面,一道题中不包含任何“互相排斥”的修改操作时,这个题就是可做的。此外,毒瘤还发现了一个规律: m−n m − n 是一个很小的数字,且任意两个修改操作都是连通的。两个修改操作 a,b a , b 是连通的,当且仅当存在若干操作 t0,t1,⋯,tl t 0 , t 1 , ⋯ , t l ,使得 t0=a,tl=b t 0 = a , t l = b ,且对 1≤i≤l 1 ≤ i ≤ l , ti−1 t i − 1 和 ti t i 都是“互相排斥”的修改操作。
一堆“互相排斥”的修改操作称为互斥对。现在毒瘤想知道,给定值 n n 和 个互斥对,他共能出出多少道可做的不同的数据结构题。两道数据结构题是不同的,当且仅当有一个修改操作在其中一道题中存在,而在另一道题中不存在。
Input
第一行为正整数
n,m
n
,
m
。
接下来
m
m
行,每行两个正整数 ,代表一对“互相排斥”的修改操作。
Output
输出一行一个整数,代表毒瘤可以出的可做的不同的“互相排斥”的修改操作的个数。这个数可能很大,所以只输出模998244353后的值。
Sample Input
12 18
12 6
3 11
8 6
2 9
10 4
1 8
6 2
11 5
10 6
12 2
9 3
7 6
2 7
3 2
7 3
5 6
2 11
12 1
Sample Output
248
HINT
n≤105,n−1≤m≤n+10 n ≤ 10 5 , n − 1 ≤ m ≤ n + 10 。
考场上想到了一个可以A的做法却没调出来……
下考后花了2h重写结果调完编译错误后直接一遍过……
想想当时自己真是mdzz。
思路:
在考场上,作为蒟蒻,当然是不会想到出题人的kernelization做法的。(关于此方法请出门左转知乎)
更不会想到考场上唯一一个A了此题的dalao(orz 机房dalao nyg)的k-仙人图做法(毕竟当时dalao讲的时候就没太听懂)。
那就说说本蒟蒻当时想到却没调出来的做法。
首先,这是在一棵加了不超过 10 10 条非树边的树上求独立集方案数。
然后,
55
55
(然而实测是
60
60
)分做法就出来了:
10
10
条非树边最多影响
20
20
个点,那么
O(220)
O
(
2
20
)
枚举这
20
20
个点的选取状态,然后对于每个合法状态进行一次正常
dp
d
p
即可。复杂度
O(220n)
O
(
2
20
n
)
。
考虑优化这个做法。
可以发现,没有被枚举的点的状态很多情况下是相同的,重复计算貌似很浪费,考虑优化这些状态数。
于是考虑虚树。
建出这
20
20
个点的虚树,可以发现总点数是小于
40
40
的。
对于转移,观察暴力转移过程,可以得到如下的信息:
设
f[i][0/1]
f
[
i
]
[
0
/
1
]
代表节点
i
i
不选/选时,子树的总方案数。
那么若虚树上存在一条边时,可以发现,
f[u][0]
f
[
u
]
[
0
]
和
f[u][1]
f
[
u
]
[
1
]
从
v
v
处获得的贡献可以表示成的形式,其中
k0
k
0
和
k1
k
1
是两个系数。
由于每个转移的
k0
k
0
和
k1
k
1
不会根据选取方案改变,因此考虑对于每个节点
v
v
,预处理出它向虚树上父亲转移时的
k0
k
0
和
k1
k
1
。
对于一条虚树边
(u,v)
(
u
,
v
)
,将原树中
u
u
到的路径提出来,从
v
v
节点向上跳并暴力转移系数,若遇到分叉则额外转移上分叉处的系数。
由于虚树的性质可以知道分叉的子树内不存在关键点(否则分叉处会作为lca出现在虚树上),因此分叉处的系数可以暴力转移。
这样的预处理是的。
最后依旧
O(220)
O
(
2
20
)
爆枚选取状态即可。
设
k=m−n+1≤10
k
=
m
−
n
+
1
≤
10
,那么复杂度为
O(n+klogk+22∗k+1k)
O
(
n
+
k
log
k
+
2
2
∗
k
+
1
k
)
。
注意由于暴力判断一个状态是否合法是
O(k)
O
(
k
)
的,后半部分复杂度不是
O(3k)
O
(
3
k
)
而是
O(22∗k+1k)
O
(
2
2
∗
k
+
1
k
)
。
可以发现这个思路实际上跟出题人的kernelization的想法很相似……
或者说虚树这个方法本来就是kernelization方法的一个子集?
真™好写好调
upd:
此方法实测没有正解和加了神奇的优化后的k-仙人图快(正解用时是另外两个方法的一半)……
但是k-仙人图不卡常基本上比下面这份代码慢,而且下面这份代码没有卡常……
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
inline int read()
{
int x=0;char ch=getchar();
while(ch<'0' || '9'<ch)ch=getchar();
while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
return x;
}
inline int maxx(int a,int b){return a>b?a:b;}
inline int minn(int a,int b){return a<b?a:b;}
typedef long long ll;
typedef pair<int,int> pr;
#define v second
#define u first
const int md=998244353;
const int N=100009;
const int M=N+19;
const int K=23;
int eto[M<<1],enxt[M<<1],ew[M<<1],ebeg[N],etot;
int fa[N][K],id[N],ed[N],seg[N],dep[N],dfn;
int n,m,to[M<<1],nxt[M<<1],beg[N],tot;
int top,p[N<<1],ha[N],ptop,must[N],htop;
int tstk[N],f[N][2],rha[N],g[N][2],ttop;
bool ban[N];
pr stk[M<<1];
struct mov
{
int k0,k1;
mov(int _k0=0,int _k1=0){k0=_k0,k1=_k1;}
inline mov operator + (mov o)
{
return mov(k0+o.k0,k1+o.k1);
}
inline mov operator * (int k)
{
return mov((ll)k0*k%md,(ll)k1*k%md);
}
inline int calc(int a,int b)
{
return ((ll)k0*a%md+(ll)k1*b%md)%md;
}
}mo[N][2];
inline void adde(int u,int v)
{
eto[++etot]=v;
enxt[etot]=ebeg[u];
ew[etot]=1;
ebeg[u]=etot;
}
inline void add(int u,int v)
{
to[++tot]=v;
nxt[tot]=beg[u];
beg[u]=tot;
}
inline void dfs(int u)
{
seg[id[u]=++dfn]=u;
for(int i=ebeg[u];i;i=enxt[i])
if(eto[i]!=fa[u][0])
{
if(!dep[eto[i]])
{
dep[eto[i]]=dep[u]+1;
fa[eto[i]][0]=u;
dfs(eto[i]);
}
else
ew[i]=0,stk[++top]=pr(minn(u,eto[i]),maxx(u,eto[i]));
}
else ew[i]=0;
ed[u]=dfn;
}
inline bool cmp(int a,int b){return id[a]<id[b];}
inline int lca(int a,int b)
{
if(dep[a]>dep[b])swap(a,b);
for(int i=K-1;~i;i--)
if(dep[fa[b][i]]>=dep[a])
b=fa[b][i];
if(a==b)return a;
for(int i=K-1;~i;i--)
if(fa[a][i]!=fa[b][i])
a=fa[a][i],b=fa[b][i];
return fa[a][0];
}
inline void dfs_ban(int u,int banc)
{
f[u][0]=f[u][1]=1;
for(int i=ebeg[u];i;i=enxt[i])
if(eto[i]!=fa[u][0] && eto[i]!=banc && !ban[eto[i]])
{
dfs_ban(eto[i],banc);
f[u][0]=(ll)f[u][0]*((f[eto[i]][0]+f[eto[i]][1])%md)%md;
f[u][1]=(ll)f[u][1]*f[eto[i]][0]%md;
}
}
inline void get_mov(int s,int t)
{
mo[s][0]=mov(1,0);
mo[s][1]=mov(0,1);
mov tmp;
for(int i=s;fa[i][0]!=t;i=fa[i][0])
{
dfs_ban(fa[i][0],i);
ban[fa[i][0]]=1;tmp=mo[s][0];
mo[s][0]=(mo[s][0]+mo[s][1])*f[fa[i][0]][0];
mo[s][1]=tmp*f[fa[i][0]][1];
}
}
inline void dfs_pre(int u)
{
for(int i=beg[u];i;i=nxt[i])
{
dfs_pre(to[i]);
get_mov(to[i],u);
}
f[u][0]=f[u][1]=1;
for(int i=ebeg[u];i;i=enxt[i])
if(!ban[eto[i]] && eto[i]!=fa[u][0] && ew[i]>0)
{
dfs_ban(eto[i],0);
f[u][0]=(ll)f[u][0]*((f[eto[i]][0]+f[eto[i]][1])%md)%md;
f[u][1]=(ll)f[u][1]*f[eto[i]][0]%md;
}
}
inline void dfs_work(int u)
{
g[u][0]=f[u][0];g[u][1]=f[u][1];
for(int i=beg[u];i;i=nxt[i])
{
dfs_work(to[i]);
int k0=mo[to[i]][0].calc(g[to[i]][0],g[to[i]][1]);
int k1=mo[to[i]][1].calc(g[to[i]][0],g[to[i]][1]);
g[u][0]=(ll)g[u][0]*((k0+k1)%md)%md;
g[u][1]=(ll)g[u][1]*k0%md;
}
if(~must[u])
g[u][must[u]^1]=0;
}
int main()
{
n=read();m=read();
for(int i=1,u,v;i<=m;i++)
{
u=read();v=read();
adde(u,v);adde(v,u);
}
dfs(dep[1]=1);
for(int i=1;i<K;i++)
for(int j=1;j<=n;j++)
fa[j][i]=fa[fa[j][i-1]][i-1];
sort(stk+1,stk+top+1);
top=unique(stk+1,stk+top+1)-stk-1;
for(int i=1;i<=top;i++)
{
if(!ban[stk[i].u])
{
ban[stk[i].u]=1;
p[++ptop]=stk[i].u;
rha[ha[++htop]=stk[i].u]=htop;
}
if(!ban[stk[i].v])
{
ban[stk[i].v]=1;
p[++ptop]=stk[i].v;
rha[ha[++htop]=stk[i].v]=htop;
}
}
sort(p+1,p+ptop+1,cmp);
for(int i=2,e=ptop;i<=e;i++)
p[++ptop]=lca(p[i-1],p[i]);
p[++ptop]=1;
sort(p+1,p+ptop+1,cmp);
ptop=unique(p+1,p+ptop+1)-p-1;
tstk[++ttop]=p[1];
for(int i=2;i<=ptop;i++)
if(ttop)
{
while(ttop && ed[tstk[ttop]]<id[p[i]])
ttop--;
add(tstk[ttop],p[i]);
tstk[++ttop]=p[i];
}
for(int i=1;i<=ptop;i++)
ban[p[i]]=1;
dfs_pre(1);
for(int i=1;i<=n;i++)
must[i]=-1;
int ans=0;
for(int i=(1<<htop)-1;i>=0;i--)
{
for(int j=1;j<=top;j++)
if((i>>(rha[stk[j].u]-1)&1) && (i>>(rha[stk[j].v]-1)&1))
goto hell;
for(int j=1;j<=htop;j++)
must[ha[j]]=(i>>(j-1))&1;
dfs_work(1);
(ans+=(g[1][0]+g[1][1])%md)%=md;
hell:;
}
printf("%dn",ans);
return 0;
}
最后
以上就是文艺电源为你收集整理的[BZOJ5287][HNOI2018]毒瘤-虚树-动态规划毒瘤的全部内容,希望文章能够帮你解决[BZOJ5287][HNOI2018]毒瘤-虚树-动态规划毒瘤所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复