2021牛客多校 第四场

比赛链接:

戳这里

E-Tree Xor:

设节点1为根,dfs一下,那么可以对每个节点,求出\(w[1]{\oplus}w[i]\)的值,记为\(val[i]\),然后又有\(l_i<=w[i]<=r_i\),这样我们可以推出n-1个长成这样的式子:

\[l_i<=val[i]{\oplus}w[1]<=r_i \]

也就是

\[w[1]{\in}([l_i,r_i]{\oplus}val[i]) \]

接下来的思路不难想:对这n-1个区间求交,交集部分的值就是合法的\(w[i]\),再结合\(w[1]\)本身的范围就可以算出答案了

然而我们发现一个很蛋疼的事情:你对一段连续区间进行异或后,结果并不一定还是一段连续区间,可能会分裂成好几个不交的区间

比如:\([8,10]{\oplus}2\),结果是\([8,8]\cup[10,11]\)

那么何种情形下才不会分裂呢?

考虑这样的一个区间\([L,R]\),其中\(L\)的二进制形式为一个任意的前缀加一段连续的后缀0,而\(R\)具有与\(L\)相同的前缀加一段连续的后缀1(比如\(L\)\([1011,0000]\)\(R\)\([1011,1111]\)这种)

显然当一个数异或这样一个区间后,得到的新区间仍然是连续的,因为\([L,R]\)中的每个数前缀部分相同,异或后肯定仍然相同,而后缀部分异或后也仍然唯一地分布在与原后缀相同的区间内

这启示我们,可以把每个\([l_i,r_i]\)拆分成一些满足上述形式的子区间,每个子区间异或后的结果仍然连续

我们可以借助一个类似01-trie的思路完成这个操作

\(l_i,r_i\)两个数加入01-trie上跑,如果朝着trie上的某条边前进,可以获得一个\(l_i<prefix<r_i\)的前缀,那就找到了一个符合条件的子区间了,那就直接选这个前缀做异或,不需要继续往下走了。反之,如果这个前缀是等于\(l_i\)\(r_i\)对应位置的前缀的,那继续往下走,直到找到符合条件的前缀为止

容易发现这样的子区间最多有\(log\)个,复杂度\(O(nlogw)\)

#include<bits/stdc++.h>
using namespace std;
struct front_star{
    int to,next,w;
}e[200005];
struct rg{
    int pos,p;
};
int cnt=0,n,ans=0;
int head[100005],l[100005],r[100005],val[100005],btl[35],btr[35],btx[35];
vector<rg>x;
bool cmp(rg a,rg b)
{
    if(a.pos<b.pos)
        return true;
    if(a.pos==b.pos)
        return a.p>b.p;
    return false;
}
void addedge(int u,int v,int wv)
{
    cnt++;
    e[cnt].to=v;
    e[cnt].w=wv;
    e[cnt].next=head[u];
    head[u]=cnt;
}
void dfs(int u,int fa)
{
    for(int i=head[u];~i;i=e[i].next)
    {
        int v=e[i].to;
        if(v!=fa)
        {
            val[v]=val[u]^e[i].w;
            dfs(v,u);
        }
    }
}
void divide(int nump,int lsum,int rsum,int now,int vxor)
{
    if(nump==0)
    {
        rg lt,rt;
        lt.pos=now^vxor;
        rt.pos=now^vxor;
        lt.p=1;
        rt.p=-1;
        x.push_back(lt);
        x.push_back(rt);
        return;
    }
    lsum+=(btl[nump]<<(nump-1));
    rsum+=(btr[nump]<<(nump-1));
    vxor+=(btx[nump]<<(nump-1));
    int vo=now+(1<<(nump-1)),vz=now;
    if(lsum<vo&&vo<rsum)
    {
        rg lt,rt;
        lt.pos=vo^vxor;
        rt.pos=vo^vxor+(1<<(nump-1))-1;
        lt.p=1;
        rt.p=-1;
        x.push_back(lt);
        x.push_back(rt);
    }
    if(lsum<vz&&vz<rsum)
    {
        rg lt,rt;
        lt.pos=vz^vxor;
        rt.pos=vz^vxor+(1<<(nump-1))-1;
        lt.p=1;
        rt.p=-1;
        x.push_back(lt);
        x.push_back(rt);
    }
    if(lsum==vo||vo==rsum)
        divide(nump-1,lsum,rsum,vo,vxor);
    if(lsum==vz||vz==rsum)
        divide(nump-1,lsum,rsum,vz,vxor);
}
int main()
{
    memset(head,-1,sizeof(head));
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&l[i],&r[i]);
    for(int i=1;i<=n-1;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        addedge(a,b,c);
        addedge(b,a,c);
    }
    val[1]=0;
    dfs(1,0);
    for(int i=2;i<=n;i++)
    {
        memset(btl,0,sizeof(btl));
        memset(btr,0,sizeof(btr));
        memset(btx,0,sizeof(btx));
        for(int j=30;j>=1;j--)
        {
            btl[j]=(l[i]&(1<<(j-1)))>>(j-1);
            btr[j]=(r[i]&(1<<(j-1)))>>(j-1);
            btx[j]=(val[i]&(1<<(j-1)))>>(j-1);
        }
        divide(30,0,0,0,0);
        //printf("elichika\n");
    }
    int tot=0,st,ed;
    sort(x.begin(),x.end(),cmp);
    for(int i=0;i<x.size();i++)
    {
        if(x[i].p==1)
        {
            tot++;
            if(tot==n-1)
                st=max(l[1],x[i].pos);
        }
        if(x[i].p==-1)
        {
            if(tot==n-1)
            {
                ed=min(r[1],x[i].pos);
                if(st<=ed)
                    ans+=ed-st+1;
            }
            tot--;
        }
    }
    printf("%d",ans);
    return 0;
}

