其他分享
首页 > 其他分享> > 3.21省选模拟

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