0708模拟赛总结
写在前面:单刀赴会,我不说为什么……
T1.红黑树
树形DP+构造
现在是一看到构造就脑袋疼,所以考试的时候就跳了,记得是先开的T3
我们设$f[x][v][red/black]$表示以$x$为根的子树黑点数为$v$,且$x$号点为红色/黑色是否可行。
转移显然,注意在转移过程中同时记录一下每个状态是从哪里转移过来的即可实现构造,时间复杂度为$O(n*\max v)$,所以实际的复杂度就是$O(n\log n)$的点击查看代码
#include<bits/stdc++.h>
#define N 1505
using namespace std;
int n,root,dep=1e9;
vector<int>G[N];
bool f[N][2][N],ans[N][2];
int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
x=x*10+ch-'0',ch=getchar();
return x*f;
}
// 0 red 1 black
void DFS1(int u,int fa,int d)
{
if(G[u].size()==1)
{
dep=min(dep,d);
return;
}
for(auto v:G[u])
{
if(v==fa)
continue;
DFS1(v,u,d+1);
}
}
void DFS(int u,int fa)
{
if(G[u].size()==1)
{
f[u][1][1]=1;
return;
}
if(G[u].size()==2 and u!=root)
{
f[u][0][1]=1;
f[u][1][1]=0;
f[u][0][2]=0;
f[u][1][2]=1;
return;
}
for(int k=1;k<=dep;k++)
{
f[u][0][k]=f[u][1][k]=1;
}
for(auto v:G[u])
{
if(v==fa)
continue;
DFS(v,u);
for(int k=1;k<=dep;k++)
{
f[u][0][k]&=f[v][1][k];
f[u][1][k]&=f[v][1][k-1]|f[v][0][k-1];
}
}
}
int cnt,col[505];
void DFS2(int u,int fa,int sum)
{
if(G[u].size()==1)
{
return;
}
if(u!=root)
{
if(f[u][0][cnt-sum] and col[fa]!=0)
col[u]=0;
else
if(f[u][1][cnt-sum])
col[u]=1;
}
if(G[u].size()==2 and u!=root)
{
return;
}
for(auto v:G[u])
{
if(v==fa)
continue;
DFS2(v,u,sum+col[u]);
}
}
int tot;
int main()
{
freopen("rbt.in","r",stdin);
freopen("rbt.out","w",stdout);
n=read();
for(int i=1,x;i<=n;i++)
{
x=read();
if(x)
{
G[i].push_back(x);
G[x].push_back(i);
}
else
{
root=i;
}
}
tot=n;
for(int i=1;i<=n;i++)
{
if(i==root)
{
if(G[i].size()<2)
{
G[i].push_back(++tot);
G[tot].push_back(i);
}
continue;
}
for(int j=1;j<=3-G[i].size();j++)
{
G[i].push_back(++tot);
G[tot].push_back(i);
}
}
DFS1(root,0,1);
DFS(root,0);
int flg=-1;
for(int i=1;i<=dep;i++)
{
if(f[root][1][i])
flg=1;
if(f[root][0][i])
flg=0;
if(flg!=-1)
{
cnt=i;
col[root]=flg;
break;
}
}
if(flg!=-1)
{
DFS2(root,0,0);
for(int i=1;i<=n;i++)
{
if(col[i])
cout<<'B';
else
cout<<'R';
}
}
else
cout<<"Impossible";
return 0;
}
T2.异或
卢卡斯定理,高维前缀和,子集枚举,状压DP
也就是一个会的,一个新学的,一个没想到的,和一个不会的
所以没做qwq
首先肯定是要考虑每个点在经过 \(t\) 的时间后对根结点的贡献是多少(即如果把异或看成加,那么最终答案中这个点的权值要被加多少次)。
这个贡献系数只跟深度有关。设根结点的深度为 \(0\) ,则一个深度为 \(d\) 的节点在时刻 \(t\) 对答案的贡献系数就是 \(\binom{t+d}{d}\) 。 由于本题是异或运算,所以一个点对答案有贡献当且仅当其贡献系数为奇数。而根据 Lucas 定理的推论, \(\binom{n}{m}\equiv 1(\mod2)\)等价于在二进制中\(m\subseteq n\)。所以一个深度为\(d\)的点在时刻\(t\)有贡献当且仅当在二进制中\(d\subseteq t+d\),也就是\(d\cap t=\emptyset\)。
那么这题的做法就很显然了,由于贡献只跟深度有关,于是可以先求出每个深度\(d\)的异或和\(f_d\)。对于每次询问\(t\),只需要求出\(t\)的补集 的子集和,使用高维前缀和预处理就行。这里注意全集只需要取最小满足\(2^k>n\)对应的\(2^k-1\),因为更高的二进制位不影响答案。复杂度就是\(O(n\log n)\)
T3.临别赠礼
CDQ分治和数据结构
想出来了,但是RE了,悲伤(还是CDQ这样超前的东西看的太少了,有点不适应)
对于每个位置\(i\)记录上一个和\(a_i\)相同的位置\(lst_i\)。那么对于一次询问,就是\(\sum_{i=l}^{r}(i-lst_i)[lst_i\geq l]\)。
还有\(i<l\)的时候有\(lst_i<l\),所以答案就是\(\sum_{i=1}^{r}(i-lst_i)[lst_i\geq l]\)。
如果不存在修改操作,那么这题就是一个经典的二维数点问题。将每个点按照\(lst_i\)从小到大依次插入,同时对询问也按照\(l\)排序依次询问,使用树状数组维护即可。在存在修改操作的情况下,由于这个答案是可加的,于是可以转成三维偏序问题用 CDQ 分治来解决。
对于每个点,可以看成有三个属性:时间\(t\),位置\(pos\),\(lst\)。
对于初始存在在数组中的\(a_i\)可以看成是权值为\(i-lst_i\)的点\((0,i,lst_i)\),对于时刻\(t\)的询问\((l,r)\)就可以看成\((t,r,l)\)。
是对于每次修改操作,我们加入一个权值为之前的\(lst_i-i\)的点表示删除,再加入一个新的点就能计算贡献。每次修改操作只会影响到两个位置的\(lst_i\),于是这题就被转化成了三维偏序,可以用 CDQ 分治或其他高妙数据结构解决。
复杂度是\(O(n\log^2 n)\)。
T4.数图
容斥+组合
推式子,按照式子分类+模拟