D-Rebuild Tree

删k条边后得到k+1个连通块,然后就是加k条边让这k+1个联通块重新联通

如果你会prufer序列,那么这玩意儿就是一个很裸的求图联通方案数

答案就是

\[\sum{n^{k-1}\prod_{i=1}^{k+1}s_i} \]

\(s_i\)是第\(i\)个联通块的点数

\(n^{k-1}\)提出来,关键就是怎么求后面那坨式子

裸求不好求,考虑其意义等价于删k条边后,再在每个连通块中选一个点,求总方案数

\(dp[i][j][0/1]\)表示节点\(i\)的子树中删了\(j\)条边,且\(i\)所在的联通块中是否已选择点的方案数

然后就是个树上背包了

#include<bits/stdc++.h>
using namespace std;
const long long M=998244353;
struct front_star{
    int to,next;
}e[100005];
int n,k,cnt=0;
int head[50005],sz[50005];
long long dp[50005][105][2],f[105][2];
long long fpow(long long base,long long power)
{
    long long result=1;
    while(power>0)
    {
        if(power&1)
            result=result*base%M;
        power>>=1;
        base=(base*base)%M;
    }
    return result;
}
void addedge(int u,int v)
{
    cnt++;
    e[cnt].to=v;
    e[cnt].next=head[u];
    head[u]=cnt;
}
void dfs(int u,int fa)
{
    sz[u]=1;
    dp[u][0][1]=1;
    dp[u][0][0]=1;
    for(int now=head[u];~now;now=e[now].next)
    {
        int v=e[now].to;
        if(v!=fa)
        {
            dfs(v,u);
            memset(f,0,sizeof(f));
            for(int i=0;i<=min(sz[u]-1,k);i++)
                for(int j=0;j<=min(sz[v]-1,k-i);j++)
                {
                    f[i+j][0]=(f[i+j][0]%M+dp[u][i][0]*dp[v][j][0]%M)%M;
                    f[i+j][1]=((f[i+j][1]%M+dp[u][i][0]*dp[v][j][1]%M)%M+dp[u][i][1]*dp[v][j][0]%M)%M;
                    if(i+j+1<=k)
                    {
                        f[i+j+1][0]=(f[i+j+1][0]%M+dp[u][i][0]*dp[v][j][1]%M)%M;
                        f[i+j+1][1]=(f[i+j+1][1]%M+dp[u][i][1]*dp[v][j][1]%M)%M;
                    }
                }
            sz[u]+=sz[v];
            for(int i=0;i<=k;i++)
            {
                dp[u][i][0]=f[i][0];
                dp[u][i][1]=f[i][1];
            }
        }
    }
}
int main()
{
    scanf("%d%d",&n,&k);
    memset(dp,0,sizeof(dp));
    memset(head,-1,sizeof(head));
    for(int i=1;i<=n-1;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        addedge(a,b);
        addedge(b,a);
    }
    dfs(1,0);
    printf("%lld",(fpow((long long)n,(long long)k-1ll)%M*dp[1][k][1]%M)%M);
    return 0;
}
posted @ 2021-09-21 12:01  nanjoln0  阅读(33)  评论(0编辑  收藏  举报