【HEOI 2018】制胡窜

YJQ的题解把思路介绍得很明白,只不过有些细节说得还是太笼统了(不过正经的题解就应该这个样子吧).
我的思路和YJQ有一些不同.
首先:

考虑反过来,求三个串都不包含询问串的方案数,这样需要的讨论会少很多.不难发现,三个串都不包含询问串的方案,就是询问串每个出现位置里(这里的位置指的是整个串,不是右端点),都至少含有一个断点的方案数.(本段落来自YJQ题解)

然后:

可以发现割的话一定是把原来的一群线段分为两波(其中某一波可以为0),假设左边的那刀为第一刀,右边的那刀为第二刀,第一波均由第一刀砍断,第二波均由第二刀砍断.
考虑如何通过划分两波线段来计算方案数.
设共匹配了m条线段,且这些线段按照位置依次排列,位置最靠前的线段为1,位置最靠后的线段为m.
考虑第一刀的位置.第一刀的位置显然是1往左,或者1上,因为如果是1往右的话是一定是切不断1的.
先看第一刀的位置在1往左的时候.这个时候第二刀必须切在所有线段的交上.这个可以很容易的判断和计算.

再来看第一刀的位置在1上的时候.先看一下上面那张图(图片来自YJQ题解),图中的1被下面线段的左端点分为了几部分——[l1,l2)、[l2,l3)、[l3,l4)、[l4,r1].不妨把1被分为的部分分为最后一段和不是最后一段两种.
先看不是最后一段的部分.每一段的贡献是|[l[i],l[i+1])|*|i+1与m的交|.写成公式就是∑(r[i+1]-r[i])*(r[i+1]-l[m]),括号拆开,所求即∑(r[i+1]-r[i])*r[i+1]与∑(r[i+1]-r[i]).(本段内容整理自YJQ题解)
再来看最后一段.考虑这一段的贡献,如果这一段是所有线段的交,那么贡献就可以用等差数列求前缀和求得,如果不是的话贡献就是|[l[i],r[1]]|*|i+1与m的交|.

(以上思路中的公式可能需要一定的微调,具体公式请读者在懂得思路后自行推断.)
以上就是我这道题思路的全部了.实现的话我用的是SAM和替罪羊启发式合并,竟然比线段树合并快……

