2021牛客多校 第四场
比赛链接:
E-Tree Xor:
设节点1为根,dfs一下,那么可以对每个节点,求出\(w[1]{\oplus}w[i]\)的值,记为\(val[i]\),然后又有\(l_i<=w[i]<=r_i\),这样我们可以推出n-1个长成这样的式子:
也就是
接下来的思路不难想:对这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序列,那么这玩意儿就是一个很裸的求图联通方案数
答案就是
\(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;
}