题意
给 个长度均为 的字符串 ,定义 为将 排序后相等的最小排序次数,若无解则为 (这好像是个黑客用语)。求
其中
思路分析
按照神xrlong的思路模拟即可,%%%%%%
神说,两个 trie 树,二维偏序肆意维护就过了,不过他终于在我的无数发问下补充了一些细节%%%%%
首先发现 是个诈骗函数,其值只可能为 ,因为规定没有相同的串,字符集相同时最多对两个串都排一次序就完成了,所以我们计算 和 的数量即可。
总操作数显然为
- 故事的开始前,先学习一下 的二维数点:
类似于二维前缀和,只不过我们没有那么大的空间和时间开销,还是一样的思路:
然后我们考虑记录每个 的 是该 是应加上还是减去的贡献。然后排序,树状数组扫描线维护即可。
q[++tot]={x1-1,y1-1,1}; q[++tot]={x2,y2,1}; q[++tot]={x1-1,y2,-1}; q[++tot]={x2,y1-1,-1}; //...... sort(q+1,q+tot+1,[](Q a,Q b){return a.qx for(int i=1,j=1;i<=tot;i++){ for(;lty[j].x<=q[i].qx&&j<=n;j++) add(lty[j].y); ans+=query(q[i].qy)*q[i].c; }
(建议先做一下P2163园丁的烦恼)
求
这个比较水,考虑对新读入的字符串桶排,然后塞到一个 树里,那么从根节点到每个叶子的路径即为一个字符集,记录字符集的个数和一些乱七八糟的东西,然后肆意求出。
CODE
//求sum(f=1337) int trk8[N][27],numk8; int c[30],cnt[N]; vector<int>k8he; int belong[N];//标记叶子节点属于的字符集 int NUM;//字符集个数 vector<int >order[N];//按照字符集顺序遍历 void jjdw(char str[],int id){ int p=0;for(int i=1;i<=26;++i) c[i]=0; for(int i=0;i'a'+1]++;//桶排序 for(int u=1;u<=26;u++){ if(c[u]){ for(int i=1;i<=c[u];++i){ if(!trk8[p][u]) trk8[p][u]=++numk8; p=trk8[p][u]; } } } if(!cnt[p]){ k8he.push_back(p); belong[p]=++NUM; } order[belong[p]].push_back(id); lty[id].bel=belong[p]; cnt[p]++; } int calc_k8(){//这个计算很好理解 int kk=0,k8sum=n; for(int i=0;isize()-1;++i){ int p=k8he[i]; k8sum-=cnt[p]; if(k8sum>0) kk+=cnt[p]*(k8sum); } return kk; }
求
这是本题精髓,考虑一个串,找出其中所有的极长不下降子串(这里不是最长,我被坑惨了),例如串 中的极长串是 ,对于每个子串,它会将把原串 分成一个前缀 和后缀 ,如果存在一个串 ,使得:
-
和 属于同一字符集,.
-
的前缀和后缀相等,
此时 (这很显然,只将 排序即可)
我们发现 实际就是两两字符串的 ,所以枚举每个字符串的每个极长不下降子串子串,求前缀和后缀和它分别相等的串的个数即可。
我们考虑对所有字符串正反建两遍 ,对叶子节点进行编号 到 ,那么每个串 就可以表示为一个二元组 ,其中 是 在正 上结束的叶子节点编号, 同理。
我们发现:前缀相同的串在 树上叶子节点的编号是连续的,考虑将前缀在正 上跑,得到一个区间 ,再将后缀在反 上跑,得到另一个区间 ,然后在矩形 二维数点即可。直观地,对于 个字符串建出正 ,如下图:
反 同理。
CODE
void ins1(char str[],int id){//正trie int p=0; for(int i=0;i int u=str[i]-'a'+1; if(!trie1[p][u]) trie1[p][u]=++num1; p=trie1[p][u]; } flag1[p]=1; ed1[id]=p; } void ins2(char str[],int id){//反trie int p=0; for(int i=len-1;i>=0;i--){ int u=str[i]-'a'+1; if(!trie2[p][u]) trie2[p][u]=++num2; p=trie2[p][u]; } flag2[p]=1; ed2[id]=p; } void dfs1(int x){ if(flag1[x]){ l1[x]=r1[x]=++Id1;return;//给叶子结点编号 } for(int i=1;i<=26;++i){ int y=trie1[x][i]; if(y){ dfs1(y); if(!l1[x]) l1[x]=l1[y]; l1[x]=min(l1[x],l1[y]); r1[x]=max(r1[x],r1[y]); //求该节点覆盖区间 } } } void dfs2(int x){ if(flag2[x]){ l2[x]=r2[x]=++Id2;return; } for(int i=1;i<=26;++i){ int y=trie2[x][i]; if(y){ dfs2(y); if(!l2[x]) l2[x]=l2[y]; l2[x]=min(l2[x],l2[y]); r2[x]=max(r2[x],r2[y]); } } }
其中还有不少坑人的sm细节:
-
对于每个区间的计算,在线算的话单次 ,我们考虑离线所有要查询的 ,这里 是一个极长不下降子串,考虑每个串所有的 都在正 的一条链上( 同理),然后就可以总复杂度 水过去了。
-
我们要统计矩形 内的点,这些点必须与当前串在同一字符集里,我们可以预处理出每个串所属的字符集,然后在数点的时候判断与当前字符集是否一致,也就是这样:
for(int i=1,j=1;i<=tot;i++){ for(;lty[j].x<=q[i].qx&&j<=n;j++){ if(lty[j].bel==id) add(lty[j].y); } count1+=query(q[i].qy)*q[i].c; }
但是这样显然是 的复杂度,狂 不止
这时xrlong让我打他的trie树清空做法,我看着眼前二百多行代码很是不舍,然后就出来一个跑飞快的优化
一个 的优化,考虑将点集以字符集编号为第一关键字,横坐标第二关键字排序,这样可以去掉无用的遍历,也就是:
sort(lty+1,lty+n+1,[](Node a,Node b){ if(a.bel==b.bel) return a.x<b.x; return a.bel<b.bel; });//玄学优化(不玄学) for(int i=1;i<=n;i++){ if(lty[i].bel!=lty[i-1].bel){ ql[lty[i].bel]=i; qr[lty[i-1].bel]=i-1; //每段字符集对应的左右端点 } } qr[lty[n].bel]=n; //...... for(int id=1;id<=NUM;++id){ //... for(int i=1,j=ql[id];i<=tot;++i){ for(;lty[j].x<=q[i].qx&&j<=qr[id];++j) add(lty[j].y); count1+=query(q[i].qy)*q[i].c; }
这样就跑飞快了。
- 考虑跑完一个字符集,进行清空,我们当然不能 清空,那么:
你怎么加进去就怎么清空——
int C[N],topc; void add(int x){ C[++topc]=x; for(int i=x;i<=n;i+=lowbit(i)) tr[i]++; } void clear(){ for(int x=1;x<=topc;x++){ for(int i=C[x];i<=n;i+=lowbit(i)){ if(!tr[i]) break;//剪个小枝 tr[i]=0; } } topc=0;tot=0; }
- 每个字符串前缀后缀都相同的串,显然也包括它自己,所以最后要减去算了多少遍自己,即极长不下降子串个数。
求
小学生问题。
差不多了,拜谢
也就二百多行,我码量大一定是因为我每个函数都写了两遍,dfs1,dfs2,ins1,ins2...
#include<bits/stdc++.h> using namespace std; #define k8 1337 #define int long long #define read read() #define pt puts("") inline int read { int x=0,f=1;char c=getchar(); while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();} while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar(); return f*x; } void write(int x) { if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); return; } const int N = 2e5+10; int n; int ans; int len; char s[N]; string T[N]; int trie1[N][27],trie2[N][27]; int l1[N],l2[N],r1[N],r2[N];//每个trie树上某节点覆盖的范围[l,r] int num1,num2; int Id1,Id2;//给叶子结点编号 int ed1[N],ed2[N];//记录每个串结束的节点 bool flag1[N],flag2[N];//标记为叶子节点 struct Node{ int x,y; int bel;//所属字符集 }lty[N]; void ins1(char str[],int id){//正trie int p=0; for(int i=0;i int u=str[i]-'a'+1; if(!trie1[p][u]) trie1[p][u]=++num1; p=trie1[p][u]; } flag1[p]=1; ed1[id]=p; } void ins2(char str[],int id){//反trie int p=0; for(int i=len-1;i>=0;i--){ int u=str[i]-'a'+1; if(!trie2[p][u]) trie2[p][u]=++num2; p=trie2[p][u]; } flag2[p]=1; ed2[id]=p; } void dfs1(int x){ if(flag1[x]){ l1[x]=r1[x]=++Id1;return;//给叶子结点编号 } for(int i=1;i<=26;++i){ int y=trie1[x][i]; if(y){ dfs1(y); if(!l1[x]) l1[x]=l1[y]; l1[x]=min(l1[x],l1[y]); r1[x]=max(r1[x],r1[y]); //求该节点覆盖区间 } } } void dfs2(int x){ if(flag2[x]){ l2[x]=r2[x]=++Id2;return; } for(int i=1;i<=26;++i){ int y=trie2[x][i]; if(y){ dfs2(y); if(!l2[x]) l2[x]=l2[y]; l2[x]=min(l2[x],l2[y]); r2[x]=max(r2[x],r2[y]); } } } //求sum(f=1337) int trk8[N][27],numk8; int c[30],cnt[N]; vector<int>k8he; int belong[N];//标记叶子节点属于的字符集 int NUM;//字符集个数 vector<int >order[N];//按照字符集顺序遍历 void jjdw(char str[],int id){ int p=0;for(int i=1;i<=26;++i) c[i]=0; for(int i=0;i'a'+1]++;//桶排序 for(int u=1;u<=26;u++){ if(c[u]){ for(int i=1;i<=c[u];++i){ if(!trk8[p][u]) trk8[p][u]=++numk8; p=trk8[p][u]; } } } if(!cnt[p]){ k8he.push_back(p); belong[p]=++NUM; } order[belong[p]].push_back(id); lty[id].bel=belong[p]; cnt[p]++; } int calc_k8(){//这个计算很好理解 int kk=0,k8sum=n; for(int i=0;isize()-1;++i){ int p=k8he[i]; k8sum-=cnt[p]; if(k8sum>0) kk+=cnt[p]*(k8sum); } return kk; } int kobe; int L[N],R[N],top; int x_1[N<<2],x_2[N<<2],y_1[N<<2],y_2[N<<2]; void search1(string a){//离线查询 int p=0; for(int i=1,j=0;i<=top;++i){ for(;j int u=a[j]-'a'+1; p=trie1[p][u]; } x_1[i]=l1[p],x_2[i]=r1[p]; } } void search2(string a){ int p=0; for(int i=top,j=len-1;i;i--){ for(;j>R[i];j--){ int u=a[j]-'a'+1; p=trie2[p][u]; } y_1[i]=l2[p],y_2[i]=r2[p]; } } int tot; struct Q{ int qx,qy,c; }q[N<<3]; int tr[N]; #define lowbit(i) (i&(-i)) int C[N],topc; void add(int x){ C[++topc]=x; for(int i=x;i<=n;i+=lowbit(i)) tr[i]++; } int query(int x){ int res=0; for(int i=x;i;i-=lowbit(i)){ res+=tr[i]; } return res; } void clear(){ for(int x=1;x<=topc;x++){ for(int i=C[x];i<=n;i+=lowbit(i)){ if(!tr[i]) break; tr[i]=0; } } topc=0;tot=0; } int ql[N],qr[N]; signed main() { n=read; kobe=(n-1)*n/2;//总操作数 for(int i=1;i<=n;++i){ scanf(" %s",s); T[i]=string(s); if(!len) len=strlen(s); ins1(s,i);ins2(s,i);//正反建trie jjdw(s,i); } dfs1(0);dfs2(0);//编号 int ck8=calc_k8();//1337的个数 ans=k8*ck8; kobe-=ck8; if(!kobe){ write(ans);return 0; } for(int i=1;i<=n;++i){ lty[i].x=l1[ed1[i]]; lty[i].y=l2[ed2[i]]; }//每个串对应的坐标 sort(lty+1,lty+n+1,[](Node a,Node b){ if(a.bel==b.bel) return a.x return a.bel });//玄学优化(不玄学) for(int i=1;i<=n;i++){ if(lty[i].bel!=lty[i-1].bel){ ql[lty[i].bel]=i; qr[lty[i-1].bel]=i-1; } } qr[lty[n].bel]=n;//处理每个字符集左右边界 int al,ar; int count1=0; string a; int self=n;//减去重复 for(int id=1;id<=NUM;++id){ for(int i:order[id]){ al=0;a=T[i]; for(ar=1;ar//统计每个极长子串 if(a[ar]-1]){ L[++top]=al,R[top]=ar-1;//离线下来一遍求,否则会T al=ar;++self; } } ar--; L[++top]=al,R[top]=ar; search1(a);search2(a); for(int i=1;i<=top;++i){ int x1=x_1[i],x2=x_2[i],y1=y_1[i],y2=y_2[i]; q[++tot]={x1-1,y1-1,1}; q[++tot]={x2,y2,1}; q[++tot]={x1-1,y2,-1}; q[++tot]={x2,y1-1,-1}; }//离线二维数点 top=0; } sort(q+1,q+tot+1,[](Q a,Q b){return a.qx for(int i=1,j=ql[id];i<=tot;++i){ for(;lty[j].x<=q[i].qx&&j<=qr[id];++j) add(lty[j].y); count1+=query(q[i].qy)*q[i].c; } clear(); } count1-=self; ans+=count1; kobe-=count1; ans+=kobe*2; write(ans); return 0; }
唉,我是 ,感觉这个题真是我现在码力的极限了...