#include <cstdio>
#include <cstring>
#include <algorithm>
typedef long long LL;
const int A=20;
const int N=100010;
const int M=300010;
const int Inf=0x3f3f3f3f;
const double alpha=0.75;
int n,m;
LL ans[M];
char s[N];
struct Q{
  int to,next,id;
}q[M];
int mine[N<<1],qt;
inline void add(int x,int y,int z){
  q[++qt].to=y,q[qt].next=mine[x],mine[x]=qt,q[qt].id=z;
}
struct V{
  int to,next;
}c[N<<1];
int head[N<<1],t;
inline void add(int x,int y){
  c[++t].to=y,c[t].next=head[x],head[x]=t;
}
struct ScapeGoat_Tree{
  ScapeGoat_Tree *ch[2];
  int size;
  int key,max,min;
  LL sum1,val1;
  int sum2,val2;
  inline void pushup(){
    size=ch[0]->size+ch[1]->size+1;
    sum1=ch[0]->sum1+ch[1]->sum1+val1;
    sum2=ch[0]->sum2+ch[1]->sum2+val2;
    max=min=key;
    max=std::max(max,ch[0]->max);
    max=std::max(max,ch[1]->max);
    min=std::min(min,ch[0]->min);
    min=std::min(min,ch[1]->min);
  }
  inline bool isbad(){
    return size*alpha+5<ch[0]->size||size*alpha+5<ch[1]->size;
  }
  inline void* operator new(size_t);
}*root[N<<1],*null,*C,*mempool,*list[N];
inline void* ScapeGoat_Tree::operator new(size_t){
  if(C==mempool){
    C=new ScapeGoat_Tree[(1<<15)+10];
    mempool=C+(1<<15)+10;
  }
  return C++;
}
int len;
inline void travel(ScapeGoat_Tree *p){
  if(p==null)return;
  travel(p->ch[0]);
  list[++len]=p;
  travel(p->ch[1]);
}
inline ScapeGoat_Tree *divide(int l,int r){
  if(l>r)return null;
  int mid=(l+r)>>1;
  list[mid]->ch[0]=divide(l,mid-1);
  list[mid]->ch[1]=divide(mid+1,r);
  list[mid]->pushup();
  return list[mid];
}
inline void rebuild(ScapeGoat_Tree *&p){
  if(p==null)return;
  len=0;
  travel(p);
  p=divide(1,len);
}
inline ScapeGoat_Tree **insert(ScapeGoat_Tree *&p,int key,LL val1,int val2){
  if(p==null){
    p=new ScapeGoat_Tree;
    p->ch[0]=p->ch[1]=null;
    p->size=1;
    p->key=p->max=p->min=key;
    p->sum1=p->val1=val1;
    p->sum2=p->val2=val2;
    return &null;
  }
  ScapeGoat_Tree **ret=insert(p->ch[key>p->key],key,val1,val2);
  p->pushup();
  if(p->isbad())ret=&p;
  return ret;
}
inline void Insert(ScapeGoat_Tree *&p,int key,LL val1,int val2){
  rebuild(*insert(p,key,val1,val2));
}
inline void update(ScapeGoat_Tree *p,int key){
  if(p==null)return;
  if(p->key<=key)update(p->ch[1],key);
  else{
    if(p->ch[0]->size==0||p->ch[0]->max<=key){
      p->val1=(LL)p->key*(p->key-key);
      p->val2=p->key-key;
    }else update(p->ch[0],key);
  }
  p->pushup();
}
inline int upper_bound(ScapeGoat_Tree *p,int key){
  if(p==null)return 0;
  if(p->key<=key)return upper_bound(p->ch[1],key);
  else{
    if(p->ch[0]->size==0||p->ch[0]->max<=key)return p->key;
    else return upper_bound(p->ch[0],key);
  }
}
inline int lower_bound(ScapeGoat_Tree *p,int key){
  if(p==null)return 0;
  if(p->key>=key)return lower_bound(p->ch[0],key);
  else{
    if(p->ch[1]->size==0||p->ch[1]->min>=key)return p->key;
    else return lower_bound(p->ch[1],key);
  }
}
inline void Insert(ScapeGoat_Tree *&p,int key){
  update(p,key);
  int pr=lower_bound(p,key);
  Insert(p,key,pr?(LL)(key-pr)*key:0,pr?key-pr:0);
}
inline void query(ScapeGoat_Tree *p,int key,LL &sum,int &size){
  if(p==null)return;
  if(p->key<=key){
    sum+=p->ch[0]->sum1+p->val1;
    size+=p->ch[0]->sum2+p->val2;
    query(p->ch[1],key,sum,size);
  }else
    query(p->ch[0],key,sum,size);
}
inline LL query(ScapeGoat_Tree *p,int len){
  int r1=p->min,rn=p->max,ln=rn-len+1;
  LL sum1=0,sum2=0,ret=0;
  int size1=0,size2=0;
  query(p,r1+len-2,sum1,size1);
  query(p,ln,sum2,size2);
  if(size1<=size2)ret=0;
  else ret=sum1-sum2-(LL)(size1-size2)*ln;
  if(ln<r1)ret+=(LL)(r1-len)*(r1-ln);
  int prv=lower_bound(p,r1+len-1),nxt=upper_bound(p,prv);
  if(!nxt)ret+=(LL)((n-ln-1)+(n-r1))*(r1-ln)>>1;
  else ret+=(LL)(r1-(prv-len+1))*std::max(nxt-ln,0);
  return ret;
}
inline void dfs(ScapeGoat_Tree *p,ScapeGoat_Tree *&to){
  if(p==null)return;
  Insert(to,p->key);
  dfs(p->ch[0],to);
  dfs(p->ch[1],to);
}
int rt,sz,trans[N<<1][10],link[N<<1],max[N<<1],f[N<<1][A],pos[N];
#define newnode(a) (max[++sz]=(a),sz)
inline int insert(int x,int last){
  int w=last,nw=newnode(max[w]+1),h,nh;
  while(w&&!trans[w][x])trans[w][x]=nw,w=link[w];
  if(!w)
    link[nw]=rt;
  else{
    h=trans[w][x];
    if(max[h]==max[w]+1)
      link[nw]=h;
    else{
      nh=newnode(max[w]+1);
      memcpy(trans[nh],trans[h],40);
      while(w&&trans[w][x]==h)trans[w][x]=nh,w=link[w];
      link[nh]=link[h],link[nw]=link[h]=nh;
    }
  }
  return nw;
}
inline void dfs(int x,int fa){
  int i;
  f[x][0]=fa;
  root[x]=null;
  for(i=1;i<A;++i)
    f[x][i]=f[f[x][i-1]][i-1];
  for(i=head[x];i;i=c[i].next)
    dfs(c[i].to,x);
}
inline int ipos(int x,int len){
  int i;
  for(i=A-1;i>=0;--i)
    if(max[f[x][i]]>=len)
      x=f[x][i];
  return x;
}
inline void dfs(int x){
  int i,j,v;
  for(i=head[x];i;i=c[i].next){
    v=c[i].to;
    dfs(v);
    if(root[x]->size<root[v]->size)
      std::swap(root[v],root[x]);
    dfs(root[v],root[x]);
  }
  for(i=mine[x];i;i=q[i].next)
    ans[q[i].id]=query(root[x],q[i].to);
}
int main(){
  rt=newnode(0);
  null=new ScapeGoat_Tree;
  memset(null,0,sizeof(*null));
  null->ch[0]=null->ch[1]=null;
  null->min=Inf,null->max=-Inf;
  scanf("%d%d",&n,&m);
  scanf("%s",s+1);
  int i,last=rt,l,r,x;
  for(i=1;i<=n;++i)
    last=pos[i]=insert(s[i]-‘0‘,last);
  for(i=2;i<=sz;++i)
    add(link[i],i);
  dfs(1,0);
  for(i=1;i<=n;++i)
    Insert(root[pos[i]],i);
  for(i=1;i<=m;++i){
    scanf("%d%d",&l,&r);
    x=ipos(pos[r],r-l+1);
    add(x,r-l+1,i);
  }
  dfs(1);
  LL sum=(LL)(n-2)*(n-1)>>1;
  for(i=1;i<=m;++i)
    printf("%lld\n",sum-ans[i]);
  return 0;
}

