3.21省选模拟
作者:互联网
Just hide this love in my heart,forever
一言难尽,$T1$细节巨多,想出来就没想写的欲望,$T2$想写个$tarjan$每次暴力缩写挂了,$T3$人类智慧题,成功垫底
T1
//首先想一下怎么找到当前串在原串的所有位置 //考场上一个没做出来很大的原因就是并不会找到这个位置,其实考场上分讨已经解决了 //我们需要得到当前串所有出现位置的right集合 //一个串的right集合就是这个串对应节点的right集合 //就是endpos集合了,前几天刚写了,建出来后缀树之后 //所有的子树的位置,对于fa建反向边,然后子树内部都是了 //考虑完出现位置,就是exr的分讨了 //考试时候我多想了一种情况 //其实只需要讨论最左边和最右边有没有相交就好了 //因为不合法的方案的计算方式是一样的 //首先有三块的话必然0了 //Sit1:相交 //其实考虑i往右移动的贡献计算方式会好算点(其实在考场上这道题也是很可做的) //首先[1,l[1]),其余的只能放在中间 //[l[i],l[i+1]),[l[i+1],r[i+1])任取就好了 //[l[m],r[1]),这部分取右边所有就好了 //最后的答案就是合并一下 //Sit2:不相交 //还是考虑往右扫的过程 //如果还是在1的长度范围内 //按照1的式子继续推就好了 //否则找出前驱后继更新就好了 //线段树维护最大值,最小值,和 //其实线段树下标是一个当前结尾串的的结束位置 //然后根据SAM的关系,我们找到所有有值的位置就好了 #include<bits/stdc++.h> #define INF 2147483647 #define int long long #define MAXN 200005 using namespace std; struct Node { int Min,Max; int Sum1,Sum2; }tr[MAXN<<5]; struct Tree { int ch[MAXN<<5][2],tot; Node Merge(const Node &a,const Node &b) { Node c; c.Min=min(a.Min,b.Min); c.Max=max(a.Max,b.Max); c.Sum1=a.Sum1+b.Sum1+b.Min*(b.Min-a.Max); c.Sum2=a.Sum2+b.Sum2+b.Min-a.Max; return c; } int Find_Max(int now,int l,int r,int L,int R) { if(!now) return 0; if(L<=l&&r<=R) return tr[now].Max; int mid=(l+r)>>1,res=0; if(L<=mid) res=Find_Max(ch[now][0],l,mid,L,R); if(R>mid) res=max(res,Find_Max(ch[now][1],mid+1,r,L,R)); return res; } int Find_Min(int now,int l,int r,int L,int R) { if(!now) return INF; if(L<=l&&r<=R) return tr[now].Min; int mid=(l+r)>>1,res=INF; if(L<=mid) res=Find_Min(ch[now][0],l,mid,L,R); if(R>mid) res=min(res,Find_Min(ch[now][1],mid+1,r,L,R)); return res; } Node tmp; void query(int now,int l,int r,int L,int R) { if(!now) return ; if(L<=l&&r<=R) { if(tmp.Min==0) tmp=tr[now]; else tmp=Merge(tmp,tr[now]); return ; } int mid=(l+r)>>1; if(L<=mid) query(ch[now][0],l,mid,L,R); if(R>mid) query(ch[now][1],mid+1,r,L,R); } void change(int &now,int l,int r,int p) { if(!now) now=++tot; if(l==r) { tr[now].Min=tr[now].Max=p; tr[now].Sum1=tr[now].Sum2=0; return ; } int mid=(l+r)>>1; if(p<=mid) change(ch[now][0],l,mid,p); else change(ch[now][1],mid+1,r,p); if(ch[now][0]&&ch[now][1]) tr[now]=Merge(tr[ch[now][0]],tr[ch[now][1]]); else if(ch[now][0]) tr[now]=tr[ch[now][0]]; else tr[now]=tr[ch[now][1]]; } int Merge_num(int x,int y) { if(!x||!y) return x+y; int now=++tot; ch[now][0]=Merge_num(ch[x][0],ch[y][0]); ch[now][1]=Merge_num(ch[x][1],ch[y][1]); if(ch[now][0]&&ch[now][1]) tr[now]=Merge(tr[ch[now][0]],tr[ch[now][1]]); else if(ch[now][0]) tr[now]=tr[ch[now][0]]; else tr[now]=tr[ch[now][1]]; return now; } }T; int ls=1,cnt=1,zx[MAXN][20],fa[MAXN],len[MAXN],pos[MAXN],trs[MAXN][26]; void Insert(int c,int id) { int p=ls; ls=++cnt; int now=cnt; pos[id]=cnt; len[now]=len[p]+1; for(;p&&!trs[p][c];p=fa[p]) trs[p][c]=now; if(!p) fa[now]=1; else { int q=trs[p][c]; if(len[p]+1==len[q]) fa[now]=q; else { int spilt=++cnt; for(int i=0;i<=25;i++) { trs[spilt][i]=trs[q][i]; } fa[spilt]=fa[q]; len[spilt]=len[p]+1; fa[now]=fa[q]=spilt; //now长,分裂出来了spilt for(;p&&trs[p][c]==q;p=fa[p]) trs[p][c]=spilt; } } } int head[MAXN],nxt[MAXN],to[MAXN],rt[MAXN],tot1,n; void add(int u,int v) { tot1++; to[tot1]=v; nxt[tot1]=head[u]; head[u]=tot1; } void dfs(int now) { // cout<<"now: "<<now<<endl; // system("pause"); zx[now][0]=fa[now]; for(int i=1;i<=18;i++) zx[now][i]=zx[zx[now][i-1]][i-1]; for(int i=head[now];i!=-1;i=nxt[i]) { int y=to[i]; // dep[y]=dep[now]+1; dfs(y); rt[now]=T.Merge_num(rt[now],rt[y]); } } void build() { memset(head,-1,sizeof(head)); for(int i=1;i<=cnt;i++) { if(fa[i]) add(fa[i],i);//fa不是endpos大的节点吗,越来越懵了. } for(int i=1;i<=n;i++) T.change(rt[pos[i]],1,n,i); //pos是第i个后缀在SAM的节点编号 dfs(1); //子树的集合和就是我们当前节点的终止位置集合 } int C(int x) { if(x<2) return 0; return x*(x-1)/2; } int query(int l,int r) { int Len=r-l+1,u=pos[r]; for(int i=18;i>=0;i--)if(len[zx[u][i]]>=Len)u=zx[u][i]; //这个是找串的位置 int L=tr[rt[u]].Min,R=tr[rt[u]].Max;//找到当前串的最大最小值 if(L<R-Len*2+1&&T.Find_Max(rt[u],1,n,L,R-Len)-Len+1>L)return C(n-1); if(R-Len+1<=L) { Node now=tr[rt[u]]; int lm=R-Len+1; long long ans=now.Sum1-now.Sum2*lm+C(L-lm)+(L-lm)*1LL*(n-Len); return C(n-1)-ans; } else { T.tmp=(Node){0,0,0,0}; int lm=R-Len+1,poslm=T.Find_Max(rt[u],1,n,1,lm); T.query(rt[u],1,n,poslm,L+Len-1); Node now=T.tmp; int p1=T.Find_Max(rt[u],1,n,1,L+Len-1); int p2=T.Find_Min(rt[u],1,n,L+Len,n); long long ans=now.Sum1-now.Sum2*lm+(p2>lm?(L-(p1-Len+1))*1LL*(p2-lm):0); return C(n-1)-ans; } } int q,l,r; char s[MAXN]; signed main() { scanf("%lld%lld",&n,&q); scanf("%s",s+1); for(int i=1;i<=n;i++) { Insert(s[i]-'0',i); } build(); for(int i=1;i<=q;i++) { cin>>l>>r; cout<<query(l,r)<<"\n"; } }
T2
//考场上写的暴力缩点然后看联通块最小值,期望能拿60,没调出来就寄了 //那么正解是什么,我们考虑.如果存在指向关系,如果不能回来的话,就指向别人的点必然不优 //我们考虑维护一堆强连通分量,其实最后有用的只有没有出度的一个强连通分量 //我们可以用栈维护强连通分量,每次加入新点,如果被删过了,全删掉 //如果被来过,并入联通块,否则判断能否回来 #include <vector> #include <set> #include "keys.h" using namespace std; const int N=1000005; set<pair<int,int> > g[N]; set<int> have[N]; vector<int> reach[N]; int n,m,fa[N],sz[N],in[N],tag[N],sta[N],top; void ckMin(int &p,int q){p=(p<q?p:q);} int fd(int x){return fa[x]^x?fa[x]=fd(fa[x]):x;} void mer(int u,int v){ u=fd(u),v=fd(v); //合并俩联通块 if(g[u].size()+have[u].size()+reach[u].size()>g[v].size()+have[v].size()+reach[v].size()) std::swap(u,v); fa[u]=v,sz[v]+=sz[u]; //把u连到v上 for(const auto&p : reach[u]) reach[v].push_back(p); reach[u].clear(); for(const auto&p : have[u]){ auto pos=g[v].lower_bound(make_pair(p,0)); while(pos!=g[v].end()&&pos->first==p) reach[v].push_back(pos->second),pos=g[v].erase(pos); //删边操作,,把联通块内的边删掉 have[v].insert(p); } have[u].clear(); for(const auto&p : g[u]) if(have[v].count(p.first)) reach[v].push_back(p.second); else g[v].insert(p); g[u].clear(); //向外连边 } void run(int x){ //这应该就是缩点的过程 //每次都能处理完一个联通块 sta[++top]=x,tag[x]=1; while(top){ if(reach[x=sta[top]].empty()){tag[x]=2,--top;continue;} //如果这个时候没有出边了,证明这个点可能成为答案 //所有连向他的点不会更优,要么这个点存在联通块内 //这个点必须是要有向外的边的,否则必然不可能出去了 int u=fd(reach[x].back());reach[x].pop_back(); if(!tag[u]){sta[++top]=u,tag[u]=1;continue;} //如果没有加入的话,就标记上,最后再看能不能进去 if(tag[u]==2) continue; while(sta[top]!=u) mer(x,sta[--top]); //这个是能回来的情况,就把所有的一个块合并了 sta[top]=fd(x); } } void ck(int u,int v,int col){ u=fd(u),v=fd(v); if(u==v) return; in[u]+=(have[u].count(col)?1:0); in[v]+=(have[v].count(col)?1:0); } std::vector<int> find_reachable(std::vector<int> r, std::vector<int> u, std::vector<int> v, std::vector<int> c) { std::vector<int> ans(r.size(), 0); n=r.size(),m=u.size();int i,mn=1e9; //r每个房间钥匙 //m边数,u,v,c点和边权 for(i=0;i<m;++i){ //第一步建边 //初始化能到的点 //显然的,只需要先把能用的边建出来 if(c[i]==r[u[i]]) reach[u[i]].push_back(v[i]); else g[u[i]].insert(make_pair(c[i],v[i])); if(c[i]==r[v[i]]) reach[v[i]].push_back(u[i]); else g[v[i]].insert(make_pair(c[i],u[i])); } for(i=0;i<n;++i) fa[i]=i,sz[i]=1,have[i].insert(r[i]); //初始化强连通分量大小 for(i=0;i<n;++i) if(!tag[i]) run(i);//处理若干个联通块 //开始计算 for(i=0;i<m;++i) ck(u[i],v[i],c[i]); for(i=0;i<n;++i) if(!in[fd(i)]) ckMin(mn,sz[fd(i)]); for(i=0;i<n;++i) if(!in[fd(i)]) ans[i]=(sz[fd(i)]==mn); return ans; }
T3
#include<bits/stdc++.h> #include"dna.h" #define MAXN 1000005 using namespace std; int n,rev[100],cn[MAXN][3][3],c[3][3]; void init(string a,string b) { n=a.size(); rev['A']=0; rev['C']=1; rev['T']=2; for(int i=1;i<=n;++i) { for(int j=0;j<3;++j) { for(int k=0;k<3;++k) { cn[i][j][k]=cn[i-1][j][k]; } } ++cn[i][rev[a[i-1]]][rev[b[i-1]]]; //无非是记录一下前缀和罢了 } } int get_distance(int x, int y) { ++x,++y; //向后错一位 for(int i=0;i<3;++i) { for(int j=0;j<3;++j) { c[i][j]=cn[y][i][j]-cn[x-1][i][j];//记录对数 } } //c[i][j]表示前面为i,后面为j的对数有多少 //c[0][1]+c[2][1]表示后面的1的个数 //c[1][0]+c[1][2]表示前面的1的个数 //c[0][1]+c[0][2]表示前面的0的个数 //c[1][0]+c[2][0]表示后面的0的个数 //既然0,1都一样了,2必然一样了 if(c[0][1]+c[2][1]!=c[1][0]+c[1][2] || c[0][1]+c[0][2]!=c[1][0]+c[2][0]) return -1; //这时候找最少的距离 //显然这三种情况分别是各自换到各自位置 //首先不同的有多少个就换多少个,发现会有重复替换的,恰好是相同两种的差的绝对值,就是说你交换完一种情况 //如果较优的话会帮忙交换另外的几种,剩下的两种就各自分各自的了 return max(c[0][1],c[1][0])+max(c[0][2],c[2][0])+max(c[1][2],c[2][1])-abs(c[0][1]-c[1][0]); }
标签:std,return,省选,top,reach,3.21,int,now,模拟 来源: https://www.cnblogs.com/Eternal-Battle/p/16035898.html