【UOJ349】【WC2018】即時戰略 LCT 動態點分治
這是一道交互題
題目大意
有一棵\(n\)個點的樹。最開始\(1\)號點是白的,其他點是黑的。
每次你可以執行一個操作:\(explore(x,y)\)。要求\(x\)是一個白點。該函數會返回從\(x\)到\(y\)的路徑上第二個點的坐標並把該點染白。
要求你把所有點都染成白色。
設操作次數為\(t\)。
對於\(30\%\)的數據:這棵樹是一條鏈(不保證\(1\)在鏈的一端),\(n=300000,t=O(n+\log n)\)
對於另外\(70\%\)的數據:\(n=300000,t=O(n\log n)\)
題解
數據範圍告訴我們鏈的情況要分開做。
做法1
有一個簡單的做法:維護當前白色節點的兩段,每次加入一個新的節點,如果這個節點是黑的,就先判斷這個點在一號點的左邊還是右邊,在進行擴展。
可以證明擴展次數是\(2\ln n\)
記\(E(n)\)為一條長為\(n+1\)的鏈(有\(n\)個黑色節點),從左邊開始擴展的期望次數(最左端是\(1\)號點)。
\[
\begin{align}
E(0)&=0\E(n)&=1+\frac{1}{n}\sum_{i=0}^{n-1}E(i)\n(E(n)-1)&=(n-1)(E(n-1)-1)+E(n-1)\nE(n)-n&=nE(n-1)-n+1-E(n-1)+1+E(n-1)\nE(n)-nE(n-1)&=1\E(n)-E(n-1)&=\frac{1}{n}\E(n)&=\sum_{i=1}^n\frac{1}{i}\approx\ln n
\end{align}
\]
就是枚舉\(n\)是第幾個擴展的,把前面的離散化到\(1\sim i-1\)。
因為這題的\(1\)號點不在一端,次數就要乘以\(2\)。
期望操作次數是\(n+2\ln n\)
但是這樣很大概率拿不了滿分。
我們加入一個新的節點時,可以默認這個點是在\(1\)號點左邊,擴展第一次時判斷要擴展的點是否是白的。如果是白的就說明新加入的點在\(1\)號點右邊。
當然,加入新節點的第一次擴展要隨機擴展左邊或右邊。
這樣期望擴展次數就是\(\ln n\)了,期望操作次數就是\(n+\ln n\)
做法2
問題轉化為:每次給你一個新的點,要你找出樹上離這個點最近的節點。
- 動態點分治:動態維護點分治樹,在點分治樹上面跳。
時間復雜度:\(O(n\log^2 n)\)
操作次數:\(O(n\log n)\)
- LCT:每次從根往下在splay上跳,如果調到另一條鏈上就splay一下繼續跳。
時間復雜度:\(O(n\log n)\)
操作次數:\(O(n\log n)\)
這兩種做法都可以拿到滿分。
如果你寫的是動態點分治,你可能會被卡常。
UPD:動態點分治跑的很快,LCT沒寫好會被卡操作常數。
代碼
LCT
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<ctime>
#include<cstdlib>
#include<utility>
#include"rts.h"
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
void open(const char *s)
{
#ifndef ONLINE_JUDGE
char str[100];
sprintf(str,"%s.in",s);
freopen(str,"r",stdin);
sprintf(str,"%s.out",s);
freopen(str,"w",stdout);
#endif
}
int n;
int b[300010];
int t;
int a[300010][2];
int f[300010];
int l[300010];
int r[300010];
int root(int x)
{
return !f[x]||(a[f[x]][0]!=x&&a[f[x]][1]!=x);
}
void mt(int x)
{
l[x]=r[x]=x;
if(a[x][0])
l[x]=l[a[x][0]];
if(a[x][1])
r[x]=r[a[x][1]];
}
void rotate(int x)
{
int p=f[x];
int q=f[p];
int ps=(x==a[p][1]);
int qs=(p==a[q][1]);
int c=a[x][ps^1];
if(!root(p))
a[q][qs]=x;
a[x][ps^1]=p;
a[p][ps]=c;
if(c)
f[c]=p;
f[p]=x;
f[x]=q;
mt(p);
mt(x);
}
void splay(int x)
{
while(!root(x))
{
int p=f[x];
if(!root(p))
{
int q=f[p];
if((p==a[q][1])==(x==a[p][1]))
rotate(p);
else
rotate(x);
}
rotate(x);
}
}
void access(int x)
{
int y=x,t=0;
while(x)
{
splay(x);
a[x][1]=t;
mt(x);
t=x;
x=f[x];
}
splay(y);
}
void gao(int x)
{
int now=1,v;
splay(now);
while(!b[x])
{
v=explore(now,x);
if(v==r[a[now][0]])
now=a[now][0];
else if(v==l[a[now][1]])
now=a[now][1];
else if(b[v])
{
splay(v);
now=v;
}
else
{
b[v]=1;
f[v]=now;
now=v;
}
}
access(x);
}
void gao1()
{
int i;
for(i=2;i<=n;i++)
if(!b[i])
gao(i);
}
void gao2()
{
int x1=1,x2=1;
int i;
b[1]=1;
for(i=2;i<=n;i++)
{
int x=i;
if(b[x])
continue;
int v=explore(x1,x);
if(!b[v])
{
b[v]=1;
x1=v;
while(x1!=x)
{
v=explore(x1,x);
b[v]=1;
x1=v;
}
}
else
{
while(x2!=x)
{
v=explore(x2,x);
b[v]=1;
x2=v;
}
}
}
}
void play(int _n,int _t,int type)
{
n=_n;
t=_t;
if(type==3)
gao2();
else
gao1();
}
動態點分治
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<ctime>
#include<cstdlib>
#include<utility>
#include<vector>
#include"rts.h"
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
void open(const char *s)
{
#ifndef ONLINE_JUDGE
char str[100];
sprintf(str,"%s.in",s);
freopen(str,"r",stdin);
sprintf(str,"%s.out",s);
freopen(str,"w",stdout);
#endif
}
vector<int> g[300010];
const double alpha=0.8;
int n;
int b[300010];
int t;
int d[300010];
int f[300010];
int s[300010];
int rebuild;
void gao(int now,int x)
{
if(now==x)
return;
int v=explore(now,x);
if(b[v])
{
while(f[v]!=now)
v=f[v];
s[now]-=s[v];
gao(v,x);
s[now]+=s[v];
}
else
{
b[v]=1;
g[now].push_back(v);
g[v].push_back(now);
d[v]=d[now]+1;
s[v]=1;
f[v]=now;
gao(v,x);
s[now]+=s[v];
}
if(s[v]>s[now]*alpha)
rebuild=now;
}
void dfs(int x,int dep)
{
b[x]=0;
for(auto v:g[x])
if(b[v]&&d[v]>=dep)
dfs(v,dep);
}
int ss[300010];
void dfs1(int x,int fa)
{
ss[x]=1;
for(auto v:g[x])
if(v!=fa&&!b[v])
{
dfs1(v,x);
ss[x]+=ss[v];
}
}
int rt,rtsz,num;
void dfs2(int x,int fa)
{
int mx=0;
for(auto v:g[x])
if(!b[v]&&v!=fa)
{
dfs2(v,x);
mx=max(mx,ss[v]);
}
mx=max(mx,num-ss[x]);
if(mx<rtsz)
{
rtsz=mx;
rt=x;
}
}
int build(int x,int dep,int fa,int mi)
{
dfs1(x,0);
num=ss[x];
rtsz=0x7fffffff;
dfs2(x,0);
x=rt;
b[x]=1;
d[x]=dep;
f[x]=fa;
s[x]=1;
for(auto v:g[x])
if(!b[v])
{
int rr=build(v,dep+1,x,mi);
s[x]+=s[rr];
}
return x;
}
int cc[300010];
void gao1()
{
int i;
for(i=1;i<=n;i++)
cc[i]=i;
srand(time(0));
random_shuffle(cc+1,cc+n+1);
b[1]=1;
d[1]=0;
s[1]=1;
f[1]=0;
int r=1;
for(i=1;i<=n;i++)
if(!b[cc[i]])
// if(!b[i])
{
gao(r,cc[i]);
// gao(r,i);
if(rebuild)
{
dfs(rebuild,d[rebuild]);
int rr=build(rebuild,d[rebuild],f[rebuild],d[rebuild]);
if(rebuild==r)
r=rr;
rebuild=0;
}
}
}
void gao3()
{
int x1=1,x2=1;
int i;
b[1]=1;
for(i=1;i<=n;i++)
cc[i]=i;
srand(time(0));
random_shuffle(cc+1,cc+n+1);
for(i=1;i<=n;i++)
{
int x=cc[i];
if(b[x])
continue;
int v=explore(x1,x);
if(!b[v])
{
b[v]=1;
x1=v;
while(x1!=x)
{
v=explore(x1,x);
b[v]=1;
x1=v;
}
}
else
{
while(x2!=x)
{
v=explore(x2,x);
b[v]=1;
x2=v;
}
}
}
}
void gao2()
{
int i,v;
for(i=2;i<=n;i++)
{
int x=1;
while((v=explore(x,i))!=i)
x=v;
}
}
void play(int _n,int _t,int type)
{
n=_n;
t=_t;
if(type==3)
gao3();
else if(type==2)
gao2();
else
gao1();
}
【UOJ349】【WC2018】即時戰略 LCT 動態點分治