Kod

原文地址:https://www.cnblogs.com/TSHugh/p/8779709.html

时间: 2024-08-11 19:25:54

【HEOI 2018】制胡窜的相关文章

并不对劲的复健训练-bzoj5253:loj2479:p4384:[2018多省联考]制胡窜

题目大意 给出一个字符串\(S\),长度为\(n\)(\(n\leq 10^5\)),\(S[l:r]\)表示\(S_l,S_{l+1}...,S_r\)这个子串.有\(m\)(\(m\leq 3\times 10^5\))次询问,每次询问给出\(l,r\),问有多少对\((i,j)\)(\(1\leq i<i+1<j\leq n\)),使与\(S[l:r]\)本质相同的子串出现在\(S[1:i]\)中或\(S[i+1:j-1]\)中或\(S[j:n]\)中. 题解 询问相当于是问有多少种方案

【HEOI 2018】林克卡特树

先说60分的.思路题解上很清晰: 问题似乎等价于选K+1条点不相交的链哎!F(x,k,0/1/2)表示考虑以x为根的子树,选了k条链,点x的度数为0/1/2的最优解. 我说一下比较坑的地方吧:1.初始化要-Inf(反正我不加这个会wa)2.注意转移的顺序3.别忘了突然出现新的路径或者突然消失了一个路径的时侯加减14.一定要割k下细节说多不多,说少不少,还得自己打.说一下100分的.60分的瓶颈在于k,那么如果对于k没有限制的话,那么我们的转移就会变成O(n)的(和60分的dp是差不多的).如何去

[HEOI 2018]一双木棋

