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(n\log 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\n",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$,则该连通块中最优解为$d\bmod len$中出现次数最多的那一个。

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

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

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

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

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

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

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

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

总时间复杂度$O(n\sqrt{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\n",k);
    for(i=0;i<=k;i++)printf("%d%c",fin[i],i<k?' ':'\n');
  }
}

  

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\n",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\times mul^b$。

时间复杂度$O(n\log n\log 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\n",ans);
      }
    }
  }
}

UPD:

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

对于每个询问,求出区间指标之和$sum$后,该询问的答案$ans=g^{sum}\bmod 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\n",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\n",U,D);else printf("%lld\n",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(n\log 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\n",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\n",ans);
  }
}

  

H. Traveling Plan

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

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

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

时间复杂度$O(n\log 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\n",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\n",ans);
  }
}

  

posted @ 2018-01-06 20:52 Claris 阅读(...) 评论(...) 编辑 收藏