弱校胡策题解
命题人: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数据结构题,代码较长较恶心…
赛前我觉得标算是NOIP算法可能被喷。然而这个题加个修改强行写树剖?并没有加大思维难度,而是多写了份模板…没意思啊
然而看见大部分人都没写标算也A掉了,好像我想多了的样子…
卧槽我出的辣么良心的部分分都被你们一眼标算给秒了…不开心TAT
题意:给一棵树,每条边上有边权,每次询问一个点到一条路径上的某点的最大边权最小是多少。
暴力。
对于每次询问枚举所有灵力点,然后暴力找路径最大值取min。
优化一下暴力。
30分算法暴力找路径改成倍增。
LCT。
首先,若一条路径上多一条边,则最大值只会变大不会变小。所以题目变成询问c点到ab的简单路径的唯一路径上边权的最大值。现在问题在于如何求出目标路径的另一个端点。
可以LCT。每次操作让c点为根,则另一个端点是a和b的lca,然后就可以提取路径了。
作为出题人,表示并不喜欢这个做法…毫无思考难度,强行数据结构,很无聊。
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 */
求 ∑i<=n∑j<=mf(gcd(i,j)) , f(n)=n是无平方因子数?n:0
然后就可以反演了,推一下式子就行了。详见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;
}
题意:给一个trie树,它的子串定义为从任意一个节点向下走任意步走到某个节点所形成的字符串。要求支持:添加一个子树,询问一个串在树中出现几次,询问当前树有多少个本质不同的子串。
暴力hash。
SDOI2016R1D2T1生成魔咒,线段树+SA或者SAM都可做。
树形态随机,所以还是hash,每次加点最多加logn个串,可以暴力。
发现每次长出子树的大小不会超过当前树的大小,这样可以暴力插入,和启发式合并的复杂度分析差不多,一个log。
询问次数很少,opt=1还是可以hash(会不会爆空间?没试过),opt=3可以对每条链做一遍kmp。
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
*/