跟Censoring S一样
考虑用栈
#include
#define in Read()
using namespace std;
inline int in{
int i=0,f=1;char ch;
while(!isdigit(ch)&&ch!='-')ch=getchar();
if(ch=='-')ch=getchar(),f=-1;
while(isdigit(ch))i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int NNN=1e5+10;
char s[NNN],t[NNN];
int n,sz;
struct Trie{
int s[26];
int val,fail,last;
}ch[NNN];
int sta[NNN],top;
int rt[NNN];//记录栈内元素对应trie里的节点
inline void update(){
int p=0,len=strlen(t+1);
for(int i=1;i<=len;++i){
int c=t[i]-'a';
if(!ch[p].s[c]) ch[p].s[c]=++sz;
p=ch[p].s[c];
}
ch[p].val=len;
}
inline void get_fail(){
queue<int>q;
int p=0;
for(int i=0;i<=26;++i){
p=ch[0].s[i];
if(!p) continue;
q.push(p);
}
while(!q.empty()){
int u=q.front();q.pop();
for(int i=0;i<26;++i){
p=ch[u].s[i];
if(!p){
ch[u].s[i]=ch[ch[u].fail].s[i];
continue;
}
q.push(p);
int v=ch[u].fail;
while(v&&!ch[v].s[i]) v=ch[v].fail;
ch[p].fail=ch[v].s[i];
ch[p].last=ch[ch[p].fail].val?ch[p].fail:ch[ch[p].fail].last;
}
}
}
inline void query(){
int ans=0,len=strlen(s+1),p=0;
for(int i=1;i<=len;++i){
int c=s[i]-'a';
p=ch[p].s[c];
rt[i]=p;
sta[++top]=i;
if(ch[p].val){
top-=ch[p].val;
if(!top) p=0;
else p=rt[sta[top]];
}
}
}
int main(){
scanf("%s",s+1);
n=in;
for(int i=1;i<=n;++i){
scanf("%s",t+1);
update();
}
get_fail();
query();
for(int i=1;i<=top;++i)
putchar(s[sta[i]]);
return 0;
}
那个
if(!top) p=0; else p=rt[sta[top]];
加不加无所谓,加要快一点
多次搜索肯定炸
考虑把文章拼在一块,单词之间用奇怪的字符分开
注意判重
#include
#define in Read()
using namespace std;
inline int in{
int i=0,f=1;char ch;
while(!isdigit(ch)&&ch!='-')ch=getchar();
if(ch=='-')ch=getchar(),f=-1;
while(isdigit(ch))i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int NNN=1e7+10;
int n,sz,lt;
char s[NNN],t[NNN];
struct Trie{
int s[26];
int fail,last,id;
}ch[NNN];
int ans[NNN],same[NNN];
inline void update(int id){
int p=0,len=strlen(s+1);
for(int i=1;i<=len;++i){
int c=s[i]-'a';
if(!ch[p].s[c]) ch[p].s[c]=++sz;
p=ch[p].s[c];
t[++lt]=s[i];
}
if(ch[p].id) same[id]=ch[p].id;
else ch[p].id=id;
t[++lt]='#';
return;
}
inline void get_fail(){
queue<int>q;
int p=0;
for(int i=0;i<26;++i){
p=ch[0].s[i];
if(!p) continue;
q.push(p);
}
while(!q.empty()){
int u=q.front();q.pop();
for(int i=0;i<26;++i){
p=ch[u].s[i];
if(!p){
ch[u].s[i]=ch[ch[u].fail].s[i];
continue;
}
q.push(p);
int v=ch[u].fail;
while(v&&!ch[v].s[i]) v=ch[v].fail;
ch[p].fail=ch[v].s[i];
ch[p].last=ch[ch[p].fail].id?ch[p].fail:ch[ch[p].fail].last;
}
}
}
inline void query(){
int p=0;
for(int i=1;i<=lt;++i){
int c=t[i]-'a';
p=ch[p].s[c];
int tmp=0;
if(ch[p].id) tmp=p;
else if(ch[p].last) tmp=ch[p].last;
while(tmp){
++ans[ch[tmp].id];
tmp=ch[tmp].last;
}
}
}
int main(){
n=in;
for(int i=1;i<=n;++i){
scanf("%s",s+1);
update(i);
}
get_fail();
query();
for(int i=1;i<=n;++i){
if(!same[i])printf("%d\n",ans[i]);
else printf("%d\n",ans[same[i]]);
}
return 0;
}
ACM上DP
常见套路:设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示长度为i,末节点在ACM上节点j的最佳答案
d p [ i ] [ s o n [ p ] ] = max { d p [ i − 1 ] [ p ] + v a l [ s o n [ p ] ] } dp[i][son[p]]=\max\{dp[i-1][p]+val[son[p]]\} dp[i][son[p]]=max{dp[i−1][p]+val[son[p]]}
注意fail树上下放val值
注意枚举的范围,从根节点开始
#include
#define in Read()
using namespace std;
inline int in{
int i=0,f=1;char ch;
while(!isdigit(ch)&&ch!='-')ch=getchar();
if(ch=='-')ch=getchar(),f=-1;
while(isdigit(ch))i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int NNN=1e3+10;
const int INF=1e9;
int n,k,sz,res;
char s[NNN];
struct Trie{
int s[3];
int val,fail,last;
}ch[NNN];
int dp[NNN][NNN];
inline void update(){
int p=0,len=strlen(s+1);
for(int i=1;i<=len;++i){
int c=s[i]-'A';
if(!ch[p].s[c]) ch[p].s[c]=++sz;
p=ch[p].s[c];
}
++ch[p].val;
}
inline void get_fail(){
queue<int>q;
int p=0;
for(int i=0;i<3;++i){
p=ch[0].s[i];
if(!p) continue;
q.push(p);
ch[p].fail=ch[p].last=0;
}
while(!q.empty()){
int u=q.front();q.pop();
for(int i=0;i<3;++i){
p=ch[u].s[i];
if(!p){
ch[u].s[i]=ch[ch[u].fail].s[i];
continue;
}
q.push(p);
int v=ch[u].fail;
while(v&&!ch[v].s[i]) v=ch[v].fail;
ch[p].fail=ch[v].s[i];
ch[p].last=ch[ch[p].fail].val?ch[p].fail:ch[ch[p].fail].last;
}
ch[u].val+=ch[ch[u].fail].val;
}
}
inline void query(){
for(int i=0;i<=k;++i)
for(int j=0;j<=sz;++j)
dp[i][j]=-INF;
dp[0][0]=0;
for(int len=1;len<=k;++len)
for(int p=0;p<=sz;++p)
for(int j=0;j<3;++j)
dp[len][ch[p].s[j]]=max(dp[len][ch[p].s[j]],dp[len-1][p]+ch[ch[p].s[j]].val);
}
int main(){
n=in,k=in;
for(int i=1;i<=n;++i){
scanf("%s",s+1);
update();
}
get_fail();
query();
for(int i=1;i<=sz;++i)
res=max(res,dp[k][i]);
printf("%d\n",res);
return 0;
}
跟T3没什么区别,双倍经验吧(复制粘贴打法好)
考虑ACM上DP
设 d p [ i ] [ j ] [ 0 / 1 ] dp[i][j][0/1] dp[i][j][0/1]表示文章长 i i i,末节点为 j j j,不可读或可读的文章
易知 p → s o n [ p ] p\to son[p] p→son[p]每一个字母都会覆盖到
考虑转移
#include
#define in Read()
#define int long long
using namespace std;
inline int in{
int i=0,f=1;char ch;
while(!isdigit(ch)&&ch!='-')ch=getchar();
if(ch=='-')ch=getchar(),f=-1;
while(isdigit(ch))i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int NNN=2e4+10;
const int MOD=1e4+7;
int n,m,sz;
char s[NNN];
struct Trie{
int s[26];
int fail,last,val;
}ch[NNN];
int dp[200][NNN][2],ans;
inline void update(){
int p=0,len=strlen(s+1);
for(int i=1;i<=len;++i){
if(!ch[p].s[s[i]-'A']) ch[p].s[s[i]-'A']=++sz;
p=ch[p].s[s[i]-'A'];
}
ch[p].val=1;
}
inline void get_fail(){
queue<int>q;
int p=0;
for(int i=0;i<26;++i){
p=ch[0].s[i];
if(!p) continue;
q.push(p);
}
while(!q.empty()){
int u=q.front();q.pop();
for(int i=0;i<26;++i){
p=ch[u].s[i];
if(!p){
ch[u].s[i]=ch[ch[u].fail].s[i];
continue;
}
q.push(p);
int v=ch[u].fail;
while(v&&!ch[v].s[i]) v=ch[v].fail;
ch[p].fail=ch[v].s[i];
ch[p].last=ch[ch[p].fail].val?ch[p].fail:ch[ch[p].fail].last;
}
ch[u].val|=ch[ch[u].fail].val;
}
}
inline void query(){
memset(dp,0,sizeof(dp));
dp[0][0][0]=1;
for(int i=1;i<=m;++i)
for(int j=0;j<=sz;++j)
for(int k=0;k<26;++k){
if(ch[ch[j].s[k]].val)
dp[i][ch[j].s[k]][1]=(dp[i][ch[j].s[k]][1]+dp[i-1][j][0]+dp[i-1][j][1])%MOD;
else{
dp[i][ch[j].s[k]][0]=(dp[i][ch[j].s[k]][0]+dp[i-1][j][0])%MOD;
dp[i][ch[j].s[k]][1]=(dp[i][ch[j].s[k]][1]+dp[i-1][j][1])%MOD;
}
}
}
signed main(){
n=in,m=in;
for(int i=1;i<=n;++i){
scanf("%s",s+1);
update();
}
get_fail();
query();
for(int i=0;i<=sz;++i)
ans=(ans+dp[m][i][1])%MOD;
printf("%lld\n",ans);
return 0;
}
看题看清楚,写数写仔细
模数写错了,D我半小时
fail 树上下放 val ,数位dp
数位dp注意维护前导 0 和上限
#include
#define in Read()
#define int long long
using namespace std;
inline int in{
int i=0,f=1;char ch;
while(!isdigit(ch)&&ch!='-')ch=getchar();
if(ch=='-')ch=getchar(),f=-1;
while(isdigit(ch))i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int NNN=3e3+5;
const int MOD=1e9+7;
int m,top,sz;
char n[NNN],s[NNN];
struct Trie{
int s[10];
int val,fail;
}ch[NNN];
int f[NNN][NNN];
inline void update(){
int p=0,len=strlen(s+1);
for(int i=1;i<=len;++i){
if(!ch[p].s[s[i]-'0']) ch[p].s[s[i]-'0']=++sz;
p=ch[p].s[s[i]-'0'];
}
ch[p].val=1;
return;
}
inline void get_fail(){
int p=0;queue<int>q;
for(int i=0;i<10;++i){
p=ch[0].s[i];
if(!p) continue;
q.push(p);
}
while(!q.empty()){
int u=q.front();q.pop();
for(int i=0;i<10;++i){
p=ch[u].s[i];
if(!p){
ch[u].s[i]=ch[ch[u].fail].s[i];
continue;
}
q.push(p);
int v=ch[u].fail;
while(v&&!ch[v].s[i]) v=ch[v].fail;
ch[p].fail=ch[v].s[i];
}
ch[u].val+=ch[ch[u].fail].val;
}
}
inline int dp(int pos/*数位*/,int p/*节点*/,int lim/*上限*/,int lead/*前导零*/){
if(pos>top) return !lead;//考虑前导零
if(lim&&f[pos][p]!=-1) return f[pos][p];
int res=0;
for(int i=0;i<10;++i){
if(!lim&&i>n[pos]-'0') break;
if(lead){
if(!ch[ch[0].s[i]].val)
res=(res+dp(pos+1,ch[0].s[i],lim||i<n[pos]-'0',lead&&!i))%MOD;
}
else{
if(!ch[ch[p].s[i]].val)
res=(res+dp(pos+1,ch[p].s[i],lim||i<n[pos]-'0',0))%MOD;
}
}
if(lim&&!lead) f[pos][p]=res;
return res;
}
signed main(){
scanf("%s",n+1);
top=strlen(n+1);
m=in;
for(int i=1;i<=m;++i){
scanf("%s",s+1);
update();
}
get_fail();
memset(f,-1,sizeof(f));
printf("%lld\n",dp(1,0,0,1));
return 0;
}
定义第 x x x个打印的字符串为模式串,第 y y y个打印的字符串为文本串(字符串问题)
可以观察到,模式串末节点被 fail 指了就会有一个贡献
而只有文本串上的、指向模式串末节点的 fail 才有贡献
考虑点亮文本串上的所有节点,跑 fail
众所周知, fail 是棵树
考虑树剖,这玩意儿优 个鬼
DFS序 + 树状数组比它优
#include
#define in Read()
using namespace std;
inline int in{
int i=0,f=1;char ch;
while(!isdigit(ch)&&ch!='-')ch=getchar();
if(ch=='-')ch=getchar(),f=-1;
while(isdigit(ch))i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int NNN=1e6+10;
char txt[NNN];
struct Trie{
int s[26];
int val,fail;
int dep;//fail树上深度
}ch[NNN];
int f[NNN];//trie上节点父亲
int en[NNN],n;//每个字符串的末节点
int sz;
inline void build(){
scanf("%s",txt+1);
int p=0,len=strlen(txt+1),c;
for(int i=1;i<=len;++i){
if(txt[i]=='B'){
p=f[p];
continue;
}
if(txt[i]=='P'){
ch[p].val=++n;
en[n]=p;
continue;
}
c=txt[i]-'a';
if(!ch[p].s[c]) ch[p].s[c]=++sz;
f[ch[p].s[c]]=p;
p=ch[p].s[c];
}
return;
}
struct Road{
int nxt,to;
}road[NNN<<1];
int tot_road,first[NNN];
inline void add(int u,int v){
++tot_road;
road[tot_road].nxt=first[u];
first[u]=tot_road;
road[tot_road].to=v;
}
inline void get_fail(){
queue<int>q;
int p=0;
for(int i=0;i<26;++i){
p=ch[0].s[i];
if(!p) continue;
q.push(p);
ch[p].fail=0;
ch[p].dep=ch[0].dep+1;
}
while(!q.empty()){
int u=q.front();q.pop();
for(int i=0;i<26;++i){
p=ch[u].s[i];
if(!p){
ch[u].s[i]=ch[ch[u].fail].s[i];
continue;
}
q.push(p);
int v=ch[u].fail;
while(v&&!ch[v].s[i]) v=ch[v].fail;
ch[p].fail=ch[v].s[i];
ch[p].dep=ch[u].dep+1;
}
add(u,ch[u].fail),add(ch[u].fail,u);
}
}
int dfn[NNN];//DFS序/时间戳
int siz[NNN];//子树大小
int tot;//DFS序
inline void Dfs(int fa,int u){
int v;
dfn[u]=++tot;
siz[u]=1;
for(int e=first[u];e;e=road[e].nxt){
v=road[e].to;
if(!(v^fa)) continue;
Dfs(u,v);
siz[u]+=siz[v];
}
}
int m;
struct Query{
int x,y,id,ans;
}qur[NNN];
int where[NNN];//记录排序后每种文本串的第一个询问
inline bool cmp1(const Query &u,const Query &v){return u.y<v.y;}
inline bool cmp2(const Query &u,const Query &v){return u.id<v.id;}
inline void get_query(){
m=in;
for(int i=1;i<=m;++i){
qur[i].x=in,qur[i].y=in;
qur[i].id=i;
}
sort(qur+1,qur+m+1,cmp1);
for(int i=1;i<=m;++i)
if(qur[i].y^qur[i-1].y)
where[qur[i].y]=i;
}
int tree[NNN];
inline int lowbit(int x){return x&(-x);}
inline void update(int p,int w){
while(p<=tot){
tree[p]+=w;
p+=lowbit(p);
}
}
inline int query(int p){
int res=0;
while(p){
res+=tree[p];
p-=lowbit(p);
}
return res;
}
inline void work(int id){
if(!where[id]) return;
int i=where[id],p;
while(true){
p=en[qur[i].x];
qur[i].ans=query(dfn[p]+siz[p]-1)-query(dfn[p]-1);
if(qur[i+1].y^qur[i].y) break;
++i;
}
}
inline void search(int u){
update(dfn[u],1);
if(ch[u].val) work(ch[u].val);
int p;
for(int i=0;i<26;++i){
p=ch[u].s[i];
if(!p) continue;
if(ch[p].dep<=ch[u].dep) continue;
search(p);
}
update(dfn[u],-1);
}
inline void print(){
sort(qur+1,qur+m+1,cmp2);
for(int i=1;i<=m;++i)
printf("%d\n",qur[i].ans);
}
int main(){
build();
get_fail();
Dfs(0,0);
get_query();
search(0);
print();
return 0;
}
考试题,当时不会,现在会了
ACM 上 DP,稍微改了一下 val 的定义
设 d p i , j dp_{i,j} dpi,j表示长度为 i i i,字典树上 j j j节点的最优方案
考虑转移方程:
d p i , s o n p = max { d p i − 1 , p + v a l s o n p } dp_{i,son_p}=\max\{dp_{i-1,p}+val_{son_p}\} dpi,sonp=max{dpi−1,p+valsonp}
#include
#define in Read()
#define int long long
using namespace std;
inline int in{
int i=0,f=1;char ch;
while(!isdigit(ch)&&ch!='-')ch=getchar();
if(ch=='-')ch=getchar(),f=-1;
while(isdigit(ch))i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int NNN=1e3+10;
const int INF=1e9+10;
int n,L,a[NNN],sz;
char s[NNN];
int ch[NNN][26],fail[NNN],val[NNN];
int dp[NNN][NNN],ans;
int same[NNN],idx[NNN];
inline void build(int id){
int p=0,len=strlen(s+1);
for(int i=1;i<=len;++i){
if(!ch[p][s[i]-'a']) ch[p][s[i]-'a']=++sz;
p=ch[p][s[i]-'a'];
}
if(idx[p]) same[id]=idx[p],val[p]+=a[id];
else val[p]=a[id],idx[p]=id;
}
inline void get_fail(){
queue<int>q;int p=0;
for(int i=0;i<26;++i){
p=ch[0][i];
if(!p) continue;
q.push(p);
}
while(!q.empty()){
int u=q.front();q.pop();
for(int i=0;i<26;++i){
p=ch[u][i];
if(!p){
ch[u][i]=ch[fail[u]][i];
continue;
}
q.push(p);
int v=fail[u];
while(v&&!ch[v][i]) v=fail[v];
fail[p]=ch[v][i];
}
val[u]+=val[fail[u]];
}
}
inline void query(){
for(int i=0;i<=L;++i)
for(int j=0;j<=sz;++j)
dp[i][j]=-INF;
dp[0][0]=0;
for(int i=1;i<=L;++i)
for(int j=0;j<=sz;++j)
for(int c=0;c<26;++c)
dp[i][ch[j][c]]=max(dp[i][ch[j][c]],dp[i-1][j]+val[ch[j][c]]);
}
signed main(){
n=in,L=in;
for(int i=1;i<=n;++i) a[i]=in;
for(int i=1;i<=n;++i){
scanf("%s",s+1);
build(i);
}
get_fail();
query();
for(int i=0;i<=sz;++i){
if(!same[i]) ans=max(ans,dp[L][i]);
else ans=max(ans,dp[L][same[i]]);
}
printf("%lld\n",ans);
return 0;
}
类似 T7,且显然暴力 T 飞
但是不像 T7,在线,没有排序的优化了, O ( n log n ) O(n\log n) O(nlogn)
无法排序也就决定区间不定,因此只能单点改,区间和
首先,建 trie,边 get_fail 边建树
然后,DFS排序,区间加上传现有单词集
最后,对于每个操作,区间加/单点改
#include
#define in Read()
using namespace std;
inline int in{
int i=0,f=1;char ch;
while(!isdigit(ch)&&ch!='-')ch=getchar();
if(ch=='-')ch=getchar(),f=-1;
while(isdigit(ch))i=(i<<1)+(i<<3)+ch-48,ch=getchar();
return i*f;
}
const int NNN=1e6+10;
int n,m,sz;
char s[NNN];
int ch[NNN][26],fail[NNN];
int where[NNN];
int exist[NNN];
inline void build(int id){
int p=0,len=strlen(s+1);
for(int i=1;i<=len;++i){
if(!ch[p][s[i]-'a']) ch[p][s[i]-'a']=++sz;
p=ch[p][s[i]-'a'];
}
where[id]=p;
}
int tot,fst[NNN],nxt[NNN],aim[NNN];
inline void add(int u,int v){
++tot;
nxt[tot]=fst[u];
fst[u]=tot;
aim[tot]=v;
}
inline void get_fail(){
int p=0;queue<int>q;
for(int i=0;i<26;++i){
p=ch[0][i];
if(!p) continue;
q.push(p);
}
while(!q.empty()){
int u=q.front();q.pop();
for(int i=0;i<26;++i){
p=ch[u][i];
if(!p){
ch[u][i]=ch[fail[u]][i];
continue;
}
q.push(p);
int v=ch[u][i];
while(v&&!ch[v][i]) v=fail[v];
fail[p]=ch[v][i];
}
add(u,fail[u]),add(fail[u],u);
}
}
int tol,dfn[NNN],siz[NNN];
inline void DFS(int fa,int u){
dfn[u]=++tol,siz[u]=1;
for(int v,e=fst[u];e;e=nxt[e]){
v=aim[e];
if(v==fa)continue;
DFS(u,v);
siz[u]+=siz[v];
}
}
int tr[NNN];
inline int lowbit(int x){return x&(-x);}
inline void update(int p,int w){
while(p<=tol){
tr[p]+=w;
p+=lowbit(p);
}
}
inline int query(int p){
int res=0;
while(p){
res+=tr[p];
p-=lowbit(p);
}
return res;
}
inline int sum(){
int ans=0,p=0,len=strlen(s+1);
for(int i=1;i<=len;++i){
p=ch[p][s[i]-'a'];
ans+=query(dfn[p]);
}
return ans;
}
int main(){
n=in,m=in;
for(int i=1;i<=m;++i){
scanf("%s",s+1);
build(i);
}
get_fail();
DFS(0,0);
for(int i=1;i<=m;++i){
exist[i]=true;
int u=where[i];
update(dfn[u],1);
update(dfn[u]+siz[u],-1);
}
for(int i=1;i<=n;++i){
char opt=getchar();
while(opt!='+'&&opt!='?'&&opt!='-') opt=getchar();
if(opt=='?'){
scanf("%s",s+1);
printf("%d\n",sum());
}else if(opt=='+'){
int id=in;
if(exist[id]) continue;
exist[id]=true;
int u=where[id];
update(dfn[u],1);
update(dfn[u]+siz[u],-1);
}else{
int id=in;
if(!exist[id]) continue;
exist[id]=false;
int u=where[id];
update(dfn[u],-1);
update(dfn[u]+siz[u],1);
}
}
}
假装我过了
这黑题好像跟 T7 没什么差别
咕掉