[BZOJ5287][HNOI2018]毒瘤-虚树-动态规划

毒瘤

Description

从前有一名毒瘤。

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

当然了,这样出的题有可能不可做。通过精妙的数学推理,毒瘤揭露了这些修改操作的关系:有 m 对“互相排斥”的修改操作,第 i 对是第 u i 个操作和第 v i 个操作。当一道题同时含有 u i v i 这两个操作时,这道题就会变得不可做。另一方面,一道题中不包含任何“互相排斥”的修改操作时,这个题就是可做的。此外,毒瘤还发现了一个规律: m n 是一个很小的数字,且任意两个修改操作都是连通的。两个修改操作 a , b 是连通的,当且仅当存在若干操作 t 0 , t 1 , , t l ,使得 t 0 = a , t l = b ,且对 1 i l t i 1 t i 都是“互相排斥”的修改操作。

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

Input

第一行为正整数 n , m
接下来 m 行,每行两个正整数 u , v ,代表一对“互相排斥”的修改操作。

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 10 5 , n 1 m n + 10


考场上想到了一个可以A的做法却没调出来……
下考后花了2h重写结果调完编译错误后直接一遍过……
想想当时自己真是mdzz。


思路:
在考场上,作为蒟蒻,当然是不会想到出题人的kernelization做法的。(关于此方法请出门左转知乎)
更不会想到考场上唯一一个A了此题的dalao(orz 机房dalao nyg)的k-仙人图做法(毕竟当时dalao讲的时候就没太听懂)。

扫描二维码关注公众号,回复: 1112204 查看本文章

那就说说本蒟蒻当时想到却没调出来的做法。

首先,这是在一棵加了不超过 10 非树边的树上求独立集方案数。

然后, 55 (然而实测是 60 )分做法就出来了:
10 条非树边最多影响 20 个点,那么 O ( 2 20 ) 枚举这 20 个点的选取状态,然后对于每个合法状态进行一次正常 d p 即可。复杂度 O ( 2 20 n )

考虑优化这个做法。
可以发现,没有被枚举的点的状态很多情况下是相同的,重复计算貌似很浪费,考虑优化这些状态数。

于是考虑虚树。
建出这 20 个点的虚树,可以发现总点数是小于 40 的。

对于转移,观察暴力转移过程,可以得到如下的信息:
f [ i ] [ 0 / 1 ] 代表节点 i 不选/选时,子树的总方案数。
那么若虚树上存在一条边 ( u , v ) 时,可以发现, f [ u ] [ 0 ] f [ u ] [ 1 ] v 处获得的贡献可以表示成 k 0 f [ v ] [ 0 ] + k 1 f [ v ] [ 1 ] 的形式,其中 k 0 k 1 是两个系数。

由于每个转移的 k 0 k 1 不会根据选取方案改变,因此考虑对于每个节点 v ,预处理出它向虚树上父亲 u 转移时的 k 0 k 1
对于一条虚树边 ( u , v ) ,将原树中 u v 的路径提出来,从 v 节点向上跳并暴力转移系数,若遇到分叉则额外转移上分叉处的系数。
由于虚树的性质可以知道分叉的子树内不存在关键点(否则分叉处会作为lca出现在虚树上),因此分叉处的系数可以暴力转移。
这样的预处理是 O ( n ) 的。

最后依旧 O ( 2 20 ) 爆枚选取状态即可。
k = m n + 1 10 ,那么复杂度为 O ( n + k log k + 2 2 k + 1 k )
注意由于暴力判断一个状态是否合法是 O ( k ) 的,后半部分复杂度不是 O ( 3 k ) 而是 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("%d\n",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zlttttt/article/details/79860509