题意:求对抗分数差值最大. 思路:状压dp,维护一条轮廓线,最大化分差.可以发现上一行的棋子个数永远比这一行多. #include<bits/stdc++.h> using namespace std; const int INF = ~0U >> 1; int n,m; int f[(1<<25)][2]; bool vis[(1<<25)][2]; int a[20][20][2]; inline int DP(int is_visited,int no

2018暑期做题部分整合

<Matrix>(HDU) 题意:n*m矩阵,每个点可黑可白,问有多少种方案使矩阵至少有A行B列全黑. 思路:第一反应当然是容斥,但是发现a+1行全黑的方案,并不是恰被a行全黑的方案多算a次,所以直接+1,-1,+1,-1这样的容斥系数就不可行. 而如果DP,复杂度太高,不可行. 于是考虑手推容斥系数,a行全黑的方案,被计数的次数取为([a>=A]-被更小的a计算次数)即可. 收获:对于复杂的计数问题,如果分类时,一种方案会在若干类中重复计数,可以使用推广的容斥来做. <Alway

Practice II 字符串

本来想做数论的--但是别的dalao都在做制胡窜 所以-- Chapter I KMP KMP 最关键的不是这个半暴力的单模匹配 而是这个nxt数组 经常出一些奇怪的题 尤其是循环节可以直接由T-nxt[T]得到--神啊 总之记住nxt就是最长公共前后缀中前缀的尾指针就OK T1 poj3461 Oulipo Time cost: 10min 纯纯的板子 真没啥可说的 就是每次清空nxt就OK Code: 1 #include<cstdio> 2 #include<cstring>

FFT字符串匹配

本文半原创 参考资料:其实就是照抄的什么参考啊 我们知道KMP可以用来在线性复杂度内进行制胡窜匹配 今天教您一种新方法:用FFT进行字符串匹配 您可能觉得这很玄学,FFT不是做多项式卷积的吗,怎么还可以做制胡窜匹配 您先别着急,请接着听 我们设两个字符串--模式串\(a\),长度为\(m\),文本串\(b\),长度为\(n\).设下标为从0开始 定义函数\(a(i)\)返回a串位置i的字符,\(b(i)\)返回b串位置i的字符(其实就是下标) 定义匹配函数\(c(x,y)=a(x)-b(y)\)

2018币圈年终大盘点-千氪

2018年还有不到十天的时间就要过去了,回顾整个币圈的2018,可以说是风雨飘摇的一年,随着越来越多的人进入区块链行业,今年币圈发生了不少令人瞠目结舌的大事件. 李笑来转行退出币圈 9月30日,李笑来在微博发布消息称,他个人不再会做任何项目投资,不管是区块链还是早期项目.他准备花几年时间认真转行,至于具体下一步做什么,目前还没有想好. 陈李之争闹上法庭 从3月到10月,李笑来和陈伟星这场持续7个月的撕逼大局争论不断.从前奏期的三点钟社群争端.EOS高风险毒瘤定论.比特币假首富,到高潮期的李笑来"

如何将C/C++程序转译成Delphi(十四)

众所周知,数据科学是这几年才火起来的概念,而应运而生的数据科学家(data scientist)明显缺乏清晰的录取标准和工作内容.此次课程以<星际争霸II>回放文件分析为例,集中在IBM Cloud相关数据分析服务的应用.面对星际游戏爱好者希望提升技能的要求,我们使用IBM Data Science Experience中的jJupyter Notebooks来实现数据的可视化以及对数据进行深度分析,并最终存储到IBM Cloudant中.这是个介绍+动手实践的教程,参会者不仅将和讲师一起在线

世界著名设计小组nrg推荐的75个FLASH酷站

众所周知,数据科学是这几年才火起来的概念,而应运而生的数据科学家(data scientist)明显缺乏清晰的录取标准和工作内容.此次课程以<星际争霸II>回放文件分析为例,集中在IBM Cloud相关数据分析服务的应用.面对星际游戏爱好者希望提升技能的要求,我们使用IBM Data Science Experience中的jJupyter Notebooks来实现数据的可视化以及对数据进行深度分析,并最终存储到IBM Cloudant中.这是个介绍+动手实践的教程,参会者不仅将和讲师一起在线