BZOJ3522. [Poi2014]Hotel
给一棵树,问两两之间距离相等的点对有多少对。
点数为5000。
树形dp+统计满足条件的三元组(用差分来统计,即建立两个数组,表示一元组数目、二元组数目)
枚举根节点,对每个子树跑一边dfs统计距离为x的点数。
时间复杂度
O
(
n
2
)
O(n^2)
O(n2)
//
// Created by artist on 2021/8/5.
//
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
//-----------------------------------------------------------------IO Template
namespace StandardIO {
template<typename T>
inline void read(T &x) {
x = 0;
T f = 1;
char c = getchar();
for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = -1;
for (; c >= '0' && c <= '9'; c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template<typename T>
inline void write(T x) {
if (x < 0) putchar('-'), x *= -1;
if (x >= 10) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace StandardIO;
//-----------------------------------------------------------------IO Template
const int maxn = 5004;
vector<int> G[maxn];
ll ans;
int dp[maxn];
ll t1[maxn],t2[maxn];
void dfs(int u,int fa,int dep){
dp[dep]++;
for(int v:G[u]){
if(v==fa) continue;
dfs(v,u,dep+1);
}
}
signed main() {
int n;read(n);
for(int i=1,u,v;i<=n-1;++i) {
read(u),read(v);
G[u].push_back(v);
G[v].push_back(u);
}
for(int i=1;i<=n;++i){
// 枚举中转点
memset(t1,0,sizeof(t1));
memset(t2,0,sizeof(t2));
for(int v:G[i]){
dfs(v,i,1);
for(int j=1;j<=n;++j){
ans += t2[j]*dp[j];
t2[j] += t1[j]*dp[j];
t1[j] += dp[j];
dp[j] = 0;
}
}
}
write(ans);
}
BZOJ4543. [POI2014]Hotel加强版
数据改成了1e5,
O
(
n
2
)
O(n^2)
O(n2)不可过。
首先考虑将做法改成换根dp。
设
f
[
i
]
[
j
]
f[i][j]
f[i][j] 表示
i
i
i 的子树中,与
i
i
i 距离为
j
j
j 的点数。
设
g
[
i
]
[
j
]
g[i][j]
g[i][j] 表示
i
i
i 的子树中,有多少个二元点对,满足:再取一个在
i
i
i 的子树外与
i
i
i 距离为
j
j
j 的点即形成三元组的数目。(跟上解的差分思想类似(据说这叫dsu on tree?))
从每棵子树向上贡献。
对每个
u
u
u ,考虑每个儿子对其的答案贡献:
a
n
s
+
=
∑
v
∈
s
o
n
(
u
)
g
[
v
]
[
i
+
1
]
∗
f
[
u
]
[
i
]
+
f
[
v
]
[
i
−
1
]
∗
g
[
u
]
[
i
]
ans += \sum_{v\in son(u)}g[v][i+1]*f[u][i]+f[v][i-1]*g[u][i]
ans+=∑v∈son(u)g[v][i+1]∗f[u][i]+f[v][i−1]∗g[u][i]
意义为:在该儿子中找两个点与该儿子外找1个点+在该儿子中找1个点与该儿子外找两个点。
考虑更新:(dsu on tree)
g
[
u
]
[
i
]
+
=
f
[
v
]
[
i
−
1
]
∗
f
[
u
]
[
i
]
g[u][i]+=f[v][i-1]*f[u][i]
g[u][i]+=f[v][i−1]∗f[u][i]
f
[
u
]
[
i
]
+
=
f
[
v
]
[
i
−
1
]
f[u][i]+=f[v][i-1]
f[u][i]+=f[v][i−1]
g
[
u
]
[
i
]
+
=
g
[
v
]
[
i
+
1
]
g[u][i]+=g[v][i+1]
g[u][i]+=g[v][i+1]
显然因为我们要枚举长度,这个做法看起来还是
O
(
n
2
)
O(n^2)
O(n2)的。
但是考虑进行长链剖分,我们第一次更新时可以更新重子节点,这个步骤利用指针数组进行
O
(
1
)
O(1)
O(1)继承重子节点的
g
g
g和
f
f
f,然后再暴力合并其他的轻子节点。
考虑复杂度:重链都是
O
(
1
)
O(1)
O(1)继承,每条轻链只被暴力一次。总共时间复杂度:
O
(
n
)
O(n)
O(n)。
长链剖分:
应用:
- O(n)统计每个点子树中可合并的以深度为下标的信息
- 经过一些预处理,单次O(1)在线查询一个点的k级祖先
//
// Created by artist on 2021/8/5.
//
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
//-----------------------------------------------------------------IO Template
namespace StandardIO {
template<typename T>
inline void read(T &x) {
x = 0;
T f = 1;
char c = getchar();
for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = -1;
for (; c >= '0' && c <= '9'; c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template<typename T>
inline void write(T x) {
if (x < 0) putchar('-'), x *= -1;
if (x >= 10) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace StandardIO;
//-----------------------------------------------------------------IO Template
const int maxn = 100005;
vector<int> G[maxn];
ll ans;
ll mem[maxn*10];
ll *f[maxn],*g[maxn]; // dp数组(指针数组)
ll *now=mem+maxn; // 内存位置
int n;
int hei[maxn],sn[maxn];
void pf(int u,int fa){
for(int v:G[u]){
if(v==fa) continue;
pf(v,u);
if(hei[v]>hei[sn[u]]) sn[u]=v;
}
hei[u] = hei[sn[u]] + 1;
}
// 新建节点,赋予内存(每一条长链公用一个内存)
void newnode(int u){
f[u]=now; now = now+2*hei[u]+1;
g[u]=now; now = now+2*hei[u]+1;
}
// 按照dfn枚举
void solve(int u,int fa){
f[u][0] = 1;
if(sn[u]) {
f[sn[u]] = f[u] + 1;
g[sn[u]] = g[u] - 1;
solve(sn[u],u); // 加上了长链的贡献
ans += g[sn[u]][1]; // 加上父亲给儿子的三元组贡献
}
for(int v:G[u]){
if(v==fa||v==sn[u]) continue;
newnode(v);
solve(v,u);
for(int i=hei[v];~i;--i){ // 更新
if(i) ans += f[u][i-1]*g[v][i];
ans += g[u][i+1]*f[v][i];
g[u][i+1] += f[u][i+1]*f[v][i];
}
for(int i=hei[v];~i;--i){
f[u][i+1] += f[v][i];
if(i) g[u][i-1] += g[v][i];
}
}
}
signed main() {
read(n);
for(int i=1,u,v;i<=n-1;++i) {
read(u),read(v);
G[u].push_back(v);
G[v].push_back(u);
}
pf(1,0); // 树剖
newnode(1);
solve(1,0);
write(ans);
}
洛谷3233 [HNOI2014]世界树
细节繁琐的虚树+树形dp。
题意:一颗3e5的树,3e5轮,每一轮给出m个关键点(m的总和不超过3e5),求距离每个关键点最近的点的个数。
题给数据和形式一眼就能看出是虚树,但是dp很难弄,细节很多。
参考题解。
首先对原树进行基本处理,把子树大小、lca、深度、dfn序整出来。(1)
然后用dfn序建虚树。
首先处理出虚树上的点的最近关键点是谁,距离为多少。
这需要一次自下而上的dfs处理每个虚树点的子树中最近关键点,又需要一次自上而下的dfs算进去子树外的最近关键点进行比较。(21和22)
然后我们统计虚树上没有的点对关键点的贡献。
首先,我们贡献掉那些位于叶子的点。虚树上一个点u,若其有一个子树,都没有虚树点,那么这个子树的最近关键点肯定跟u共用关键点。于是我们考虑对每个u,都先贡献给其关键点sz[u]。然后遍历u的子树,利用lca找到该子树在原树中u的儿子,其关键点的答案减去这个儿子为根的子树的sz。(3)
现在我们考虑贡献掉那些位于虚树边上的点。这些点在原树中,却不在虚树中。对于一个u,若其儿子(虚树上的)v和u所属关键点不同。那么(u,v)上一定存在一个深度,位于这个深度的点的子树(直到v)都属于v的关键点,(u,v)上其他节点就属于u的关键点。那么我们先算出这个深度,然后使用倍增到达那个点。再进行贡献。(4)
//
// Created by Artist on 2021/8/5.
//
#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e5+5;
#define pii pair<int,int>
#define mkp make_pair
#define dis first
#define ver second
#define pb push_back
#define DB(args...) do {cout<<#args<<" : ";dbg(args);}while(0)
void dbg() {std::cout<<" #\n";}
template<typename T, typename...Args>
void dbg(T a,Args...args) {std::cout<<a<<" ";dbg(args...);}
vector<int> G[maxn]; // ori
vector<int> g[maxn]; // vir
const int LOG = __lg(maxn)+1;
int f[maxn][LOG+3],dep[maxn],dfn[maxn],up[maxn],crt[maxn],sz[maxn]; // parent & depth
int n,m,rt,cnt,top;
int ans[maxn];
int st[maxn]; // 要手写栈
pii pt[maxn]; // 距离最近的距离及 critical point编号
void dfs1(int u){
sz[u]=1;
dfn[u]=++cnt;
for(int v:G[u]) if(v!=f[u][0]) {
dep[v]=dep[u]+1;
f[v][0]=u;
dfs1(v);
sz[u]+=sz[v];
}
}
void dfs21(int u){
if(crt[u]) pt[u]=mkp(0,u);
else pt[u]=mkp(1000000,0);
for(int v:g[u]){
dfs21(v);
pii tmp=mkp(pt[v].dis+dep[v]-dep[u],pt[v].ver);
pt[u]=min(pt[u],tmp);
}
}
void dfs22(int u){
for(int v:g[u]) {
if(!crt[v]) pt[v]=min(pt[v],mkp(pt[u].dis+dep[v]-dep[u],pt[u].ver));
dfs22(v);
}
}
void dfs3(int u){
ans[pt[u].ver] += sz[u];
for(int v:g[u]){
int x=v;
for(int j=LOG;~j;--j) if(f[x][j] && dep[f[x][j]]>dep[u]) x=f[x][j];
ans[pt[u].ver] -= sz[up[v]=x];
dfs3(v);
}
}
void dfs4(int u){
for(int v:g[u]){
if (pt[u].ver==pt[v].ver) ans[pt[u].ver] += sz[up[v]] - sz[v];
else {
// 倍增
int x=up[v],y=v,h;
h = dep[pt[v].ver] + dep[u] - pt[u].dis;
h=h&1?h+1>>1:(pt[v].ver<pt[u].ver?h>>1:(h>>1)+1);
for(int j=LOG;~j;--j) if(f[y][j] && dep[f[y][j]] >= h) y=f[y][j];
ans[pt[v].ver] += sz[y] - sz[v];
ans[pt[u].ver] += sz[x] - sz[y];
}
dfs4(v);
}
}
void dfs5(int u){
crt[u]=0;
ans[u]=0;
for(int v:g[u]){
dfs5(v);
}
g[u].clear();
}
int lca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(int j=LOG;~j;--j)
if(dep[f[x][j]]>=dep[y] && f[x][j]) x=f[x][j];
if(x==y) return x;
for(int j=LOG;~j;--j)
if(f[x][j]!=f[y][j] && f[x][j])
x=f[x][j],y=f[y][j];
return f[x][0];
}
int a[maxn],ori[maxn];
bool cmp(int x,int y){
return dfn[x]<dfn[y];
}
int main(){
scanf("%d",&n);
for(int i=1,x,y;i<=n-1;++i){
scanf("%d%d",&x,&y);
G[x].pb(y),G[y].pb(x);
}
// 预处理
dfs1(1);
// lca
for(int j=1;j<=LOG;++j) for(int i=1;i<=n;++i) f[i][j]=f[f[i][j-1]][j-1];
int q;scanf("%d",&q);
while(q--){
scanf("%d",&m);
for(int i=1;i<=m;++i) {
scanf("%d",&a[i]);
ori[i]=a[i];
crt[a[i]]=1; // is critical point
}
// virtual tree
sort(a+1,a+1+m,cmp);
top = 0;
st[++top]=a[1];
for(int i=2;i<=m;++i){
int x=a[i],y=lca(x,st[top]);
while(top>1 && dfn[y] <= dfn[st[top-1]])
g[st[top-1]].pb(st[top]),top--;
if(st[top]!=y) g[y].pb(st[top]),st[top]=y;
st[++top] = x;
}
while(top>1) g[st[top-1]].pb(st[top]),top--;
rt=st[1];
// 求出每个虚树节点靠得最近的"关键节点"
dfs21(rt);
dfs22(rt);
// 贡献上没有关键节点的子树
dfs3(rt);
// 贡献上道路中间的点
dfs4(rt);
ans[pt[rt].ver] += sz[1] - sz[rt];
for(int i=1;i<=m;++i)
printf("%d%c",ans[ori[i]],i==m?'\n':' ');
// 清空
dfs5(rt);
}
}
2021牛客多校第八场 D-OR
一个性质:
a
+
b
−
a
∧
b
=
a
∨
b
a+b - a\wedge b=a\vee b
a+b−a∧b=a∨b。
bitmask计数
//
// Created by artist on 2021/8/9.
//
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
//-----------------------------------------------------------------IO Template
namespace StandardIO {
template<typename T>
inline void read(T &x) {
x = 0;
T f = 1;
char c = getchar();
for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = -1;
for (; c >= '0' && c <= '9'; c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template<typename T>
inline void write(T x) {
if (x < 0) putchar('-'), x *= -1;
if (x >= 10) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace StandardIO;
//-----------------------------------------------------------------IO Template
const int maxn = 1e5+4;
int b[maxn],c[maxn],num[maxn]; // 暂时存
int cnt,lst;
// 上一位的可能情况数,以及上一位的可能情况数如果是1的话,是谁
signed main() {
int n;read(n);
for(int i=2;i<=n;++i){
read(b[i]);
}
for(int i=2;i<=n;++i){
read(c[i]);
c[i] = c[i]-b[i];
}
ll ans = 1;
for(int i=0;i<31;++i){
int flg=2;
num[1]=1;
for(int j=2;j<=n;++j){
int cc = (c[j]>>i)&1;
int bb = (b[j]>>i)&1;
if(cc==1){
num[j]=1;
}else{
num[j]=bb==1?1-num[j-1]:0;
}
if((num[j-1]|num[j])!=bb||(num[j-1]&num[j])!=cc) {
flg--;
break;
}
}
num[1]=0;
for(int j=2;j<=n;++j){
int cc = (c[j]>>i)&1;
int bb = (b[j]>>i)&1;
if(cc==1){
num[j]=1;
}else{
num[j]=bb==1?1-num[j-1]:0;
}
if((num[j-1]|num[j])!=bb||(num[j-1]&num[j])!=cc) {
flg--;
break;
}
}
ans *= flg;
if(ans==0) break;
}
write(ans);
}
2021牛客多校第八场 F-Robots
离线处理+bitset优化
//
// Created by artist on 2021/8/9.
//
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pb push_back
//-----------------------------------------------------------------IO Template
namespace StandardIO {
template<typename T>
inline void read(T &x) {
x = 0;
T f = 1;
char c = getchar();
for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = -1;
for (; c >= '0' && c <= '9'; c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template<typename T>
inline void write(T x) {
if (x < 0) putchar('-'), x *= -1;
if (x >= 10) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace StandardIO;
//-----------------------------------------------------------------IO Template
const int maxn = 503;
const int maxq = 5e5+5;
char mp[maxn][maxn];
struct node{int x,y,id,opt;};
vector<node> rbt[maxn][maxn];
bitset<maxq> ans;
bitset<260004> bs[maxn]; // 每个y坐标,给一个可达图,开了250003过不了,不知道为什么
signed main() {
int n,m;read(n),read(m);
for(int i=1;i<=n;++i){
scanf("%s",mp[i]+1);
}
int q;read(q);
for(int i=1,t,x1,y1,x2,y2;i<=q;++i){
read(t),read(x1),read(y1),read(x2),read(y2);
rbt[x1][y1].pb(node{x2,y2,i,t});
}
for(int i=n;i;--i){
for(int j=m;j;--j){
if (mp[i][j]=='0'){
bs[j]|=bs[j+1]; // 继承右边的。继承下面这个步骤已经自动处理了(有点类似滚动数组),因为是个dp从下一行for到这一行
bs[j][m*i+j]=true; // 可达自己当前这个位置
}else{
bs[j].reset(); // 当前不可达,就封死
}
for(auto rb:rbt[i][j]){ // 对起点在当前点的所有robot处理答案
if(rb.opt==1){
ans[rb.id]=(rb.y==j && bs[j][m*rb.x+rb.y]);
}else if(rb.opt==2){
ans[rb.id]=(rb.x==i && bs[j][m*rb.x+rb.y]);
}else{
ans[rb.id]=bs[j][m*rb.x+rb.y];
}
}
}
}
for(int i=1;i<=q;++i) printf(ans[i]?"yes\n":"no\n");
}
2021牛客多校第八场 J-Tree
博弈+树+rmq
//
// Created by artist on 2021/8/10.
//
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define DB1(args...) do { cout << #args << " : "; dbg(args); } while (0)
void dbg() { std::cout << " #\n"; }
template<typename T, typename...Args>
void dbg(T a, Args...args) {
std::cout << a << ' ';
dbg(args...);
}
const int maxn = 1e6+5;
vector<int> G[maxn];
int onpath[maxn]; // 在s-t路径上
int hei[maxn];
int parent[maxn]; // 路径
vector<int> d;
int st[3][maxn][23];
const int inf = 0x3f3f3f3f;
void dfs(int u,int fa){
hei[u]=0;
for(int v:G[u]){
if(v==fa || onpath[v]) continue;
parent[v]=u;
dfs(v,u);
hei[u]=max(hei[v]+1,hei[u]);
}
}
int Query(int l,int r,int opt){
int k=__lg(r-l+1);
return max(st[opt][l][k],st[opt][r-(1<<k)+1][k]);//把拆出来的区间分别取最值
}
int solve(int l,int r,int opt){
// 先手位置,后手位置,现在是谁
if(l>=r) return opt?-inf:inf;
if(!opt){
return max(solve(l+1,r,1),st[0][l][0]-Query(l+1,r,1));
}else{
return min(solve(l,r-1,0),Query(l,r-1,0)-st[1][r][0]);
}
}
signed main() {
int n,s,t;scanf("%d%d%d",&n,&s,&t);
for(int i=1,u,v;i<n;++i){
scanf("%d%d",&u,&v);
G[u].push_back(v);
G[v].push_back(u);
}
parent[s]=0;
dfs(s,0);
// 标记路上的节点
for(int f=t;f;f=parent[f]){
onpath[f]=1;
}
// 一路回溯,记录路径外的高度
for(int f=t;f;f=parent[f]){
dfs(f,0);
d.push_back(hei[f]);
}
reverse(d.begin(),d.end());
int m=d.size();
for(int i=0;i<m;++i){
st[0][i+1][0]=d[i]+i;
st[1][i+1][0]=d[i]+m-1-i;
}
// rmq
for(int j=1;(1<<j)<=m;j++){
for(int i=1;i+(1<<(j-1))<=m;i++){
st[0][i][j]=max(st[0][i][j-1],st[0][i+(1<<(j-1))][j-1]);
st[1][i][j]=max(st[1][i][j-1],st[1][i+(1<<(j-1))][j-1]);
}
}
printf("%d\n",solve(1,m,0));
}
LOJ105 文艺平衡树
用来学splay
1.无旋treap版本
//
// Created by artist on 2021/6/17.
//
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int maxn = 1e5+5;
struct node{
int key,lch,rch,pri,sz,lzy;
node(int k=0,int l=0,int r=0,int p=0,int s=0):key(k),lch(l),rch(r),pri(p),sz(s){}
}tr[maxn<<2];
int cnt,rt;
#define key(x) tr[x].key
#define pri(x) tr[x].pri
#define lch(x) tr[x].lch
#define rch(x) tr[x].rch
#define sz(x) tr[x].sz
#define lzy(x) tr[x].lzy
int newnode(int v){
tr[++cnt]=node(v,0,0,rand(),1);
return cnt;
}
void push_up(int u){
sz(u)=sz(lch(u))+sz(rch(u))+1;
}
void push_down(int u){
if(lzy(u)){
lzy(u)=0;
swap(lch(u),rch(u));
lzy(lch(u))^=1,lzy(rch(u))^=1;
}
}
// 把区间分为中序遍历序号小于等于k的和大于k的
pair<int,int> split(int u,int k){
if(!u) return make_pair(0,0);
push_down(u);
if(k<=sz(lch(u))){
pair<int,int> o = split(lch(u),k);
lch(u)=o.second;
push_up(u);
return make_pair(o.first,u);
}else{
pair<int,int> o = split(rch(u),k-sz(lch(u))-1);
rch(u)=o.first;
push_up(u);
return make_pair(u,o.second);
}
}
// 满足u的所有点key小于v所有点的key
int merge(int u,int v){
// u==0或v==0
if(!u||!v) return u+v;
push_down(u);
push_down(v);
if(pri(u)<pri(v)){
rch(u)=merge(rch(u),v);
push_up(u);
return u;
}else{
lch(v)=merge(u,lch(v));
push_up(v);
return v;
}
}
// 这个l和r是区间..不是key...
void rev(int l,int r){
pair<int,int> o = split(rt,l-1);
pair<int,int> q = split(o.second,r-l+1);
lzy(q.first)^=1;
rt=merge(o.first,merge(q.first,q.second));
}
void dfs(int u){
if(!u) return;
push_down(u);
dfs(lch(u));
printf("%d ",u);
dfs(rch(u));
}
int main(){
srand(time(NULL));
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) rt=merge(rt,newnode(i));
for(int i=1,l,r;i<=m;++i){
scanf("%d%d",&l,&r);
rev(l,r);
}
dfs(rt);
}
2.splay版本
这里的Splay维护的显然不再是权值排序
现在按照的是序列中的编号排序(不过在这道题目里面就是权值诶。。。)
那么,继续考虑,其实最终的结果也就是整颗Splay的中序遍历(平衡树的性质诶)
那么,现在如果按照权值来维护显然是不正确的
继续找找规律,发现,如果一个点在序列中的位置为第K个
那么,他就是平衡树的第K大(就当做普通的Splay来看的话)
所以,序列中的位置就变成了区间的第K大点
继续考虑如何翻转
翻转也就是整颗子树的每一个节点的左右儿子交换
因此,只要在根节点的地方打一个标记
在旋转之前下方一下标记就行了
最后输出的时候输出的就是Splay的中序遍历
至于初始的Splay怎么建立,可以直接构造完美的Splay
像我这种比较懒得,直接弄了一个insert。。。
那么我们现在已经有一棵编号树了(并且由于递归建树,一开始是平衡的),我们要对它进行区间翻转操作。那么实际上我们可以发现,在反转区间[l,r]的时候,我们可以考虑利用Splay的性质,将l-1翻转至根节点,再将r+1翻转至根节点的幼儿子
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
int fa[maxn],sz[maxn],cnt[maxn],ch[maxn][2],val[maxn],rt,tot,flg[maxn];
int n;
// 0:左儿子,1:右儿子
struct Splay{
void maintain(int x) {sz[x]=sz[ch[x][0]]+sz[ch[x][1]]+cnt[x];}
bool get(int x) {return x==ch[fa[x]][1];}
void clear(int x) {
ch[x][0]=ch[x][1]=fa[x]=val[x]=sz[x]=cnt[x]=0;
}
void rotate(int x){
int y=fa[x],z=fa[y],chk=get(x);
ch[y][chk]=ch[x][chk^1];
if(ch[x][chk^1]) fa[ch[x][chk^1]]=y;
ch[x][chk^1]=y;
fa[y]=x;
fa[x]=z;
if(z) ch[z][y==ch[z][1]] = x;
maintain(x);
maintain(y);
}
void push_down(int x) {
if(flg[x]) {
flg[x]=0;
swap(ch[x][0],ch[x][1]);
flg[ch[x][0]]^=1;
flg[ch[x][1]]^=1;
}
}
void splay(int x,int goal){
for(int f;f=fa[x],f!=goal;rotate(x))
if(fa[f]!=goal) rotate(get(x)==get(f)?f:x);
if(!goal) rt=x;
}
void ins(int k){
if(!rt){
val[++tot]=k;
cnt[tot]++;
rt=tot;
maintain(rt);
return;
}
int cur = rt, f = 0;
while(1){
if(val[cur]==k){
cnt[cur]++;
maintain(cur);
maintain(f);
splay(cur,0);
break;
}
f=cur;
cur=ch[cur][val[cur]<k];
if(!cur){
val[++tot]=k;
cnt[tot]++;
fa[tot]=f;
ch[f][val[f]<k]=tot;
maintain(tot);
maintain(f);
splay(tot,0);
break;
}
}
}
int kth(int k){
int cur=rt;
while(1){
push_down(cur);
if(ch[cur][0]&&k<=sz[ch[cur][0]]){
cur=ch[cur][0];
}else{
k-=cnt[cur]+sz[ch[cur][0]];
if(k<=0){
// splay(cur,0);
return val[cur];
}
cur=ch[cur][1];
}
}
}
void rev(int l,int r){
int ll=l-1?kth(l-1):0,rr=r==n?0:kth(r+1);
if(ll) splay(ll,0);
if(rr) splay(rr,ll);
int cur;
if(ll&&rr) cur=ch[rr][0]; // 防止如果旋转的是边界
else if(ll) cur=ch[rt][1];
else if(rr) cur=ch[rr][0];
else cur=rt;
flg[cur]^=1;
}
void dfs(int u){
if(!u) return;
push_down(u);
dfs(ch[u][0]);
printf("%d ",u);
dfs(ch[u][1]);
}
}tr;
int main(){
freopen("in.in","r",stdin);
int m;scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
tr.ins(i);
}
for(int i=1,l,r;i<=m;++i){
scanf("%d%d",&l,&r);
tr.rev(l,r);
}
tr.dfs(rt);
}
BZOJ3282 Tree(lct模板)
求一条路径的异或和。
操作包括:加边、删边、改点权
//
// Created by artist on 2021/8/11.
//
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
const int maxn = 3e5+5;
int flg[maxn],val[maxn],f[maxn],ch[maxn][2],sum[maxn];
struct lct{
#define Get(x) (ch[f[x]][1]==x)
#define isRoot(x) (ch[f[x]][0]!=x && ch[f[x]][1]!=x)
inline void push_up(int x){
sum[x]=val[x]^sum[ch[x][0]]^sum[ch[x][1]];
}
inline void push_down(int x){
if(flg[x]){
flg[x]=0;
swap(ch[x][0],ch[x][1]);
flg[ch[x][0]]^=1;
flg[ch[x][1]]^=1;
}
}
inline void rotate(int x){
int y=f[x],z=f[y],k=Get(x);
if(!isRoot(y)) ch[z][ch[z][1]==y]=x;
f[ch[y][k]=ch[x][!k]]=y;
ch[x][!k]=y,f[y]=x,f[x]=z;
push_up(y),push_up(x);
}
inline void splay(int x){
update(x);
for(int fa;fa=f[x],!isRoot(x);rotate(x))
if(!isRoot(fa)) rotate(Get(fa)==Get(x)?fa:x);
}
inline int access(int x){
int p;
for(p=0;x;p=x,x=f[x])
splay(x),ch[x][1]=p,push_up(x);
return p;
}
inline void update(int p){
if (!isRoot(p)) update(f[p]);
push_down(p);
}
inline void makeRoot(int p){
p=access(p);
flg[p]^=1;
push_down(p);
}
inline void link(int x,int p){
// 先判断是否合法
if(Find(x)==Find(p)) return;
makeRoot(x);
splay(x);
f[x]=p;
}
inline void split(int x,int y){
makeRoot(x);
access(y);
splay(y);
}
inline void cut(int x,int p){
// 先判断是否合法
// 1.在一个连通块内 2.中序遍历紧挨着
if(Find(x)!=Find(p)) return;
makeRoot(x),access(p),splay(p);
if(ch[p][0]!=x) return;
ch[p][0]=f[x]=0;
}
inline int Find(int p){
access(p),splay(p),push_down(p);
while(ch[p][0]) p=ch[p][0],push_down(p);
splay(p);
return p;
}
}tr;
signed main() {
int m;scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) scanf("%d",&val[i]);
for(int i=1;i<=m;++i){
int opt,x,y;scanf("%d%d%d",&opt,&x,&y);
if(opt==0){
tr.split(x,y);
printf("%d\n",sum[y]);
}else if(opt==1){
tr.link(x,y);
}else if(opt==2){
tr.cut(x,y);
}else{
tr.access(x);
tr.splay(x);
val[x]=y;
tr.push_up(x);
}
}
}
洛谷P3203 [HNOI2010]弹飞绵羊
lct裸题
新建一个点表示被弹飞
//
// Created by artist on 2021/8/11.
//
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define DB1(args...) do { cout << #args << " : "; dbg(args); } while (0)
void dbg() { std::cout << " #\n"; }
template<typename T, typename...Args>
void dbg(T a, Args...args) {
std::cout << a << ' ';
dbg(args...);
}
const int maxn = 2e5+5;
int flg[maxn],f[maxn],ch[maxn][2],sum[maxn];
struct lct{
#define Get(x) (ch[f[x]][1]==x)
#define isRoot(x) (ch[f[x]][0]!=x && ch[f[x]][1]!=x)
inline void push_up(int x){
sum[x]=sum[ch[x][0]]+sum[ch[x][1]]+1;
}
inline void push_down(int x){
if(flg[x]){
flg[x]=0;
swap(ch[x][0],ch[x][1]);
flg[ch[x][0]]^=1;
flg[ch[x][1]]^=1;
}
}
inline void rotate(int x){
int y=f[x],z=f[y],k=Get(x);
if(!isRoot(y)) ch[z][ch[z][1]==y]=x;
f[ch[y][k]=ch[x][!k]]=y;
ch[x][!k]=y,f[y]=x,f[x]=z;
push_up(y),push_up(x);
}
inline void splay(int x){
update(x);
for(int fa;fa=f[x],!isRoot(x);rotate(x))
if(!isRoot(fa)) rotate(Get(fa)==Get(x)?fa:x);
}
inline int access(int x){
int p;
for(p=0;x;p=x,x=f[x])
splay(x),ch[x][1]=p,push_up(x);
return p;
}
inline void update(int p){
if (!isRoot(p)) update(f[p]);
push_down(p);
}
inline void makeRoot(int p){
p=access(p);
flg[p]^=1;
push_down(p);
}
inline void link(int x,int p){
// 先判断是否合法
if(Find(x)==Find(p)) return;
makeRoot(x);
splay(x);
f[x]=p;
}
inline void split(int x,int y){
makeRoot(x);
access(y);
splay(y);
}
inline void cut(int x,int p){
// 先判断是否合法
// 1.在一个连通块内 2.中序遍历紧挨着
if(Find(x)!=Find(p)) return;
makeRoot(x),access(p),splay(p);
if(ch[p][0]!=x) return;
ch[p][0]=f[x]=0;
}
inline int Find(int p){
access(p),splay(p),push_down(p);
while(ch[p][0]) p=ch[p][0],push_down(p);
splay(p);
return p;
}
}tr;
int a[maxn];
signed main() {
int n;scanf("%d",&n);
for(int i=1,y;i<=n;++i){
scanf("%d",&y);
a[i]=y;
if(i+y>n) tr.link(i,n+1);
else tr.link(i,i+y);
}
int m;scanf("%d",&m);
for(int i=1;i<=m;++i){
int opt,x,y;scanf("%d%d",&opt,&x);
x++;
if(opt==1){
tr.makeRoot(x);
tr.access(n+1);
tr.splay(n+1);
printf("%d\n",sum[n+1]-1);
}else{
scanf("%d",&y);
if(x+a[x]<=n) tr.cut(x,x+a[x]);
else tr.cut(x,n+1);
a[x]=y;
if(x+a[x]<=n) tr.link(x,x+a[x]);
else tr.link(x,n+1);
}
}
}
牛客第九场 I Incentive Model
题意:A和B竞争,A初始x/y,B初始1-x/y。Ta:Pr[Ta=t]=(1-pA){t-1}pA(几何分布)。赢下一个块的条件:Ta<Tb。赢了一个块:奖励w的得分,得分可以增加下一次挖矿的效率。pa=a/{2256}={k*w+x/y}/{2^256}。问A的期望赢的块数。
令 X i ∈ 0 , 1 X_i\in {0,1} Xi∈0,1 分别表示矿工A能不能获得第i个区块。令 S i S_i Si 表示在竞争完第 i i i 个区块后A的stake。
S 0 = a S_0=a S0=a
首先我们考虑 X i X_i Xi为1的概率。(概率论白学现场)= S i − 1 1 + w ( i − 1 ) \frac{S_{i-1}}{1+w(i-1)} 1+w(i−1)Si−1 (已经发放了i-1次奖励)。(我也不知道为啥直接a之于总和的概率这样就可以了)
显然有 S i + 1 = S i + w X i + 1 S_{i+1}=S_i+wX_{i+1} Si+1=Si+wXi+1。
考虑条件期望(俺也不会)。 E [ S i + 1 ∣ S i ] = S i + S i 1 + w i E[S_{i+1}|S_i]=S_i+\frac{S_i}{1+wi} E[Si+1∣Si]=Si+1+wiSi
那么 E [ S i + 1 ] = E [ E [ S i + 1 ∣ S i ] ] = E [ S i ] × 1 + w i + w 1 + w i E[S_{i+1}]=E[E[S_{i+1}|S_i]]=E[S_i]\times\frac{1+wi+w}{1+wi} E[Si+1]=E[E[Si+1∣Si]]=E[Si]×1+wi1+wi+w(发现是一个递推过程)
因此有 E [ S i + 1 ] = E [ S 0 ] × ∏ j = 0 i 1 + w j + w 1 + w j = a × ∏ j = 0 i 1 + w ( j + 1 ) 1 + w j E[S_{i+1}]=E[S_0]\times\prod_{j=0}^{i}{\frac{1+wj+w}{1+wj}}=a\times \prod_{j=0}^i\frac{1+w(j+1)}{1+wj} E[Si+1]=E[S0]×∏j=0i1+wj1+wj+w=a×∏j=0i1+wj1+w(j+1)
因此 E [ S i ] = a × ( 1 + w i ) E[S_i]=a\times(1+wi) E[Si]=a×(1+wi)
答案就是 n × E [ λ A ] = n a × ( 1 + w n ) − a w n = a n n\times E[\lambda_A]=n\frac{a\times(1+wn)-a}{wn}=an n×E[λA]=nwna×(1+wn)−a=an
#include<bits/stdc++.h>
using namespace std;
const int mod = 998244353;
int qpow(int a,int n){
long long ans=1;
while(n){
if(n&1) ans=ans*a%mod;
a=(long long)a*a%mod;
n>>=1;
}
return ans;
}
int main(){
int n,w,x,y;cin>>n>>w>>x>>y;
cout<<(long long)x*qpow(y,mod-2)%mod*n%mod<<endl;
}