【弱校胡策】2016.4.19 LCA+LCT+莫比烏斯反演+SAM+啟發式合併
弱校胡策題解
命題人:Loi_DQS 2016.4.19
前言
來自出題人的吐槽:
T1的題目來源是去年十月份做NOIP模擬題和lcyz(聊城一中)胡策(其實也不算胡策,從他們那裡要的題)的T3,T2是去年五月份學長帶著我們在tyvj舉辦的有獎賽(http://www.tyvj.cn/Contest/187 and http://www.tyvj.cn/Contest/192)的某題。T3是上週六(2016.4.16)半夜躺在床上YY出來的原創題(出題一時爽系列)。
然後因為T3比較趕,賽中賽後導致出現了種種問題,出題人表示抱歉QAQ。
關於題目:
T1我想出成思考題,然後好像大部分人都寫得資料結構?
T2數論。
T3資料結構題,程式碼較長較噁心…
關於T1:
賽前我覺得標算是NOIP演算法可能被噴。然而這個題加個修改強行寫樹剖?並沒有加大思維難度,而是多寫了份模板…沒意思啊
然而看見大部分人都沒寫標算也A掉了,好像我想多了的樣子…
關於T3:
臥槽我出的辣麼良心的部分分都被你們一眼標算給秒了…不開心TAT
DQS和tree
題意:給一棵樹,每條邊上有邊權,每次詢問一個點到一條路徑上的某點的最大邊權最小是多少。
30分演算法(O(n^2m)):
暴力。
對於每次詢問列舉所有靈力點,然後暴力找路徑最大值取min。
60分演算法(O(mnlogn)):
優化一下暴力。
30分演算法暴力找路徑改成倍增。
100分演算法:
演算法1(O(nlogn)):
LCT。
首先,若一條路徑上多一條邊,則最大值只會變大不會變小。所以題目變成詢問c點到ab的簡單路徑的唯一路徑上邊權的最大值。現在問題在於如何求出目標路徑的另一個端點。
可以LCT。每次操作讓c點為根,則另一個端點是a和b的lca,然後就可以提取路徑了。
作為出題人,表示並不喜歡這個做法…毫無思考難度,強行資料結構,很無聊。
演算法2(O(nlogn)):
LCA。
觀察LCT的演算法,可以發現是強行改變樹結構然後找到另一個端點。靜態的情況分類討論一下就可以了。
就這三種情況:
就三種情況,有些路徑會退化。我們發現只要區分出來這三種情況就可以了,路徑另一個端點只和三個點兩兩的lca有關。
然後第一張圖是lca(a,c)=lca(b,c),答案是c到lca(a,b)的最大值…其他同理
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long LL;
const int SZ = 200010;
const int INF = 1000000010;
int head[SZ],nxt[SZ];
struct edge{
int t,d;
}l[SZ];
void build(int f,int t,int d)
{
static int tot = 1;
l[++ tot] = (edge) {t,d};
nxt[tot] = head[f]; head[f] = tot;
}
int anc[SZ][30],dist[SZ][30],deep[SZ];
void dfs(int u,int fa)
{
anc[u][0] = fa;
deep[u] = deep[fa] + 1;
for(int i = 1;anc[u][i - 1];i ++)
{
anc[u][i] = anc[anc[u][i - 1]][i - 1];
dist[u][i] = max(dist[u][i - 1],dist[anc[u][i - 1]][i - 1]);
}
for(int i = head[u];i;i = nxt[i])
{
int v = l[i].t;
if(v == fa) continue;
dist[v][0] = l[i].d;
dfs(v,u);
}
}
int ask_lca(int x,int y)
{
if(deep[x] < deep[y]) swap(x,y);
if(deep[x] > deep[y])
{
int dd = deep[x] - deep[y];
for(int i = 20;i >= 0;i --)
if(dd >> i & 1)
x = anc[x][i];
}
if(x != y)
{
for(int i = 20;i >= 0;i --)
if(anc[x][i] != anc[y][i])
x = anc[x][i],y = anc[y][i];
}
if(x == y) return x;
return anc[x][0];
}
int ask_dist(int x,int y)
{
int ans = 0;
if(deep[x] < deep[y]) swap(x,y);
if(deep[x] > deep[y])
{
int dd = deep[x] - deep[y];
for(int i = 20;i >= 0;i --)
if(dd >> i & 1)
ans = max(ans,dist[x][i]),x = anc[x][i];
}
if(x != y)
{
for(int i = 20;i >= 0;i --)
if(anc[x][i] != anc[y][i])
{
ans = max(ans,max(dist[x][i],dist[y][i]));
x = anc[x][i],y = anc[y][i];
}
}
if(x == y) return ans;
return max(ans,max(dist[x][0],dist[y][0]));
}
int main()
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
int n;
scanf("%d",&n);
for(int i = 1;i < n;i ++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
build(a,b,c); build(b,a,c);
}
dfs(1,0);
int m;
scanf("%d",&m);
while(m --)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
int ab = ask_lca(a,b),ac = ask_lca(a,c),bc = ask_lca(b,c);
if(ac == bc)
printf("%d\n",ask_dist(ab,c));
else if(ac == ab)
printf("%d\n",ask_dist(bc,c));
else
printf("%d\n",ask_dist(ac,c));
}
fclose(stdin); fclose(stdout);
return 0;
}
/*
8
1 2 3
1 3 4
3 8 1
7 2 5
5 7 7
4 6 10
2 4 2
233
1 6 5
*/
DQS和number
求
100分演算法(O(n)+O(Tsqrt(n))):
然後就可以反演了,推一下式子就行了。詳見std。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int SZ = 2000010;
const int MAXN = 1500000;
bool vis[SZ];
int pri[SZ],f[SZ],F[SZ],a[SZ];
LL sum[SZ];
void shai()
{
a[1] = f[1] = F[1] = 1;
for(int i = 2,tot = 0;i <= MAXN;i ++)
{
if(!vis[i])
{
pri[++ tot] = i;
f[i] = i;
F[i] = i - 1;
a[i] = i;
}
for(int j = 1,m;j <= tot && (m = i * pri[j]) <= MAXN;j ++)
{
vis[m] = 1;
if(i % pri[j] == 0)
{
f[m] = 0;
F[m] = -F[i / a[i]] * f[a[i]];
a[m] = a[i] * pri[j];
break;
}
else
{
f[m] = f[i] == 0 ? 0 : m;
F[m] = F[i] * F[pri[j]];
a[m] = pri[j];
}
}
}
for(int i = 1;i <= MAXN;i ++)
sum[i] = sum[i - 1] + F[i];
}
LL ask(int n,int m)
{
LL ans = 0;
if(n > m) swap(n,m);
for(int i = 1,r;i <= n;i = r + 1)
{
r = min(n / (n / i),m / (m / i));
ans += (sum[r] - sum[i - 1]) * (n / i) * (m / i);
}
return ans;
}
int main()
{
freopen("number.in","r",stdin);
freopen("number.out","w",stdout);
int T;
scanf("%d",&T);
shai();
while(T --)
{
int n,m;
scanf("%d%d",&n,&m);
printf("%lld\n",ask(n,m));
}
fclose(stdin); fclose(stdout);
return 0;
}
DQS和trie
題意:給一個trie樹,它的子串定義為從任意一個節點向下走任意步走到某個節點所形成的字串。要求支援:新增一個子樹,詢問一個串在樹中出現幾次,詢問當前樹有多少個本質不同的子串。
20分演算法(O(n^2m)):
暴力hash。
40分演算法(O(n)或者O(nlogn)):
SDOI2016R1D2T1生成魔咒,線段樹+SA或者SAM都可做。
50分演算法(O(mlogn)):
樹形態隨機,所以還是hash,每次加點最多加logn個串,可以暴力。
60分演算法(O(mlogn)):
發現每次長出子樹的大小不會超過當前樹的大小,這樣可以暴力插入,和啟發式合併的複雜度分析差不多,一個log。
詢問次數很少,opt=1還是可以hash(會不會爆空間?沒試過),opt=3可以對每條鏈做一遍kmp。
100分演算法(O(mlog^2n)):
SAM+LCT+啟發式合併。
好像和Bzoj2555有點像?加強版?。
LCT維護一下par樹,這樣就可以log^2n的時間內維護新增子樹。記錄變數ans維護p -> val – p -> par -> val的和,trie上建立SAM,和普通的串上一樣,dfs一下就行了。操作三把串在SAM上跑一遍就行了。
程式碼較長,考場上寫出來簡直神
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int SZ = 400010;
const int MAXN = 400000;
const int INF = 1000000010;
namespace LCT{
struct node{
node *ch[2],*f;
int sz,sum,v;
bool rev;
int add;
void maintain()
{
sum = v + ch[0] -> sum + ch[1] -> sum;
sz = ch[0] -> sz + 1 + ch[1] -> sz;
}
void pushdown();
int dir() { return f -> ch[1] == this; }
bool isroot() { return f -> ch[0] != this && f -> ch[1] != this;}
void setc(node* x,int d) { (ch[d] = x) -> f = this; }
}T[SZ], *tree[SZ], *null;
int Tcnt = 0;
node* newnode(int x)
{
node *k = T + (Tcnt ++);
k -> ch[1] = k -> ch[0] = k -> f = null;
k -> sz = 1;
k -> sum = k -> v = x;
k -> add = k -> rev = 0;
return k;
}
void pushrev(node *p)
{
if(p == null) return;
p -> rev ^= 1;
swap(p -> ch[0],p -> ch[1]);
}
void pushadd(node *p,int d)
{
if(p == null) return;
p -> v += d;
p -> sum += d * p -> sz;
p -> add += d;
}
void node :: pushdown()
{
if(rev)
pushrev(ch[0]),pushrev(ch[1]),rev = 0;
if(add)
pushadd(ch[0],add),pushadd(ch[1],add),add = 0;
}
node *S[SZ];
void pushpath(node *p)
{
int top = 0;
while(!p -> isroot())
S[++ top] = p,p = p -> f;
S[++ top] = p;
while(top) S[top --] -> pushdown();
}
void rotate(node *p)
{
node *fa = p -> f;
int d = p -> dir();
p -> f = fa -> f;
if(!fa -> isroot())
p -> f -> ch[fa -> dir()] = p;
fa -> ch[d] = p -> ch[d ^ 1];
if(fa -> ch[d] != null)
fa -> ch[d] -> f = fa;
p -> setc(fa,d ^ 1);
fa -> maintain(); p -> maintain();
}
void splay(node *p)
{
pushpath(p);
while(!p -> isroot())
{
if(p -> f -> isroot()) rotate(p);
else
{
if(p -> dir() == p -> f -> dir())
rotate(p -> f),rotate(p);
else
rotate(p),rotate(p);
}
}
p -> maintain();
}
void access(node *p)
{
node *last = null;
while(p != null)
{
splay(p);
p -> ch[1] = last; p -> maintain();
last = p;
p = p -> f;
}
}
void makeroot(node *p)
{
access(p); splay(p); pushrev(p);
}
void cut(node *x,node *y)
{
makeroot(x); access(y); splay(y);
y -> ch[0] = x -> f = null; y -> maintain();
}
void link(node *x,node *y)
{
makeroot(x);
x -> f = y;
}
void add(node *x,node *y,int d)
{
makeroot(x); access(y); splay(y);
pushadd(y,d);
}
int ask(node *x,node *y)
{
makeroot(x); access(y); splay(y);
return y -> sum;
}
void init()
{
null = newnode(0);
null -> sz = 0;
for(int i = 0;i <= MAXN;i ++)
tree[i] = newnode(0);
}
};
namespace SAM{
struct sam_node{
sam_node *ch[3],*par;
int val;
}T[SZ], *root;
LCT :: node* getnode(sam_node* x)
{
return LCT :: tree[x - T + 1];
}
int Tcnt = 0;
sam_node* newnode(int x)
{
sam_node* k = T + (Tcnt ++);
k -> val = x; k -> par = 0;
memset(k -> ch,0,sizeof(k -> ch));
return k;
}
LL ans = 0;
LL get_ans(sam_node *p)
{
return p -> val - p -> par -> val;
}
sam_node* sam_insert(int x,sam_node* last)
{
sam_node *p = last,*np = newnode(last -> val + 1);
while(p && !p -> ch[x])
p -> ch[x] = np,p = p -> par;
if(!p)
{
np -> par = root;
ans += get_ans(np);
LCT :: link(getnode(np),getnode(root));
}
else
{
sam_node *q = p -> ch[x];
if(q -> val == p -> val + 1)
{
np -> par = q;
ans += get_ans(np);
LCT :: link(getnode(np),getnode(q));
}
else
{
sam_node *nq = newnode(p -> val + 1);
LCT :: cut(getnode(q),getnode(q -> par));
LCT :: link(getnode(nq),getnode(q -> par));
LCT :: link(getnode(q),getnode(nq));
LCT :: link(getnode(np),getnode(nq));
LCT :: pushpath(getnode(q));
getnode(nq) -> v = getnode(q) -> v;
memcpy(nq -> ch,q -> ch,sizeof(nq -> ch));
nq -> par = q -> par; ans += get_ans(nq);
np -> par = nq; ans += get_ans(np);
ans -= get_ans(q); q -> par = nq; ans += get_ans(q);
while(p && p -> ch[x] == q)
p -> ch[x] = nq,p = p -> par;
}
}
LCT :: add(getnode(np),getnode(root),1);
return np;
}
int ask(char s[])
{
int l = strlen(s);
sam_node *p = root;
for(int i = 0;i < l;i ++)
{
int x = s[i] - 'a';
if(!p -> ch[x]) return 0;
p = p -> ch[x];
}
LCT :: pushpath(getnode(p));
return getnode(p) -> v;
}
void init()
{
root = newnode(0);
}
}
int head[SZ],nxt[SZ],tot = 1;
struct edge{
int t,d;
}l[SZ];
void build(int f,int t,int d)
{
l[++ tot] = (edge) {t,d};
nxt[tot] = head[f];
head[f] = tot;
}
SAM :: sam_node* rt[SZ];
void dfs(int u,int fa)
{
for(int i = head[u];i;i = nxt[i])
{
int v = l[i].t;
if(v == fa) continue;
rt[v] = SAM :: sam_insert(l[i].d,rt[u]);
dfs(v,u);
}
}
int n = 1;
void insert(int rot)
{
int sz;
scanf("%d",&sz);
for(int i = 1;i < sz;i ++)
{
int a,b;
char c[2];
scanf("%d%d%s",&a,&b,c);
build(a,b,c[0] - 'a'); build(b,a,c[0] - 'a');
}
dfs(rot,0);
tot = 1; head[rot] = 0;
for(int i = n + 1;i < n + sz;i ++)
head[i] = 0;
n += sz - 1;
}
char s[SZ];
int main()
{
freopen("trie.in","r",stdin);
freopen("trie.out","w",stdout);
SAM :: init();
LCT :: init();
int xx; scanf("%d",&xx);
rt[1] = SAM :: root;
insert(1);
int m;
scanf("%d",&m);
while(m --)
{
int opt;
scanf("%d",&opt);
if(opt == 1)
printf("%lld\n",SAM :: ans);
else if(opt == 2)
{
int r;
scanf("%d",&r);
insert(r);
}
else
{
scanf("%s",s);
printf("%d\n",SAM :: ask(s));
}
}
fclose(stdin); fclose(stdout);
return 0;
}
/*
4
1 2 a
1 3 b
2 4 b
5
1
2 2 4
2 5 b
2 6 c
5 7 b
3 ab
2 6 3
6 8 a
6 9 b
1
4
1 2 a
1 3 b
2 4 b
6
1
2 2 4
2 5 b
2 6 c
5 7 b
1
3 ab
2 6 3
6 8 a
6 9 b
1
*/