[弱省胡策] \(\sf OVOO\)
题目描述
\(\sf Oxide\) 有一棵 \(n\) 个点的树,每条边有一个权值。
定义一个连通块为一个点集与使这些点连通的所有边(这些点必须连通),定义一个连通块的权值为这个连通块的边权和(如果一个连通块只包含一个点那么它的权值为 \(0\))。
\(\sf Oxide\) 希望你求出包含 \(1\) 号点的所有连通块中权值第 \(k\) 小的连通块的权值。如果不存在则输出满足条件最大连通块的权值。
\(n,k\le 10^5\).
解法
像这种 要求包含某连通块(因为可以一直合并而 不分裂)且计算第 \(k\) 小(直接求第 \(k\) 大是不可行的,这不满足一直合并的性质)的题目,我们可以首先考虑左偏树。
考虑从 \(1\) 号点开始拓展,那么有两种操作:在当前连通块向外拓展的边中选权值最小的边拓展;去掉边集中权值最小的一条边(前提是去掉这条边只会使连通块少一个点),在当前连通块向外拓展的边中选权值最小的边拓展。
再讲一讲具体实现的细节。用一棵左偏树维护待选边集,根节点表示最小权值边,再开一个优先队列维护已选边集权值和最小的连通块。每次取出连通块的根 \(u\),考虑如何实现那两种操作:
- 为了不把连通块算重,钦定根表示的边必选,也即它不会在操作二中被去掉,所以合并根的左右子树,将根从左偏树中删掉,但保留根对连通块的贡献;
- 为了不把连通块算重,钦定根表示的边必不选,所以还是合并根的左右子树,将根从左偏树中删掉,并去掉根对连通块的贡献。
由于合并时不能将被合并两点信息破坏,所以需要可持久化。时间复杂度 \(\mathcal O(n\log (n\log n)+k\cdot (\log k+\log (n\log n)))\)。算得很粗糙……
代码
#include <cstdio>
#define print(x,y) write(x),putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while((s=getchar())>'9' or s<'0')
f|=(s=='-');
while(s>='0' and s<='9')
x=(x<<1)+(x<<3)+(s^48),
s=getchar();
return f?-x:x;
}
template <class T>
inline void write(const T x) {
if(x<0) {
putchar('-'),write(-x);
return;
}
if(x>9) write(x/10);
putchar(x%10^48);
}
#include <queue>
#include <iostream>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
int n,k,d[maxn*45],w[maxn*45];
int ls[maxn*45],rs[maxn*45];
int rt[maxn],idx,to[maxn*45];
struct node {
int u; ll w;
node() {}
node(int U,ll W):u(U),w(W) {}
bool operator < (const node &t) const {
return w>t.w;
}
};
priority_queue <node> q;
int NewNode(int v,int W) {
w[++idx]=W,to[idx]=v;
return idx;
}
void Give(int o,int x) {
ls[o]=ls[x],rs[o]=rs[x];
w[o]=w[x],to[o]=to[x];
d[o]=d[x];
}
int merge(int x,int y) {
if(!x or !y)
return x|y;
int o=++idx;
if(w[x]>w[y]) swap(x,y);
Give(o,x);
rs[o]=merge(rs[o],y);
if(d[rs[o]]>d[ls[o]])
swap(ls[o],rs[o]);
d[o]=d[rs[o]]+1;
return o;
}
int main() {
n=read(9),k=read(9)-1;
for(int i=2;i<=n;++i) {
int fa=read(9),w=read(9);
rt[fa]=merge(rt[fa],NewNode(i,w));
}
node t=node(0,0);
q.push(node(rt[1],w[rt[1]]));
while(k--) {
if(q.empty()) break;
t=q.top(); q.pop();
int nxt=merge(ls[t.u],rs[t.u]);
if(nxt)
q.push(node(nxt,t.w+w[nxt]-w[t.u]));
nxt=merge(nxt,rt[to[t.u]]);
if(nxt)
q.push(node(nxt,t.w+w[nxt]));
}
print(t.w,'\n');
return 0;
}
\(\text{[BZOJ 4212] }\)神牛的养成计划
解法
一个暴力的解法是对于每个 \(\rm dna\) 序列哈希一下前缀与后缀,然后对于每个询问,\(\mathcal O(n)\) 地枚举。
考虑更优的解法。\(\rm trie\) 树可以快速求出包含某前缀的字符串个数,但是询问相当于要求同时具有两个前缀。可以先将 \(\rm dna\) 序列按字典序排序正向插入 \(\rm trie\) 树(或者也可以先插入 \(\rm trie\) 树,再排序:\(\rm Link.\)),得到包含某前缀的序列下标区间,然后按排序倒序插入 \(\rm trie\) 树(可持久化),最后查询前缀对应下标区间内,有多少个 \(\rm dna\) 序列的后缀为给定后缀即可。时间复杂度是 \(\mathcal O(26\cdot 2\cdot 10^6)\).
代码
\(\mathcal O(nm)\):
#include <cstdio>
#define print(x,y) write(x),putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while((s=getchar())>'9' or s<'0')
f|=(s=='-');
while(s>='0' and s<='9')
x=(x<<1)+(x<<3)+(s^48),
s=getchar();
return f?-x:x;
}
template <class T>
inline void write(const T x) {
if(x<0) {
putchar('-');
write(-x);
return;
}
if(x>9) write(x/10);
putchar(x%10^48);
}
#include <cstring>
const int maxn=3200002;
const int mod=998362291,bas=233;
int n,m,len[2005];
char str[maxn];
#define readStr(w) do { \
w=new char*[n+1]; \
for(int i=1;i<=n;++i) { \
scanf("%s",str+1); \
len[i]=strlen(str+1); \
w[i]=new char[len[i]+1]; \
for(int j=1;j<=len[i];++j) \
w[i][j]=str[j]; \
} \
} while(0);
#define getMemo(w) do { \
w=new int*[n+1]; \
for(int i=1;i<=n;++i) \
w[i]=new int[len[i]+1]; \
} while(0);
char **s;
int **hs,**hs1;
void init() {
int maxlen=0;
for(int i=1;i<=n;++i) {
maxlen=(maxlen>len[i])?maxlen:len[i];
hs[i][1]=s[i][1]-'a';
for(int j=2;j<=len[i];++j)
hs[i][j]=(1ll*hs[i][j-1]*bas%mod+s[i][j]-'a')%mod;
hs1[i][len[i]]=s[i][len[i]]-'a';
for(int j=len[i]-1;j>=1;--j)
hs1[i][j]=(1ll*hs1[i][j+1]*bas%mod+s[i][j]-'a')%mod;
}
}
int main() {
n=read(9); readStr(s);
getMemo(hs); getMemo(hs1);
init();
m=read(9);
int l1,l2,Pre,Suf,ans=0,x;
while(m--) {
Pre=Suf=0;
scanf("%s",str+1);
l1=strlen(str+1);
for(int i=1;i<=l1;++i)
x=(str[i]-'a'+ans)%26,
Pre=(1ll*Pre*bas%mod+x)%mod;
scanf("%s",str+1);
l2=strlen(str+1);
for(int i=l2;i>=1;--i)
x=(str[i]-'a'+ans)%26,
Suf=(1ll*Suf*bas%mod+x)%mod;
ans=0;
for(int i=1;i<=n;++i) {
if(l1>len[i] or l2>len[i])
continue;
if(
Pre==hs[i][l1] and
Suf==hs1[i][len[i]-l2+1])
++ans;
}
print(ans,'\n');
}
return 0;
}
\(\mathcal O(26\cdot 2\cdot 10^6)\):
#include <cstdio>
#define print(x,y) write(x),putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while((s=getchar())>'9' or s<'0')
f|=(s=='-');
while(s>='0' and s<='9')
x=(x<<1)+(x<<3)+(s^48),
s=getchar();
return f?-x:x;
}
template <class T>
inline void write(const T x) {
if(x<0) {
putchar('-');
write(-x);
return;
}
if(x>9) write(x/10);
putchar(x%10^48);
}
#include <iostream>
#include <algorithm>
using namespace std;
typedef pair <int,int> type;
const int maxn=2e6+2;
int n,idx,u_idx,t[2][maxn][26];
short siz[maxn],mx[maxn],mn[maxn];
int rt[2001],ans;
string s[2001];
void ins(int &o,int pre,int id) {
int p; p=o=++u_idx;
int len=s[id].length();
for(int i=len-1;i>=0;--i) {
int d=s[id][i]-'a';
for(int k=0;k<26;++k)
t[1][p][k]=t[1][pre][k];
t[1][p][d]=++u_idx;
p=t[1][p][d],pre=t[1][pre][d];
siz[p]=siz[pre]+1;
}
for(int k=0;k<26;++k)
t[1][p][k]=t[1][pre][k];
}
void ins(short id) {
int p=0,len=s[id].length();
for(int i=0;i<len;++i) {
int d=s[id][i]-'a';
if(!t[0][p][d])
t[0][p][d]=++idx,
mn[idx]=3000;
p=t[0][p][d];
mx[p]=max(mx[p],id);
mn[p]=min(mn[p],id);
}
}
type ask() {
int len=s[0].length(),p=0;
for(int i=0;i<len;++i) {
int d=s[0][i]-'a';
if(!t[0][p][d])
return make_pair(0,0);
p=t[0][p][d];
}
return make_pair(mn[p],mx[p]);
}
int ask(int l,int r) {
int len=s[0].length(),ret=0;
for(int i=len-1;i>=0;--i) {
int d=s[0][i]-'a';
ret=siz[t[1][r][d]]-siz[t[1][l][d]];
if(!ret)
return 0;
r=t[1][r][d],l=t[1][l][d];
}
return ret;
}
void Decode() {
cin>>s[0];
int len=s[0].length();
for(int i=0;i<len;++i)
s[0][i]=(s[0][i]-'a'+ans)%26+'a';
}
int main() {
n=read(9);
for(int i=1;i<=n;++i)
cin>>s[i];
sort(s+1,s+n+1);
for(int i=1;i<=n;++i)
ins(i);
for(int i=1;i<=n;++i)
ins(rt[i],rt[i-1],i);
type ran;
for(int m=read(9);m;--m) {
Decode();
ran=ask();
Decode();
if(ran.first==0) {
puts("0");
continue;
}
print(ans=ask(rt[ran.first-1],rt[ran.second]),'\n');
}
return 0;
}