• ZOJ Monthly, January 2018


    A. Candy Game

    显然最优策略是一个一个吃,故比较哪种糖果的个数比较多即可。

    #include<cstdio>
    int T,n,i,x,sum;
    int main(){
      scanf("%d",&T);
      while(T--){
        scanf("%d",&n);
        sum=0;
        for(i=1;i<=n;i++)scanf("%d",&x),sum+=x;
        for(i=1;i<=n;i++)scanf("%d",&x),sum-=x;
        puts(sum>0?"BaoBao":"DreamGrid");
      }
    }
    

      

    B. PreSuffix

    对所有串建立AC自动机,那么若前缀$i$是前缀$j$的后缀,说明$i$是Fail树上$j$的祖先。

    所以对于询问$(x,y)$,答案就是两点在Fail树上的LCA在原Trie中子树内的字符串总数。

    时间复杂度$O(nlog n)$。

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int N=100010,M=500010;
    char s[M];
    int tot,son[M][26],fail[M],q[M],val[M],G[M],NXT[M],size[M],heavy[M],top[M],d[M];
    int n,i,j,m,x,y,pos[N];
    inline void ins(int p){
      int l=strlen(s),x=0;
      for(int i=0,w;i<l;i++){
        if(!son[x][w=s[i]-'a'])son[x][w]=++tot;
        x=son[x][w];
        val[x]++;
      }
      pos[p]=x;
    }
    void make(){
      int h=1,t=0,i,j,x;fail[0]=-1;
      for(i=0;i<26;i++)if(son[0][i])q[++t]=son[0][i];
      while(h<=t)for(x=q[h++],i=0;i<26;i++)
        if(son[x][i])fail[son[x][i]]=son[fail[x]][i],q[++t]=son[x][i];
        else son[x][i]=son[fail[x]][i];
    }
    void dfs(int x){
      size[x]=1;
      for(int i=G[x];i;i=NXT[i]){
        d[i]=d[x]+1;
        dfs(i);
        size[x]+=size[i];
        if(heavy[x]<0)heavy[x]=i;
        else if(size[i]>size[heavy[x]])heavy[x]=i;
      }
    }
    void dfs2(int x,int y){
      top[x]=y;
      if(~heavy[x])dfs2(heavy[x],y);
      for(int i=G[x];i;i=NXT[i])if(i!=heavy[x])dfs2(i,i);
    }
    inline int lca(int x,int y){
      for(;top[x]!=top[y];x=fail[top[x]])if(d[top[x]]<d[top[y]])swap(x,y);
      return d[x]<d[y]?x:y;
    }
    inline void ask(int x,int y){
      x=pos[x];
      y=pos[y];
      int z=lca(x,y);
      if(!val[z])puts("N");else printf("%d
    ",val[z]);
    }
    int main(){
      while(~scanf("%d",&n)){
        for(i=1;i<=n;i++)scanf("%s",s),ins(i);
        make();
        for(i=1;i<=tot;i++)NXT[i]=G[fail[i]],G[fail[i]]=i;
        for(i=0;i<=tot;i++)heavy[i]=-1;
        dfs(0);
        dfs2(0,0);
        scanf("%d",&m);
        while(m--)scanf("%d%d",&x,&y),ask(x,y);
        for(i=0;i<=tot;i++){
          for(fail[i]=G[i]=j=0;j<26;j++)son[i][j]=0;
          val[i]=size[i]=top[i]=d[i]=0;
          heavy[i]=-1;
        }
        tot=0;
      }
      return 0;
    }
    

      

    C. An Unsure Catch

    考虑$k=0$的情形:

    对于每个连通块,断开一条环边变成树,求出树上每个点的深度$d$。

    设环长为$len$,则该连通块中最优解为$dmod len$中出现次数最多的那一个。

    考虑$k>0$的情形,有两种最优策略:

    $1.$ 利用一步操作将某个环长修改为$1$,再用$k-1$步操作把其它$k-1$个连通块合并上来。

    故此时答案为连通块点数最大的$k$个的点数之和。

    $2.$ 不刻意制造长度为$1$的环,直接用$k$步操作把$k-1$个连通块合并到某个点上。

    枚举每个连通块作为最终合并点,假设其环长为$K$,则要选$k-1$个其它连通块,将它们各断开一条边,然后将环长修改为$K$,使得$dmod K$中出现次数最大值最大。

    注意到$K$的取值只有$O(sqrt{n})$种,枚举每个$K$,对于每个连通块计算最优解,然后计数排序统计前$k$大值的和即可。

    对于每个连通块,假设环长为$len$,那么按顺序依次断开每条环边后,对断开点子树内所有$d$的影响是整体减去$len$,共$O(n)$次修改和$O(n)$次查询。

    注意到每次修改时,只会将某个数加$1$或减$1$,故最大值也只会变化$1$,记录每种值有多少个即可判断最大值是否需要变化,时间复杂度$O(1)$。

    总时间复杂度$O(nsqrt{n})$。

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    typedef pair<int,int>P;
    const int N=100010,inf=100000000;
    int Case,n,i,a[N],g[N],nxt[N];
    bool vis[N],visit[N],on[N];
    int d[N];
    int f[N];
    int id[N],m,ce;
    int fin[N];
    int vs[N];
    bool need[N];
    int len[N],head[N],cur;
    P pool[N];
    int nowmx,cf[N],have[N];
    inline void add(int x,int y){nxt[y]=g[x];g[x]=y;}
    void dfs(int x){
      if(vis[x])return;
      vis[x]=1;
      id[++m]=x;
      for(int i=g[x];i;i=nxt[i])dfs(i);
      dfs(a[x]);
    }
    int cal(int x){
      if(d[x])return d[x];
      return d[x]=cal(a[x])+1;
    }
    inline void solve(){
      int x=id[1];
      while(!visit[x])visit[x]=1,x=a[x];
      int circle=0;
      while(!on[x]){
        circle++;
        d[x]=inf-circle;
        on[x]=1,x=a[x];
      }
      ce++;
      vs[ce]=m;
      need[circle]=1;
      len[ce]=circle;
      head[ce]=x;
      for(int i=1;i<=m;i++)pool[++cur]=P(ce,cal(id[i]));
    }
    inline void up(int&a,int b){a<b?(a=b):0;}
    inline void addf(int x){
      cf[++f[x]]++;
      up(nowmx,f[x]);
    }
    inline void delf(int x){
      cf[f[x]--]--;
      if(!cf[nowmx])nowmx--;
    }
    int MO,L;
    void update(int x){
      delf(d[x]%MO);
      addf((d[x]-L)%MO);
      for(int i=g[x];i;i=nxt[i])if(!on[i])update(i);
    }
    inline void mustlen(int K){
      MO=K;
      int i,j,k=0,x;
      int hismax=0;
      for(i=1;i<=n+1;i++)have[i]=0;
      for(i=1;i<=n;i=j){
        for(j=i;j<=n&&pool[i].first==pool[j].first;j++);
        int l=i,r=j-1;
        k++;
        nowmx=0;
        L=len[k];
        int best=0;
        for(x=l;x<=r;x++)addf(pool[x].second%K);
        best=nowmx;
        int S=x=head[k];
        while(1){
          update(x);
          up(best,nowmx);
          x=a[x];
          if(x==S)break;
        }
        for(x=l;x<=r;x++)delf((pool[x].second-L)%K);
        have[best]++;
        if(L==K&&best>hismax)hismax=best;
      }
      have[hismax]--;
      up(fin[k=0],hismax);
      for(i=n;i;i--)for(j=have[i];j;j--)up(fin[++k],hismax+=i);
      for(k++;k<=n;k++)up(fin[k],hismax);
    }
    int main(){
      scanf("%d",&Case);
      cf[0]=1e9;
      while(Case--){
        scanf("%d",&n);
        for(i=0;i<=n;i++)g[i]=vis[i]=visit[i]=on[i]=d[i]=f[i]=id[i]=need[i]=0;
        m=ce=cur=0;
        for(i=1;i<=n;i++)scanf("%d",&a[i]),add(a[i],i);
        for(i=1;i<=n;i++)if(!vis[i])m=0,dfs(i),solve();
        sort(vs+1,vs+ce+1);reverse(vs+1,vs+ce+1);
        for(i=1;i<=ce;i++)vs[i]+=vs[i-1];
        sort(pool+1,pool+cur+1);
        for(i=0;i<=n;i++)fin[i]=vs[min(ce,i)];
        for(i=1;i<=n;i++)if(need[i])mustlen(i);
        int k=0;
        for(i=0;i<=n;i++){
          fin[i]=min(fin[i],n);
          k=i;
          if(fin[i]>=n)break;
        }
        printf("%d
    ",k);
        for(i=0;i<=k;i++)printf("%d%c",fin[i],i<k?' ':'
    ');
      }
    }
    

      

    D. Seat Assignment

    $lcm(1,2,...,10)=2520$,对于模$lcm$的每个余数$k$分析其是否是$1$到$10$的倍数,可以发现一共$48$种本质不同的情况。

    那么计算出每种情况的容量之后,就转化成了左边$10$个点,右边$48$个点的最大流。

    #include<cstdio>
    const int N=10000,inf=~0U>>2;
    int lcm,m,i,j,Case,lim[N],q[N],mask[N],vis[N],cnt;
    int S,T,h[N],gap[N],maxflow;
    struct E{int t,f;E*nxt,*pair;}*g[N],*d[N],pool[100000],*cur=pool;
    inline int min(int a,int b){return a<b?a:b;}
    inline void add(int s,int t,int f){
      E*p=cur++;p->t=t;p->f=f;p->nxt=g[s];g[s]=p;
      p=cur++;p->t=s;p->f=0;p->nxt=g[t];g[t]=p;
      g[s]->pair=g[t];g[t]->pair=g[s];
    }
    int sap(int v,int flow){
      if(v==T)return flow;
      int rec=0;
      for(E*p=d[v];p;p=p->nxt)if(h[v]==h[p->t]+1&&p->f){
        int ret=sap(p->t,min(flow-rec,p->f));
        p->f-=ret;p->pair->f+=ret;d[v]=p;
        if((rec+=ret)==flow)return flow;
      }
      if(!(--gap[h[v]]))h[S]=T;
      gap[++h[v]]++;d[v]=g[v];
      return rec;
    }
    int main(){
      lcm=2520;
      for(i=0;i<lcm;i++){
        //k%lcm=i
        int S=0;
        for(j=1;j<=10;j++)if(i%j==0)S|=1<<j;
        if(!vis[S]){
          vis[S]=++cnt;
          q[cnt]=S;
        }
        mask[i]=vis[S];
      }
      scanf("%d",&Case);
      while(Case--){
        scanf("%d",&m);
        for(i=1;i<=cnt;i++)lim[i]=0;
        int sum=0;
        for(i=0;i<lcm;i++){
          int now=m-i;
          if(now<0)now=0;
          now=now/lcm;
          if(i&&i<=m)now++;
          lim[mask[i]]+=now;
        }
        
        S=cnt+11;
        T=S+1;
        for(cur=pool,i=1;i<=T;i++)g[i]=d[i]=NULL,h[i]=gap[i]=0;
        
        for(i=1;i<=cnt;i++)add(S,i,lim[i]);
        for(i=1;i<=10;i++){
          int x;
          scanf("%d",&x);
          add(cnt+i,T,x);
          if(x)for(j=1;j<=cnt;j++)if(q[j]>>i&1)add(j,cnt+i,x);
        }
        
        for(gap[maxflow=0]=T,i=1;i<=T;i++)d[i]=g[i];
        while(h[S]<T)maxflow+=sap(S,inf);
        
        printf("%d
    ",maxflow);
      }
    }
    /*
    2^10
    %1=0
    %2=
    
    1..m
    %lcm
    
    lcm=8*9*5*7
    */
    

      

    E. Yet Another Data Structure Problem

    线段树维护序列乘积$mul$,标记$(a,b)$表示$mul=a imes mul^b$。

    时间复杂度$O(nlog nlog P)$。

    #include<cstdio>
    const int N=100010,M=262150,P=1000000007;
    int Case,n,m,i,a[N],ans;
    int v[M],ta[M],tb[M],len[M];
    inline int po(int a,int b){
      int t=1;
      for(;b;b>>=1,a=1LL*a*a%P)if(b&1)t=1LL*t*a%P;
      return t;
    }
    inline void tag1(int x,int a,int b){
      v[x]=1LL*po(v[x],b)*po(a,len[x])%P;
      ta[x]=1LL*po(ta[x],b)*a%P;
      tb[x]=1LL*tb[x]*b%(P-1);
    }
    inline void pb(int x){
      if(ta[x]==1&&tb[x]==1)return;
      tag1(x<<1,ta[x],tb[x]);
      tag1(x<<1|1,ta[x],tb[x]);
      ta[x]=tb[x]=1;
    }
    inline void up(int x){v[x]=1LL*v[x<<1]*v[x<<1|1]%P;}
    void build(int x,int a,int b){
      ta[x]=tb[x]=1;
      len[x]=b-a+1;
      if(a==b){v[x]=::a[a];return;}
      int mid=(a+b)>>1;
      build(x<<1,a,mid),build(x<<1|1,mid+1,b);
      up(x);
    }
    void change(int x,int a,int b,int c,int d,int A,int B){
      if(c<=a&&b<=d){tag1(x,A,B);return;}
      pb(x);
      int mid=(a+b)>>1;
      if(c<=mid)change(x<<1,a,mid,c,d,A,B);
      if(d>mid)change(x<<1|1,mid+1,b,c,d,A,B);
      up(x);
    }
    void ask(int x,int a,int b,int c,int d){
      if(c<=a&&b<=d){ans=1LL*ans*v[x]%P;return;}
      pb(x);
      int mid=(a+b)>>1;
      if(c<=mid)ask(x<<1,a,mid,c,d);
      if(d>mid)ask(x<<1|1,mid+1,b,c,d);
    }
    int main(){
      scanf("%d",&Case);
      while(Case--){
        scanf("%d%d",&n,&m);
        for(i=1;i<=n;i++)scanf("%d",&a[i]);
        build(1,1,n);
        while(m--){
          int op,l,r,k;
          scanf("%d%d%d",&op,&l,&r);
          if(op==1){
            scanf("%d",&k);
            change(1,1,n,l,r,k,1);
          }
          if(op==2){
            scanf("%d",&k);
            change(1,1,n,l,r,1,k);
          }
          if(op==3){
            ans=1;
            ask(1,1,n,l,r);
            printf("%d
    ",ans);
          }
        }
      }
    }
    

    UPD:

    $P=1000000007$的原根$g$为$5$,如果将每个数都取指标的话,则变为模$P-1$意义下的区间加、区间乘、区间求和,可以线段树$O(nlog n)$维护。

    对于每个询问,求出区间指标之和$sum$后,该询问的答案$ans=g^{sum}mod P$。

    注意到只需要预处理$1$到$1000$的指标,故更改BSGS算法中的步长为$300000$,可以降低每次求指标的时间复杂度。

    #include<cstdio>
    #include<algorithm>
    #include<tr1/unordered_map>
    using namespace std;
    using namespace std::tr1;
    const int N=100010,M=262150,P=1000000007,Q=P-1;
    int Case,n,m,i,a[N],ans;
    int v[M],ta[M],tb[M],len[M];
    inline int po(int a,int b){
      int t=1;
      for(;b;b>>=1,a=1LL*a*a%P)if(b&1)t=1LL*t*a%P;
      return t;
    }
    namespace NT{
    unordered_map<int,int>T;
    typedef pair<int,int>PI;
    const int K=300000,G=5;
    int ind[1005],base,i;
    inline int ask(int x){
      if(x==1)return 0;
      int t=po(x,P-2);
      for(int i=K;;i+=K){
        t=1LL*t*base%P;
        unordered_map<int,int>::iterator it=T.find(t);
        if(it!=T.end())return i-it->second;
      }
    }
    void init(){
      for(base=1,i=0;i<K;i++){
        T.insert(PI(base,i));
        base=1LL*base*G%P;
      }
      for(i=1;i<=1000;i++)ind[i]=ask(i);
    }
    }
    inline void tag1(int x,int a,int b){
      v[x]=(1LL*v[x]*b+1LL*a*len[x])%Q;
      tb[x]=1LL*tb[x]*b%Q;
      ta[x]=(1LL*ta[x]*b+a)%Q;
    }
    inline void pb(int x){
      if(ta[x]==0&&tb[x]==1)return;
      tag1(x<<1,ta[x],tb[x]);
      tag1(x<<1|1,ta[x],tb[x]);
      ta[x]=0,tb[x]=1;
    }
    inline void up(int x){v[x]=(v[x<<1]+v[x<<1|1])%Q;}
    void build(int x,int a,int b){
      ta[x]=0,tb[x]=1;
      len[x]=b-a+1;
      if(a==b){v[x]=::a[a];return;}
      int mid=(a+b)>>1;
      build(x<<1,a,mid),build(x<<1|1,mid+1,b);
      up(x);
    }
    void change(int x,int a,int b,int c,int d,int A,int B){
      if(c<=a&&b<=d){tag1(x,A,B);return;}
      pb(x);
      int mid=(a+b)>>1;
      if(c<=mid)change(x<<1,a,mid,c,d,A,B);
      if(d>mid)change(x<<1|1,mid+1,b,c,d,A,B);
      up(x);
    }
    void ask(int x,int a,int b,int c,int d){
      if(c<=a&&b<=d){ans=(ans+v[x])%Q;return;}
      pb(x);
      int mid=(a+b)>>1;
      if(c<=mid)ask(x<<1,a,mid,c,d);
      if(d>mid)ask(x<<1|1,mid+1,b,c,d);
    }
    int main(){
      NT::init();
      scanf("%d",&Case);
      while(Case--){
        scanf("%d%d",&n,&m);
        for(i=1;i<=n;i++)scanf("%d",&a[i]),a[i]=NT::ind[a[i]];
        build(1,1,n);
        while(m--){
          int op,l,r,k;
          scanf("%d%d%d",&op,&l,&r);
          if(op==1){
            scanf("%d",&k);
            change(1,1,n,l,r,NT::ind[k],1);
          }
          if(op==2){
            scanf("%d",&k);
            change(1,1,n,l,r,0,k);
          }
          if(op==3){
            ans=0;
            ask(1,1,n,l,r);
            printf("%d
    ",po(NT::G,ans));
          }
        }
      }
    }
    

        

    F. The Limit

    如果分子分母不同时为$0$,那么极限显然,否则根据洛必达法则分别求导计算。

    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    const int N=15;
    char s[100000];
    ll X;
    struct Poly{
      ll v[N];
      Poly(){memset(v,0,sizeof v);}
      void read(){
        memset(v,0,sizeof v);
        scanf("%s",s);
        int len=strlen(s);
        int i,j;
        for(i=0;i<len;){
          if(s[i]=='x'){
            if(s[i+1]=='^'){
              v[s[i+2]-'0']++;
              i+=3;
              continue;
            }
            v[1]++;
            i++;
            continue;
          }
          if(s[i]=='+'||s[i]=='-'){
            int flag=s[i]=='+'?1:-1;
            if(s[i+1]=='x'){
              if(s[i+2]=='^'){
                v[s[i+3]-'0']+=flag;
                i+=4;
                continue;
              }
              v[1]+=flag;
              i+=2;
              continue;
            }else{
              //is number
              ll num=flag*(s[i+1]-'0');
              if(s[i+2]=='x'){
                if(s[i+3]=='^'){
                  v[s[i+4]-'0']+=num;
                  i+=5;
                  continue;
                }
                v[1]+=num;
                i+=3;
                continue;
              }else{
                v[0]+=num;
                i+=2;
                continue;
              }
            }
          }
          //is number
          ll num=s[i]-'0';
          if(s[i+1]=='x'){
            if(s[i+2]=='^'){
              v[s[i+3]-'0']+=num;
              i+=4;
              continue;
            }
            v[1]+=num;
            i+=2;
            continue;
          }else{
            v[0]+=num;
            i++;
            continue;
          }
        }
      }
      ll f(ll x){
        ll ret=0,t=1;
        for(int i=0;i<N;i++){
          ret+=v[i]*t;
          t*=x;
        }
        return ret;
      }
      void write(){
        for(int i=0;i<N;i++)if(v[i])printf("%lldx^%d ",v[i],i);puts(".");
      }
      void dao(){
        for(int i=1;i<N;i++)v[i-1]=v[i]*i;
        v[N-1]=0;
      }
    }A,B;
    ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
    void solve(){
      A.read();
      B.read();
      scanf("%lld",&X);
      while(1){
        ll U=A.f(X),D=B.f(X);
        if(!U&&D){
          puts("0");
          return;
        }
        if(U&&!D){
          puts("INF");
          return;
        }
        if(U&&D){
          ll d=gcd(abs(U),abs(D));
          U/=d,D/=d;
          if(D<0)U*=-1,D*=-1;
          if(D!=1)printf("%lld/%lld
    ",U,D);else printf("%lld
    ",U);
          return;
        }
        A.dao();
        B.dao();
      }
    }
    int main(){
      int Case;
      scanf("%d",&Case);
      while(Case--)solve();
    }
    

      

    G. It's High Noon

    假设人位于$(x,y)$,若攻击部分背对原点,那么显然最优情况下$(x,y)=(0,0)$。将所有点极角排序然后双指针枚举即可。注意特判点在原点的情况。

    若攻击部分面向原点,那么显然最优情况下$x^2+y^2=R^2$,且直线与圆相切。抠除圆内部的所有点之后,对于剩下的点求出切线角度,那么角度在某个区间内的直线都能取到这个点,扫描线统计即可。

    时间复杂度$O(nlog n)$。

    #include<cstdio>
    #include<cmath>
    #include<algorithm>
    using namespace std;
    const int N=500000;
    const double pi=acos(-1.0),PI=acos(-1.0)*2.0,eps=1e-9;
    int Case,n,K,i,ans;
    struct P{
      int x,y;
    }a[N],b[N];
    inline int pos(int x,int y){return x?x>0:y>0;}
    inline int cross(const P&a,const P&b){return a.x*b.y-a.y*b.x;}
    inline bool cmp(const P&a,const P&b){
      if(pos(a.x,a.y)!=pos(b.x,b.y))return pos(a.x,a.y)<pos(b.x,b.y);
      return cross(a,b)<0;
    }
    struct E{
      double x;int y;
      E(){}
      E(double _x,int _y){x=_x,y=_y;}
    }e[N];
    inline bool cmpe(const E&a,const E&b){return a.x<b.x;}
    void solve_center(){
      int atcenter=0;
      int m=0;
      int i,j;
      for(i=1;i<=n;i++)if(!a[i].x&&!a[i].y)atcenter++;else{
        b[++m]=a[i];
      }
      ans=max(ans,atcenter);
      sort(b+1,b+m+1,cmp);
      for(i=1;i<=m;i++)b[i+m]=b[i];
      for(i=j=1;i<=m;i++){
        if(j<i)j=i;
        while(j+1<i+m&&cross(b[i],b[j+1])<=0)j++;
        ans=max(ans,j-i+1+atcenter);
      }
    }
    inline int sgn(double x){
      if(x>eps)return 1;
      if(x<-eps)return -1;
      return 0;
    }
    inline void fix(double&x){
      while(sgn(x)<0)x+=PI;
      while(sgn(x-PI)>=0)x-=PI;
      x=max(x,(double)0.0);
    }
    const double eps2=eps*5;
    void solve_ka(){
      int all=0;
      int i,j,k;
      int m=0;
      for(i=1;i<=n;i++){
        int dis=a[i].x*a[i].x+a[i].y*a[i].y;
        if(dis>K){
          double o=atan2(-a[i].y,-a[i].x);
          double w=acos(sqrt(max(1.0-1.0*K/dis,0.0)));
          w=fabs(w);
          double A=o-w,B=o+w+pi;
          double l=A,r=B;
          l-=eps2,r+=eps2;
          fix(l);
          fix(r);
          if(!sgn(l-r))r=l+eps;
          //printf("%.10f %.10f
    ",l,r);
          if(l>r)all++;
          //[l..r] 1 else 0
          e[++m]=E(l,1);
          e[++m]=E(r,-1);
        }else all++;
      }
      ans=max(ans,all);
      sort(e+1,e+m+1,cmpe);
      for(i=1;i<=m;i++){
        all+=e[i].y;
        ans=max(ans,all);
      }
    }
    int main(){
      scanf("%d",&Case);
      while(Case--){
        scanf("%d%d",&n,&K);K*=K;
        for(i=1;i<=n;i++)scanf("%d%d",&a[i].x,&a[i].y);
        ans=0;
        solve_center();
        solve_ka();
        printf("%d
    ",ans);
      }
    }
    

      

    H. Traveling Plan

    对于每个点$x$求出$d_x$表示离它最近的补给站到它的距离。

    对于每条边$(x,y,w)$,将边权重置为$d_x+d_y+w$。

    那么对于询问$(x,y)$,答案就是最小生成树上两点间边权的最大值。

    时间复杂度$O(nlog n)$。

    #include<cstdio>
    #include<algorithm>
    #include<vector>
    #include<queue>
    using namespace std;
    typedef long long ll;
    typedef pair<ll,int>P;
    const int N=100010,M=200010,K=20;
    const ll inf=1LL<<60;
    int n,m,q,x,y,i,j,f[N],g[M<<1],v[M<<1],nxt[M<<1],ed;ll w[M<<1];
    int d[N],fa[N][K];
    ll fw[N][K];
    ll dis[N];
    bool vis[N];
    priority_queue<P,vector<P>,greater<P> >Q;
    struct E{int x,y;ll z;}e[M];
    inline bool cmp(const E&a,const E&b){return a.z<b.z;}
    int F(int x){return f[x]==x?x:f[x]=F(f[x]);}
    inline void add(int x,int y,ll z){v[++ed]=y;w[ed]=z;nxt[ed]=g[x];g[x]=ed;}
    void dfs(int x,int y){
      vis[x]=1;
      for(int i=1;i<K;i++){
        fa[x][i]=fa[fa[x][i-1]][i-1];
        fw[x][i]=max(fw[x][i-1],fw[fa[x][i-1]][i-1]);
      }
      for(int i=g[x];i;i=nxt[i])if(v[i]!=y){
        fa[v[i]][0]=x;
        fw[v[i]][0]=w[i];
        d[v[i]]=d[x]+1;
        dfs(v[i],x);
      }
    }
    inline ll ask(int x,int y){
      ll ret=0;
      if(d[x]<d[y])swap(x,y);
      for(int i=K-1;~i;i--)if(d[fa[x][i]]>=d[y]){
        ret=max(ret,fw[x][i]);
        x=fa[x][i];
      }
      if(x==y)return ret;
      for(int i=K-1;~i;i--)if(fa[x][i]!=fa[y][i]){
        ret=max(ret,max(fw[x][i],fw[y][i]));
        x=fa[x][i];
        y=fa[y][i];
      }
      return max(ret,max(fw[x][0],fw[y][0]));
    }
    int main(){
      scanf("%d%d",&n,&m);
      for(i=1;i<=n;i++){
        scanf("%d",&x);
        if(x==1){
          Q.push(P(0,i));
        }else{
          dis[i]=inf;
        }
      }
      for(i=1;i<=m;i++){
        scanf("%d%d%lld",&e[i].x,&e[i].y,&e[i].z);
        add(e[i].x,e[i].y,e[i].z);
        add(e[i].y,e[i].x,e[i].z);
      }
      while(!Q.empty()){
        P t=Q.top();Q.pop();
        if(dis[t.second]<t.first)continue;
        for(i=g[t.second];i;i=nxt[i])if(dis[v[i]]>t.first+w[i])Q.push(P(dis[v[i]]=t.first+w[i],v[i]));
      }
      for(i=1;i<=m;i++)e[i].z+=dis[e[i].x]+dis[e[i].y];
      sort(e+1,e+m+1,cmp);
      for(i=1;i<=n;i++)g[i]=0;
      ed=0;
      for(i=1;i<=n;i++)f[i]=i;
      for(i=1;i<=m;i++)if(F(e[i].x)!=F(e[i].y)){
        f[f[e[i].x]]=f[e[i].y];
        add(e[i].x,e[i].y,e[i].z);
        add(e[i].y,e[i].x,e[i].z);
      }
      for(i=1;i<=n;i++)if(!vis[i])dfs(i,0);
      scanf("%d",&q);
      while(q--)scanf("%d%d",&x,&y),printf("%lld
    ",ask(x,y));
    }
    

      

    I. Wooden Bridge

    留坑。

    J. Distance

    枚举$O(n^2)$对区间右端点,左端点的取值满足单调性,双指针即可。

    时间复杂度$O(n^2)$。

    #include<cstdio>
    typedef long long ll;
    const int N=1010;
    int Case,n,p,i,j,k,ans,g[N*3];ll a[N],b[N],f[N][N],w[N*3],V;
    inline ll cal(ll x){
      if(x<0)x=-x;
      ll t=1;
      for(int i=0;i<p;i++)t*=x;
      return t;
    }
    int main(){
      scanf("%d",&Case);
      while(Case--){
        scanf("%d%lld%d",&n,&V,&p);
        for(i=1;i<=n;i++)scanf("%lld",&a[i]);
        for(i=1;i<=n;i++)scanf("%lld",&b[i]);
        ans=0;
        for(i=1;i<=n;i++)for(j=1;j<=n;j++)f[i][j]=cal(a[i]-b[j]);
        for(i=0;i<=n+n+5;i++)g[i]=w[i]=0;
        for(i=1;i<=n;i++)for(j=1;j<=n;j++){
          k=i-j+n;
          g[k]++;
          w[k]+=f[i][j];
          int G=g[k];ll W=w[k];
          while(G>0&&W>V){
            G--;
            W-=f[i-G][j-G];
          }
          g[k]=G;
          w[k]=W;
          ans+=G;
        }
        printf("%d
    ",ans);
      }
    }
    

      

  • 相关阅读:
    【LeetCode 104_二叉树_遍历】Maximum Depth of Binary Tree
    【LeetCode 110_二叉树_遍历】Balanced Binary Tree
    【LeetCode 111_二叉树_遍历】Minimum Depth of Binary Tree
    【剑指Offer】36两个链表的第一个公共结点
    【剑指Offer】34第一个只出现一次的字符
    【剑指Offer】33丑数
    【剑指Offer】32把数组排成最小的数
    xgboost的原理没你想像的那么难(转载)
    【剑指Offer】31整数中1出现的次数(从1到n整数中1出现的次数)
    【剑指Offer】28连续子数组的最大和
  • 原文地址:https://www.cnblogs.com/clrs97/p/8215277.html
Copyright © 2020-2023  润新知