全序開題
準備打假
A.CF757G Can Bash Save the Day?
樹上動態求到的距離和,支持交換
可持久化點分樹,答案爲第個版本上點分樹的答案減去第個版本上點分樹的答案,修改就是對第個版本的單點修改。
因爲可持久化需要把到兒子的鏈接全部複製(如果你寫自底向上的點分樹也一樣需要把兒子到父親的鏈接全部修改),所以兒子個數太多可持久化複雜度就沒有保證,需要三度化原圖後再進行點分治。
UPD:仔細想了一想,如果要維護的可持久化數組的話,那麼一次修改好像需要改整個子樹,所以自底向上好像根本寫不了的樣子,就照可持久化線段樹的寫法寫吧。
//11:06~12:12
//Can Bush Save the Day ?
#include<bits/stdc++.h>
#define maxn 400005
#define maxp maxn * 20
#define lim 30
#define LL long long
#define pb push_back
#define pii pair<int,LL>
#define mp make_pair
#define Fi first
#define Se second
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;
char cb[1<<16],*cs=cb,*ct=cb;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<16,stdin),cs==ct)?0:*cs++)
void read(int &res){
char ch;
for(;!isdigit(ch=getc()););
for(res=ch-'0';isdigit(ch=getc());res=res*10+ch-'0');
}
const int M = (1 << 30) - 1;
int n,q,a[maxn],cnt_p;
int sz[maxn],dep[maxn],rt[maxn],st[maxn],ed[maxn],id[maxp],tot;
LL ds[maxn][lim],Sm[maxp],Sz[maxp],Smf[maxp],Szf[maxp];
vector<pii >tE[maxn],E[maxn];
vector<int>G[maxp];
void dfs0(int u,int ff){
int p=0;
for(auto v:tE[u]) if(v.Fi^ff){
if(!p){
E[u].pb(mp(v.Fi,v.Se));
E[v.Fi].pb(mp(u,v.Se));
p = u;
}
else{
int t = ++cnt_p;
E[p].pb(mp(t,0));
E[t].pb(mp(p,0));
E[t].pb(mp(v.Fi,v.Se));
E[v.Fi].pb(mp(t,v.Se));
p = t;
}
dfs0(v.Fi,u);
}
}
void dfs1(int u,int ff,int tsz,int &mn,int &rt){
int mx = 0;sz[u] = 1;
for(auto v:E[u]) if(v.Fi^ff && !dep[v.Fi])
dfs1(v.Fi,u,tsz,mn,rt),sz[u]+=sz[v.Fi],mx=max(mx,sz[v.Fi]);
if((mx=max(mx,tsz-sz[u])) < mn)
mn = mx , rt = u;
}
int Gert(int u,int tsz){
int mn,rt;
dfs1(u,0,tsz,mn=0x3f3f3f3f,rt=-1);
return rt;
}
void dfs2(int u,int ff,LL dis,int d){
ds[u][d] = dis;sz[u] = 1;
for(auto v:E[u]) if(v.Fi^ff && !dep[v.Fi])
dfs2(v.Fi,u,dis+v.Se,d),sz[u]+=sz[v.Fi];
}
void Build(int u,int d){
dep[u] = d;
dfs2(u,0,0,d);
for(auto v:E[u]) if(!dep[v.Fi]){
int t = Gert(v.Fi,sz[v.Fi]);
G[u].pb(t);
Build(t,d+1);
}
}
void dfs3(int u){
static int tot = 0;
st[u] = ++tot;
for(int v:G[u]) dfs3(v);
ed[u] = tot;
}
void ins(int &u,int p,int d){
Sm[++tot] = Sm[u] + ds[p][d] , Sz[tot] = Sz[u] + 1 ,
Smf[tot] = Smf[u] + ds[p][d-1] , Szf[tot] = Szf[u] + 1;
G[tot] = G[u] , id[tot] = id[u];
u = tot;
for(int &v:G[u]) if(st[id[v]] <= st[p] && st[p] <= ed[id[v]])
ins(v,p,d+1);
}
LL qry(int u,int p,int d){
LL r = Sm[u] + Sz[u] * ds[p][d];
if(d>1) r -= Smf[u] + Szf[u] * ds[p][d-1];
for(int v:G[u]) if(st[id[v]] <= st[p] && st[p] <= ed[id[v]])
r += qry(v,p,d+1);
return r;
}
int main(){
// freopen("1.in","r",stdin);
read(n),read(q);
rep(i,1,n) read(a[i]);
rep(i,1,n-1){
int u,v,w;
read(u),read(v),read(w);
tE[u].pb(mp(v,w)),tE[v].pb(mp(u,w));
}
cnt_p = n;
dfs0(1,0);
Build(rt[0]=Gert(1,cnt_p),1);
dfs3(rt[0]);
rep(i,1,cnt_p) id[i] = i;tot=cnt_p;
rep(i,1,n) ins(rt[i]=rt[i-1],a[i],1);
LL ans = 0;
for(int t,a,b,c;q--;){
read(t);
if(t == 1){
read(a),read(b),read(c);
int l = (ans & M) ^ a , r = (ans & M) ^ b , v = (ans & M) ^ c;
ans = qry(rt[r],v,1) - qry(rt[l-1],v,1);
printf("%lld\n",ans);
}
else{
read(a);
int x = (ans & M) ^ a;
ins(rt[x]=rt[x-1],::a[x+1],1);
swap(::a[x],::a[x+1]);
}
}
}
CF650D Zip-line
每次詢問將原序列的一個點的值改變後的最長嚴格上升子序列的長度。
那麼修改後的最長上升子序列分爲經過修改點和不經過的。
不經過的可以求出每個,最靠前的使得,且結尾的最長上升子序列長度與開頭的最長上升子序列長度之和爲最長上升子序列長度。
然後對於區間取即可,可以用表實現。
經過的可以用可持久化線段樹簡單維護。
可能對於不經過的有更優的其他做法。
UPD:不經過的可以通過判斷是否每個LIS都經過這個點來得知答案是還是,可以轉化爲:
假設這個點結尾的長度是,那麼就是判斷是否對於所有可能在全局中的點,以結尾的長度是否是,如果是,則有不經過。
//12:20~12:52
//Zip-Line
#include<bits/stdc++.h>
#define maxn 400005
#define maxp maxn * 50
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;
char start;
int n,m,a[maxn],b[maxn];
int h[maxn],sb[maxn<<1],rt[2][maxn],f[2][maxn],lc[maxp],rc[maxp],mx[maxp],in[maxn],tot;
vector<int>G[maxn];
void ins(int &u,int l,int r,int p,int v){
lc[++tot] = lc[u] , rc[tot] = rc[u] , mx[tot] = max(mx[u] , v);
u = tot;
if(l == r) return;
int m = l+r >> 1;
p <= m ? ins(lc[u],l,m,p,v) : ins(rc[u],m+1,r,p,v);
}
int qry(int u,int l,int r,int ql,int qr){
if(l>qr||ql>r||ql>qr||!u) return 0;
if(ql<=l&&r<=qr) return mx[u];
int m=l+r>>1;
return max(qry(lc[u],l,m,ql,qr),qry(rc[u],m+1,r,ql,qr));
}
char End;
int main(){
scanf("%d%d",&n,&m);
rep(i,1,n)
scanf("%d",&h[i]),sb[++sb[0]] = h[i];
rep(i,1,m)
scanf("%d%d",&a[i],&b[i]),sb[++sb[0]] = b[i];
sort(sb+1,sb+1+sb[0]);
sb[0] = unique(sb+1,sb+1+sb[0])-sb-1;
rep(i,1,n){
h[i] = lower_bound(sb+1,sb+1+sb[0],h[i])-sb;
f[0][i] = qry(rt[0][i-1],1,sb[0],1,h[i]-1) + 1;
ins(rt[0][i] = rt[0][i-1],1,sb[0],h[i],f[0][i]);
}
per(i,n,1){
f[1][i] = qry(rt[1][i+1],1,sb[0],h[i]+1,sb[0]) + 1;
ins(rt[1][i] = rt[1][i+1],1,sb[0],h[i],f[1][i]);
}
int mx = *max_element(f[0]+1,f[0]+n+1);
rep(i,1,n) if(f[0][i] + f[1][i] == mx + 1)
G[f[0][i]].push_back(i);
rep(i,1,n) if(G[i].size() == 1)
in[G[i][0]] = 1;
rep(i,1,m){
b[i] = lower_bound(sb+1,sb+1+sb[0],b[i])-sb;
printf("%d\n",max(qry(rt[0][a[i]-1],1,sb[0],1,b[i]-1)+qry(rt[1][a[i]+1],1,sb[0],b[i]+1,sb[0])+1,mx-in[a[i]]));
}
}
CF547E Mike and Friends
長度爲的字符串序列
每次詢問一個,求在中在每個字符串中作爲子串出現次數的和。
可持久化廣義後綴自動機
可持久化廣義後綴自動機的樹即可。
對於一個,把每個字符串的每個前綴在後綴自動機上的節點都,
求子樹和即爲出現次數。
單點修改子樹查詢。
但是爲什麼不離線呢?
因爲這是可持久化作業。
//Endless Falling Light.
//14:22~15:08
//Mike and Friends
#include<bits/stdc++.h>
#define maxn 200005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define maxc 26
#define maxp maxn * 20
using namespace std;
char ST;
char *s[maxn],S[maxn];
int n,q,slen[maxn],pos[maxn],rt[maxn];
int last,tr[maxn<<1][maxc],fa[maxn<<1],len[maxn<<1],tot;
int lc[maxp],rc[maxp],sm[maxp],cnt;
void ins(int c){
int p,q;
if(tr[last][c]){
p = last;
if(len[q = tr[p][c]] != len[p] + 1){
int cl = ++tot;
memcpy(tr[cl],tr[q],sizeof tr[q]);
fa[cl] = fa[q] , len[cl] = len[p] + 1;
for(;p!=-1 && tr[p][c] == q;tr[p][c] = cl , p = fa[p]);
fa[q] = cl;
}
}
else{
int cur = ++tot;
len[cur] = len[p = last] + 1;
for(;p!=-1 && !tr[p][c];tr[p][c]=cur,p=fa[p]);
if(p == -1) fa[cur] = 0;
else if(len[q = tr[p][c]] == len[p] + 1) fa[cur] = q;
else{
int cl = ++tot;
memcpy(tr[cl],tr[q],sizeof tr[q]);
fa[cl] = fa[q] , len[cl] = len[p] + 1;
for(;p!=-1 && tr[p][c] == q;tr[p][c] = cl , p = fa[p]);
fa[q] = fa[cur] = cl;
}
}
}//last maintained outside
void ins(int &u,int l,int r,int p){
lc[++cnt] = lc[u] , rc[cnt] = rc[u] , sm[cnt] = sm[u] + 1;
u = cnt;
if(l==r) return ;
int m = l+r>>1;
p <= m ? ins(lc[u],l,m,p) : ins(rc[u],m+1,r,p);
}
int qry(int u,int l,int r,int ql,int qr){
if(l>qr||ql>r||ql>qr||!u) return 0;
if(ql<=l&&r<=qr) return sm[u];
int m=l+r>>1;
return qry(lc[u],l,m,ql,qr) + qry(rc[u],m+1,r,ql,qr);
}
vector<int>G[maxn<<1];
int st[maxn<<1],ed[maxn<<1],tim;
void dfs(int u){
st[u] = ++tim;
for(int v:G[u])
dfs(v);
ed[u] = tim;
}
char ED;
int main(){
scanf("%d%d",&n,&q);
fa[0] = -1;
rep(i,1,n){
scanf("%s",S);
slen[i] = strlen(S);
s[i] = new char[slen[i]+1];
memcpy(s[i],S,sizeof(char) * slen[i]);
last = 0;
rep(j,0,slen[i]-1) ins(s[i][j]-'a'),last=tr[last][s[i][j]-'a'];
}
rep(i,1,tot) G[fa[i]].push_back(i);
dfs(0);
rep(i,1,n){
rt[i] = rt[i-1];
rep(j,0,slen[i]-1)
pos[i] = tr[pos[i]][s[i][j]-'a'],
ins(rt[i],1,tot+1,st[pos[i]]);
}
for(int l,r,K;q--;){
scanf("%d%d%d",&l,&r,&K);
printf("%d\n",qry(rt[r],1,tot+1,st[pos[K]],ed[pos[K]]) - qry(rt[l-1],1,tot+1,st[pos[K]],ed[pos[K]]));
}
}
THUSC2016補退選
樹上插入刪除,求單點值大小超過的最早時刻。
可持久化地維護每個版本的歷史最大和即可二分答案詢問,插入。
但是這個題可以在上用維護第一次和爲的最小時刻的近乎暴力的方法得到的複雜度。
那麼爲什麼要可持久化呢
也許這就是可持久化變量吧
//Waterloo.gugu.com
//15:12 ~ 15:22
//No No Choice
#include<bits/stdc++.h>
#define maxn 100005
#define maxp maxn * 61
#define maxc 10
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;
int n;
int tr[maxp][maxc],sz[maxp],tot;
vector<int>G[maxp];
char S[61];
int main(){
scanf("%d",&n);
int ans = 0;
rep(i,1,n){int op;
scanf("%d",&op);
if(op == 1){
scanf("%s",S);
int L = strlen(S) , u = 0;
rep(j,0,L-1){
int v = S[j] - 'a';
if(!tr[u][v]) tr[u][v] = ++tot;
u = tr[u][v];
sz[u] ++;
if(sz[u] > G[u].size()) G[u].push_back(i);
}
}
if(op == 2){
scanf("%s",S);
int L = strlen(S) , u = 0;
rep(j,0,L-1){
int v = S[j] - 'a';
if(!tr[u][v]) tr[u][v] = ++tot;
u = tr[u][v];
sz[u] --;
}
}
if(op == 3){
scanf("%s",S);
ans = abs(ans);
int a,b,c;scanf("%d%d%d",&a,&b,&c);
int t = (1ll * a * ans + b) % c + 1;
int L = strlen(S) , u = 0;
rep(j,0,L-1){
int v = S[j] - 'a';
u = tr[u][v];
if(!u){
u = -1;
break;
}
}
if(u == -1) ans = -1;
else if(G[u].size() >= t) ans = G[u][t-1];
else ans = -1;
printf("%d\n",ans);
}
}
}
LOJ #6198. 謝特
給出一個字符串,定義兩個後綴組合的權值爲
求兩個後綴組合權值的最大值。
考慮在後綴數組的數組上操作。
那麼變成每次取最小值的位置,求這個位置兩邊各選一個異或起來的最大值。
直接啓發式合併,掃短的那邊放在長的那邊的樹上查詢即可。
有理有據的可持久化
$\mathcal AC \ Code $
//shi-----(f)t.
//15:23~16:10
//shit
#include<bits/stdc++.h>
#define maxn 100005
#define lim 17
#define maxp maxn * 30
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;
int n,tr[maxp][2],sz[maxp],rt[maxn],tot;
char s[maxn];
void ins(int &u,int w,int d){
sz[++tot] = sz[u] + 1 , tr[tot][0] = tr[u][0] , tr[tot][1] = tr[u][1];
u = tot;
if(d < 0) return;
int v = w >> d & 1;
ins(tr[u][v],w,d-1);
}
int qry(int u,int u2,int w,int d){
if(!u) return 0;
int v = w >> d & 1;
if(sz[tr[u][!v]] - sz[tr[u2][!v]]) return qry(tr[u][!v],tr[u2][!v],w,d-1) + (1<<d);
return qry(tr[u][v],tr[u2][v],w,d-1);
}
int c[maxn],wa[maxn],wb[maxn],sa[maxn],rk[maxn],h[maxn];
void BuildSA(char *s,int n,int m){
int *x=wa,*y=wb;
rep(i,0,m-1) c[i] = 0;
rep(i,0,n-1) c[x[i] = s[i]]++;
rep(i,1,m-1) c[i] += c[i-1];
per(i,n-1,0) sa[--c[x[i]]] = i;
for(int j=1,p=0;j<n && p < n;j<<=1,m=p){
p=0;
rep(i,n-j,n-1) y[p++] = i;
rep(i,0,n-1) if(sa[i] >= j) y[p++]=sa[i]-j;
rep(i,0,m-1) c[i] = 0;
rep(i,0,n-1) c[x[i]]++;
rep(i,1,m-1) c[i] += c[i-1];
per(i,n-1,0) sa[--c[x[y[i]]]] = y[i];
p=0;
swap(x,y);
x[sa[0]] = p++;
rep(i,1,n-1)
x[sa[i]] = (y[sa[i]] == y[sa[i-1]] && y[sa[i]+j] == y[sa[i-1]+j]) ? p-1 : p++;
}
rep(i,0,n-1) rk[sa[i]] = i;
for(int i=0,k=0;i<n-1;h[rk[i++]] = k)
for(k?k--:0;s[i+k] == s[sa[rk[i]-1]+k];k++);
}
int lc[maxn],rc[maxn],q[maxn],tp,w[maxn];
int ans = 0;
void Solve(int u,int l,int r){
if(!u) return;
if(u-l < r-u+1){
rep(i,l,u-1) ans = max(ans , h[u] + qry(rt[r],rt[u-1],w[sa[i]],lim-1));
}
else{
rep(i,u,r) ans = max(ans , h[u] + qry(rt[u-1],rt[l-1],w[sa[i]],lim-1));
}
Solve(lc[u],l,u-1),Solve(rc[u],u,r);
}
int main(){
scanf("%d",&n);
scanf("%s",s);
BuildSA(s,n+1,250);
rep(i,0,n-1) scanf("%d",&w[i]);
rep(i,1,n) ins(rt[i]=rt[i-1],w[sa[i]],lim-1);
rep(i,2,n){
for(;tp && h[q[tp]] >= h[i];tp--)
rc[q[tp]] = lc[i],lc[i] = q[tp];
q[++tp] = i;
}
for(;tp>1;tp--)
rc[q[tp-1]] = q[tp];
Solve(q[1],1,n);
printf("%d\n",ans);
}
NOI 2018 你的名字
每次詢問有多少個子串滿足不是的子串。
後綴自動機樹上線段樹合併得到每個節點的。
把扔到上匹配,
加入,判斷是否失配:
判斷這個字符的下一個點的最短字符串是否在出現過,即下一個點的集合中是否有中的。
失配了,跳後,或者是沒失配跳到下一個點,可能需要縮小當前長度,求中最大的再減去,再和原長度取即可得到對於的每個前綴,最靠前的使得在中出現過。
然後把這些信息扔到的後綴自動機上求出他們本質不同的部分即可。
//ION0202 fister.
#include<bits/stdc++.h>
#define maxn 2000005
#define maxp maxn * 11
#define maxc 26
#define LL long long
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;
char mstar;
char S[maxn],T[maxn];
int n,m;
int rt[maxn],lc[maxp],rc[maxp],mx[maxp],tot;
void ins(int &u,int l,int r,int p){
if(!u) u = ++tot;
mx[u] = max(mx[u] , p);
if(l == r) return;
int m = l+r>>1;
p <= m ? ins(lc[u],l,m,p) : ins(rc[u],m+1,r,p);
}
int qry(int u,int l,int r,int ql,int qr){
if(ql>r||l>qr||ql>qr||!u) return 0;
if(ql<=l&&r<=qr) return mx[u];
int m = l+r>>1;
int t = qry(rc[u],m+1,r,ql,qr);
if(t) return t;
return qry(lc[u],l,m,ql,qr);
}
void merge(int &u,int l,int r){
if(!l || !r) return (void)(u = l+r);
u = ++tot;
mx[u] = max(mx[l] , mx[r]);
merge(lc[u],lc[l],lc[r]) , merge(rc[u],rc[l],rc[r]);
}
bool cmp(const int &u,const int &v);
bool cmp2(const int &u,const int &v);
struct SAM{
int tr[maxn][maxc],fa[maxn],len[maxn],tot,last,pos[maxn],Len,c[maxn];
int ans[maxn],tg[maxn];
void ins(int c){
int cur = ++tot , p = last , q;
len[last = cur] = len[p] + 1;
for(;~p && !tr[p][c];p=fa[p]) tr[p][c] = cur;
if(p == -1) fa[cur] = 0;
else if(len[q = tr[p][c]] == len[p] + 1) fa[cur] = q;
else{
int cl=++tot;
memcpy(tr[cl],tr[q],sizeof tr[q]);
fa[cl] = fa[q] , len[cl] = len[p] + 1;
for(;~p && tr[p][c] == q;tr[p][c] = cl,p=fa[p]);
fa[q] = fa[cur] = cl;
}
}
void init(char *S,int L){
last = tot = 0;
memset(tr,0,sizeof(tr[0])*(L+1)*2);
memset(fa,0,sizeof(int)*(L+1)*2);
memset(len,0,sizeof(int)*(L+1)*2);
fa[0] = -1;
rep(i,0,L-1) ins(S[i]-'a'),pos[i+1] = last;
rep(i,1,tot) ans[i] = len[fa[i]] , tg[i] = 0;
tg[0] = 0;
Len = L;
}
void Build(){
rep(i,1,Len) ::ins(rt[pos[i]],1,Len,i);
rep(i,0,tot) c[i] = i;
sort(c,c+tot+1,cmp);
per(i,tot,0){
int u = c[i];
if(~fa[u])
merge(rt[fa[u]],rt[fa[u]],rt[u]);
}
}
LL query(char *S,int L,int l,int r,SAM &B){
int u = 0,nl = 0 , bu = 0;
rep(i,0,L-1){
int v = S[i] - 'a' , p = tr[u][v];
int t = p ? qry(rt[p],1,Len,l+len[fa[p]],r) : 0;
for(;~u && !t;){
u = fa[u] , p = tr[u][v];
t = p ? qry(rt[p],1,Len,l+len[fa[p]],r) : 0;
}
if(u == -1) nl = 0 , u = 0;
else nl = min(nl + 1 , min(qry(rt[u],1,Len,l,r)-l+1,len[u]) + 1) , u = tr[u][v];
nl = min(nl , qry(rt[u],1,Len,l,r)-l+1);
bu = B.tr[bu][v];
B.tg[bu] = nl;
}
LL ret = 0;
rep(i,0,B.tot) B.c[i] = i;
sort(B.c,B.c+B.tot+1,cmp2);
per(i,B.tot,1){
int u = B.c[i];
B.tg[B.fa[u]] = max(B.tg[B.fa[u]] , B.tg[u]);
ret += min(max(B.ans[u] , B.tg[u]) , B.len[u]) - B.len[B.fa[u]];
}
return ret;
}
}SA,TA;
bool cmp(const int &u,const int &v){ return SA.len[u] < SA.len[v]; }
bool cmp2(const int &u,const int &v){ return TA.len[u] < TA.len[v]; }
char mend;
int main(){
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
scanf("%s",S);
n = strlen(S);
SA.init(S,n);
SA.Build();
int q;scanf("%d",&q);
for(;q--;){
scanf("%s",T);
m = strlen(T);
TA.init(T,m);
LL ans=0;
rep(i,1,TA.tot)
ans += TA.len[i] - TA.len[TA.fa[i]];
int l,r;scanf("%d%d",&l,&r);
ans -= SA.query(T,m,l,r,TA);
printf("%lld\n",ans);
}
}
「PA 2019」Podatki drogowe
給定一棵邊權爲的樹,在個點對中的最短路徑中求第短路徑。
套路題。
二分答案。
發現答案值域很大但是可取的值很少(只有種)。
那麼每次隨機一個(在二分範圍內)可取的值(方案),然後判斷比他短的有沒有個即可完成二分答案。
期望次數次。
如何比較兩條路徑大小?
發現路徑的長度之和永遠不會進位。
所以路徑的長度即爲對位相加。
比較大小就相當於比較字符串大小,用哈希求出後即可判斷大小。
如何快速求出比一條路徑短的路徑數?
點分治,對於每個分治中心,維護到其點分樹子樹中每個點的字符串。
因爲點分樹子樹是樹上聯通塊,具有樹形結構,我們可以用可持久化線段樹維護到每個點的字符串以及字符串的任意一個區間的哈希值。
發現可以合併兩個字符串,在可持久化線段樹上表現爲對位哈希值相加。
於是將一個點分中心周圍的所有字符串排個序,每次詢問的時候即可求出經過點分中心的字符串有多少小於詢問串。
一次字符串比較是的,於是預處理是的。
一次詢問是的。
加上二分是的。
隨機一個在二分範圍內的答案,因爲我們是用處理詢問的,所以我們自然可以得到每個點分中心,選一個點爲一端時另一端的在什麼範圍內纔在二分範圍內。
本來我是沿着租酥雨的思路想寫點分的,但是中途看了一下EI的博客然後覺得點分還要去重實在是太噁心了,就寫了邊分治
注意因爲是隨機二分,所以你不能像普通二分一樣,在這個題甚至連是什麼路徑都不需要維護,只需要維護可選值域即可。
所以就迭代次即可通過此題。
//next time.
#include<bits/stdc++.h>
#define maxn 50005
#define lim 16
#define LL long long
#define maxp maxn * lim * lim
#define mod 1000000007
#define pii pair<int,int>
#define mp make_pair
#define pb push_back
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;
LL K;
int n,cnt_p;
int info[maxn],Prev[maxn<<1],to[maxn<<1],cst[maxn<<1],cnt_e=1;
void Node(int u,int v,int c){ Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v,cst[cnt_e]=c; }
int fir[maxn],nxt[maxn<<1],tar[maxn<<1],cw[maxn<<1],cnte=1;
void add(int u,int v,int c){ nxt[++cnte]=fir[u],fir[u]=cnte,tar[cnte]=v,cw[cnte]=c; }
int sz[maxn],vis[maxn];
void dfs0(int u,int ff){
int p = 0;
for(int i=info[u],v;i;i=Prev[i]) if((v=to[i])^ff){
dfs0(v,u);
if(!p){
add(u,v,cst[i]),add(v,u,cst[i]);
p = u;
}
else{
int t = ++cnt_p;
add(p,t,0),add(t,p,0);
add(t,v,cst[i]),add(v,t,cst[i]);
p = t;
}
}
}
void dfs1(int u,int ff,int tsz,int &mn,int &rt){
sz[u] = 1;
for(int i=fir[u],v;i;i=nxt[i]) if((v=tar[i])^ff && !vis[i/2]){
dfs1(v,u,tsz,mn,rt);
int t = max(sz[v] , tsz - sz[v]);
if(t < mn) mn = t , rt = i/2;
sz[u] += sz[v];
}
}
int Gert(int u,int tsz){
int mn = 0x3f3f3f3f , rt = -1;
dfs1(u,0,tsz,mn,rt);
return rt;
}
int rt[maxn],lc[maxp],rc[maxp],sm[maxp],tot;
vector<int>G[maxn],rG[maxn],mx[maxn],mn[maxn],pt[maxn];
LL hs[maxp],sj[maxn];
void ins(int &u,int l,int r,int p){
if(!p) return;
++tot;
hs[tot] = hs[u] + sj[p];
sm[tot] = sm[u] + 1;
lc[tot] = lc[u];
rc[tot] = rc[u];
u = tot;
if(l==r) return;
int m = l+r>>1;
p <= m ? ins(lc[u],l,m,p) : ins(rc[u],m+1,r,p);
}
int comp(int u,int v,int l,int r,int u2=0,int v2=0){// 0 equal -1 greater 1 less
if(hs[u] + hs[u2] == hs[v] + hs[v2]) return 0;
if(l == r){
int a = sm[u] + sm[u2] , b = sm[v] + sm[v2];
return a < b ? 1 : a > b ? -1 : 0;
}
int m = l+r>>1;
int t = comp(rc[u],rc[v],m+1,r,rc[u2],rc[v2]);
if(t) return t;
return comp(lc[u],lc[v],l,m,lc[u2],lc[v2]);
}
bool cmp(const int &u,const int &v){
return comp(u,v,1,n) == 1;
}
struct node{
int u,v;
node(const int &u=0,const int &v=0):u(u),v(v){}
bool operator <(const node &B)const{
int t = comp(u,B.u,1,n,v,B.v);
return t == 1;
}
};
void dfs2(int u,int ff,vector<int>&G){
sz[u] = 1;
if(u <= n)
G.push_back(rt[u]);
for(int i=fir[u],v;i;i=nxt[i])
if((v=tar[i])^ff && !vis[i / 2]){
rt[v] = rt[u];
ins(rt[v],1,n,cw[i]);
dfs2(v,u,G);
sz[u] += sz[v];
}
}
void Solve(int u){
if(u == -1) return;
vis[u] = 1;
rt[tar[u<<1]] = 0;
ins(rt[tar[u<<1]],1,n,cw[u<<1]);
rt[tar[u<<1|1]] = 0;
dfs2(tar[u<<1],0,G[u]);
dfs2(tar[u<<1|1],0,rG[u]);
mn[u].resize(G[u].size());
mx[u].resize(G[u].size());
pt[u].resize(G[u].size());
rep(i,0,G[u].size()-1)
mx[u][i] = rG[u].size(),
mn[u][i] = 0;
sort(rG[u].begin(),rG[u].end(),cmp);
sort(G[u].begin(),G[u].end(),cmp);
Solve(Gert(tar[u<<1],sz[tar[u<<1]]));
Solve(Gert(tar[u<<1|1],sz[tar[u<<1|1]]));
}
int Pow(int b,int k){ int r=1;for(;k;k>>=1,b=1ll*b*b%mod) if(k&1) r=1ll*r*b%mod; return r; }
int calc(int u,int v,int l,int r){
if(l == r) return 1ll * (sm[u] + sm[v]) * Pow(n , l) % mod;
int m = l+r>>1;
return (calc(lc[u],lc[v],l,m) + calc(rc[u],rc[v],m+1,r)) % mod;
}
int main(){
scanf("%d%lld",&n,&K);
rep(i,1,n) sj[i] = rand() * 1ll << 45 | rand() * 1ll << 30 | rand() * 1ll << 15 | rand();
rep(i,1,n-1){
int u,v,c;
scanf("%d%d%d",&u,&v,&c);
Node(u,v,c),Node(v,u,c);
}
cnt_p = n;
dfs0(1,0);
Solve(Gert(1,cnt_p));
LL al = 1ll * n * (n-1) / 2;
node mid;
for(int T=1;T<=100;T++){
LL t = (rand() * 1ll << 30 | rand() * 1ll << 15 | rand()) % al + 1;
rep(i,1,cnte/2){
rep(j,0,G[i].size()-1){
if(mx[i][j] - mn[i][j] >= t){ mid = node(G[i][j],rG[i][mn[i][j]+t-1]) , t = -1; break;}
else t -= mx[i][j] - mn[i][j];
}
if(t < 0) break;
}
LL hd = 0;
rep(i,1,cnte/2) if(G[i].size()){
pt[i][0] = mx[i][0];
rep(j,0,G[i].size()-1){
if(j) pt[i][j] = min(mx[i][j],pt[i][j-1]);
for(;pt[i][j] > mn[i][j] && mid < node(G[i][j],rG[i][pt[i][j]-1]);pt[i][j]--);
hd += pt[i][j];
}
}
if(hd == K)
break;
al = 0;
if(hd < K){
rep(i,1,cnte/2) rep(j,0,G[i].size()-1) mn[i][j] = pt[i][j] , al += mx[i][j] - mn[i][j];
}
else{
rep(i,1,cnte/2) rep(j,0,G[i].size()-1) mx[i][j] = pt[i][j] , al += mx[i][j] - mn[i][j];;
}
}
printf("%d\n",(calc(mid.u,mid.v,1,n)+mod)%mod);
}
剩下個題下午明天寫。