Potyczki Algorytmiczne 2012

Trial Round:

Obraz

按题意模拟。

#include<cstdio>
const int N=1005;
int n,m,i,j;char a[N],b[N][N];
int main(){
  scanf("%d%d",&n,&m);
  for(i=n;i;i--)for(scanf("%s",a+1),j=1;j<=m;j++)b[j][i]=a[j];
  for(i=1;i<=m;i++)puts(b[i]+1);
}

  

Round 1:

Stół [B]

算出贴着横纵两条底边能放下的椅子数,并加以细节上的特判。

#include<cstdio>
typedef long long ll;
ll A,B,K;
ll solve(ll A,ll B,ll K){
  A/=K,B/=K;
  ll ans=0;
  if(A==1)ans=B;
  if(B==1)ans=A;
  if(A>1&&B>1)ans=(A+B)*2-4;
  return ans;
}
int main(){
  scanf("%lld%lld%lld",&A,&B,&K);
  printf("%lld",solve(A,B,K));
}

  

Round 2:

Mecze [B]

使用long long压位记录每个人在每一局是否位于左半边,那么两个人对局情况相同当且仅当它们对应的long long相同。

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
int n,m,i,x;ll f[40005],base=1;
int main(){
  scanf("%d%d",&n,&m);
  while(m--){
    for(i=1;i<=n;i++){
      scanf("%d",&x);
      if(i<=n/2)f[x]+=base;
    }
    base<<=1;
  }
  sort(f+1,f+n+1);
  for(i=2;i<=n;i++)if(f[i]==f[i-1])return puts("NIE"),0;
  puts("TAK");
}

  

Wojna [A]

模拟博弈的整个流程,可以得到一棵二叉树的结构:每个点代表一个数,左右儿子分别表示这个数的两种可能的来源。

设$f[x]$表示考虑$x$的子树,最终剩下的唯一的数要给$A$时$A$得分减去$B$得分的最优值;

$g[x]$表示考虑$x$的子树,最终剩下的唯一的数要给$B$时$A$得分减去$B$得分的最优值;

从底向上树DP求出答案。

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

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=4000010;
int n,m,i,x,o,al,ar,bl,br,a[N],b[N];ll f[N],g[N];
int main(){
  scanf("%d",&n);
  al=bl=1,ar=br=n;
  for(i=1;i<=n;i++){
    scanf("%d",&x);
    f[a[i]=++m]=x;
    g[m]=-x;
  }
  for(i=1;i<=n;i++){
    scanf("%d",&x);
    f[b[i]=++m]=x;
    g[m]=-x;
  }
  while(al!=ar||bl!=br){
    if(o==0){
      ++m;
      f[m]=max(f[a[al]],f[a[al+1]]);
      g[m]=max(g[a[al]],g[a[al+1]]);
      al+=2;
      b[++br]=m;
    }else{
      ++m;
      f[m]=min(f[b[bl]],f[b[bl+1]]);
      g[m]=min(g[b[bl]],g[b[bl+1]]);
      bl+=2;
      a[++ar]=m;
    }
    o^=1;
  }
  printf("%lld",f[a[ar]]+g[b[br]]);
}

  

Round 3:

Dookoła świata [A]

Dijkstra求出$1$到每个点顺时针总距离减去逆时针总距离本质不同的最短路和次短路,即可求出顺时针总距离不等于逆时针总距离的最短路。

时间复杂度$O(m\log n)$。

#include<cstdio>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
typedef pair<ll,ll>P;
typedef pair<P,int>PI;
const int N=100010,M=200010,K=1296000;
const ll inf=1LL<<60;
int n,m,i,a[N],x,y,z,g[N],v[M<<1],w[M<<1],c[M<<1],nxt[M<<1],ed;
P d[N][2];
priority_queue<PI,vector<PI>,greater<PI> >q;
inline void add(int x,int y,int z,int t){v[++ed]=y;w[ed]=z;c[ed]=t;nxt[ed]=g[x];g[x]=ed;}
inline void ext(int x,ll y,ll z){
  if(d[x][0].first>y){
    if(d[x][0].second!=z)d[x][1]=d[x][0];
    q.push(PI(d[x][0]=P(y,z),x));
    return;
  }
  if(d[x][0].second==z)return;
  if(d[x][1].first>y)q.push(PI(d[x][1]=P(y,z),x));
}
int main(){
  scanf("%d%d",&n,&m);
  for(i=1;i<=n;i++)scanf("%d",&a[i]);
  while(m--){
    scanf("%d%d%d%d",&x,&y,&z,&i);
    if(i==-1)swap(x,y);
    if(a[x]<a[y]){
      add(x,y,z,a[y]-a[x]);
      add(y,x,z,-a[y]+a[x]);
    }else{
      add(x,y,z,a[y]-a[x]+K);
      add(y,x,z,-a[y]+a[x]-K);
    }
  }
  for(i=1;i<=n;i++)d[i][0]=d[i][1]=P(inf,inf);
  ext(1,0,0);
  while(!q.empty()){
    PI t=q.top();q.pop();
    for(i=g[t.second];i;i=nxt[i])ext(v[i],t.first.first+w[i],t.first.second+c[i]);
  }
  if(d[1][1].first==inf)d[1][1].first=-1;
  printf("%lld",d[1][1].first);
}

  

Malowanie [B]

预处理出所有前后缀的矩形交,被恰好$n-1$个矩形覆盖等价于恰好没有被某个矩形覆盖,枚举恰好没被哪个矩形覆盖,那么对应的面积可以由前后缀的矩形交算出。

由于被所有$n$个矩形都覆盖住的部分被重复计算了$n$次,因此最后需要减去所有矩形交的面积乘以$(n-1)$。

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

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=500005,inf=1000000010;
int n,i;ll ans;
struct E{
  int xl,xr,yl,yr;
  E(){}
  E(int _xl,int _xr,int _yl,int _yr){xl=_xl,xr=_xr,yl=_yl,yr=_yr;}
  void read(){scanf("%d%d%d%d",&xl,&yl,&xr,&yr);}
  E operator+(const E&b)const{return E(max(xl,b.xl),min(xr,b.xr),max(yl,b.yl),min(yr,b.yr));}
  ll area(){return xl<xr&&yl<yr?1LL*(xr-xl)*(yr-yl):0;}
}e[N],pre[N],suf[N];
int main(){
  scanf("%d",&n);
  pre[0]=suf[n+1]=E(-inf,inf,-inf,inf);
  for(i=1;i<=n;i++)e[i].read(),pre[i]=pre[i-1]+e[i];
  for(i=n;i;i--)suf[i]=suf[i+1]+e[i],ans+=(pre[i-1]+suf[i+1]).area();
  printf("%lld",ans-pre[n].area()*(n-1));
}

  

Round 4:

Bandżo [B]

离散化后设$f[i]$表示覆盖$i$点的区间数,则需要找一对$(a,b)$满足$f[a]+f[b]-$同时覆盖$[a,b]$的区间数最大。

不妨设$a\leq b$,从左往右枚举$b$,对于每个$a$维护$v[a]=f[a]-$同时覆盖$[a,b]$的区间数,当$b$增加$1$时需要对$v$进行区间修改,使用线段树打标记维护。

时间复杂度$O(n\log n)$。

#include<cstdio>
#include<algorithm>
using namespace std;
const int N=500010,M=2333333;
int _,n,m,i,j,x,y,ans,a[N<<1],f[N<<1],e[N][2],val[M],tag[M],ga[N<<1],gd[N<<1],v[N<<1],nxt[N<<1],ed;
inline void umax(int&a,int b){a<b?(a=b):0;}
inline void up(int x){val[x]=max(val[x<<1],val[x<<1|1]);}
inline void tag1(int x,int p){val[x]+=p,tag[x]+=p;}
void build(int x,int a,int b){
  if(a==b){val[x]=f[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 p){
  if(c<=a&&b<=d){tag1(x,p);return;}
  if(tag[x])tag1(x<<1,tag[x]),tag1(x<<1|1,tag[x]),tag[x]=0;
  int mid=(a+b)>>1;
  if(c<=mid)change(x<<1,a,mid,c,d,p);
  if(d>mid)change(x<<1|1,mid+1,b,c,d,p);
  up(x);
}
inline void add(int&x,int y){v[++ed]=y;nxt[ed]=x;x=ed;}
int main(){
  scanf("%d",&_);
  while(_--){
    scanf("%d%d",&x,&y);
    if(x==y)continue;
    e[++n][0]=x;
    e[n][1]=y;
    a[++m]=x;
    a[++m]=y;
  }
  if(n<=1)return printf("%d",n),0;
  sort(a+1,a+m+1);
  for(_=0,i=1;i<=m;i++)if(i==1||a[i]>a[_])a[++_]=a[i];
  m=_;
  for(i=1;i<=n;i++){
    x=lower_bound(a+1,a+m+1,e[i][0])-a;
    y=lower_bound(a+1,a+m+1,e[i][1])-a;
    f[x]++,f[y]--;
    add(ga[x],y),add(gd[y],x);
  }
  for(i=1;i<=m;i++)f[i]+=f[i-1];
  build(1,1,m);
  for(i=1;i<=m;i++){
    for(j=ga[i];j;j=nxt[j])change(1,1,m,i,v[j]-1,-1);
    for(j=gd[i];j;j=nxt[j])change(1,1,m,v[j],i-1,1);
    umax(ans,f[i]+val[1]);
  }
  printf("%d",ans);
}

  

 

Renowacja dróg [A]

令$in[x],out[x]$分别表示连入/连出$x$的边权最小的边,则一个可行解是$\sum(in[i]+out[i])$。

对于一条边$x\rightarrow y$,其边权为$z$,可以同时满足$x$点的连出以及$y$点的连入,利用它调整上述可行解的代价为$-out[x]-in[y]+z$,使用KM求解最小费用流即可。

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

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=305,M=500005,inf=3*N*M;
int n,m,i,j,x,y,z,p,py,in[N],out[N],g[N][N],lk[N],pre[N];
bool vy[N];ll lx[N],ly[N],d,w[N][N],slk[N],ans;
inline void add(int x,int y,ll z){if(w[y][x]<z)w[y][x]=z;}
int main(){
  scanf("%d%d",&n,&m);
  for(i=1;i<=n;i++)for(j=1;j<=n;j++)g[i][j]=inf;
  for(i=1;i<=n;i++)in[i]=out[i]=inf;
  while(m--){
    scanf("%d%d%d",&x,&y,&z);
    g[x][y]=min(g[x][y],z);
    in[y]=min(in[y],z);
    out[x]=min(out[x],z);
  }
  for(i=1;i<=n;i++){
    if(in[i]==inf||out[i]==inf)return puts("NIE"),0;
    ans+=in[i]+out[i];
    for(j=1;j<=n;j++)if(g[i][j]<inf)add(i,j,out[i]+in[j]-g[i][j]);
  }
  for(i=1;i<=n;i++)for(j=1;j<=n;j++)lx[i]=max(lx[i],w[i][j]);
  for(i=1;i<=n;i++){
    for(j=1;j<=n;j++)slk[j]=inf,vy[j]=0;
    for(lk[py=0]=i;lk[py];py=p){
      vy[py]=1;d=inf;x=lk[py];
      for(y=1;y<=n;y++)if(!vy[y]){
        if(lx[x]+ly[y]-w[x][y]<slk[y])slk[y]=lx[x]+ly[y]-w[x][y],pre[y]=py;
        if(slk[y]<d)d=slk[y],p=y;
      }
      for(y=0;y<=n;y++)if(vy[y])lx[lk[y]]-=d,ly[y]+=d;else slk[y]-=d;
    }
    for(;py;py=pre[py])lk[py]=lk[pre[py]];
  }
  for(i=1;i<=n;i++)ans-=lx[i]+ly[i];
  printf("%lld",ans);
}

  

Round 5:

Nożyczki [B]

建立无向图,$n$个点表示多边形的顶点。

对于每个顶点,分别往上下左右四个方向尝试往多边形的严格内部发射射线,使用扫描线+set快速找到射线与多边形的交点,如果交点也是顶点,则在两点间连一条无向边。

假设一个连通块有$k$个点,且成功发射了射线,则对答案的贡献为$\lfloor\frac{k+1}{2}\rfloor$。

时间复杂度$O(n\log n)$。

#include<cstdio>
#include<algorithm>
#include<set>
#include<map>
using namespace std;
typedef pair<int,int>PI;
typedef pair<PI,int>PII;
typedef long long ll;
const int N=100005,inf=1000000010;
int n,cnt,ans,i,ce,pre,tmp,f[N],g[N];bool flag,mark[N];
set<PII>T;
map<PI,int>idx;
struct P{int x,y;}a[N];
struct E{int t,l,r;E(){}E(int _t,int _l,int _r){t=_t,l=_l,r=_r;}}e[N];
inline bool cmp(const E&a,const E&b){return a.t<b.t;}
inline ll cross(const P&a,const P&b){return 1LL*a.x*b.y-1LL*a.y*b.x;}
inline void cover(int l,int r,int p){
  set<PII>::iterator it;
  while(1){
    it=T.lower_bound(PII(PI(l,-inf),-inf));
    if(it->first.first>r)break;
    if(it->first.second<=r)T.erase(it);
    else{
      PII tmp=*it;
      tmp.first.first=r+1;
      T.erase(it);
      T.insert(tmp);
      break;
    }
  }
  T.insert(PII(PI(l,r),p));
  it=T.find(PII(PI(l,r),p));
  it--;
  if(it->first.second>=l&&it->first.second<=r){
    PII tmp=*it;
    tmp.first.second=l-1;
    T.erase(it);
    T.insert(tmp);
  }else if(it->first.second>r){
    PII tmp=*it,tmp2=tmp;
    tmp.first.second=l-1;
    tmp2.first.first=r+1;
    T.erase(it);
    T.insert(tmp);
    T.insert(tmp2);
  }
}
inline int ask(int x){
  set<PII>::iterator it=T.lower_bound(PII(PI(x+1,-inf),-inf));
  it--;
  return it->second;
}
inline int getid(int x,int y){
  int&ret=idx[PI(x,y)];
  if(!ret&&!flag){
    ret=++cnt;
    f[cnt]=cnt;
    g[cnt]=1;
  }
  return ret;
}
int F(int x){return f[x]==x?x:f[x]=F(f[x]);}
inline void addedge(int A,int B,int C,int D){
  int x=getid(A,B),y=getid(C,D);
  mark[x]=1;
  if(!y)return;
  if(F(x)!=F(y))g[f[y]]+=g[f[x]],f[f[x]]=f[y];
}
int main(){
  scanf("%d",&n);
  for(i=0;i<n;i++)scanf("%d%d",&a[i].x,&a[i].y);
  ll area=0;
  for(i=0;i<n;i++)area+=cross(a[i],a[(i+1)%n]);
  if(area<0)reverse(a,a+n);
  a[n]=a[0];
  for(i=0;i<n;i++)getid(a[i].x,a[i].y);
  flag=1;
  //up
  ce=0;
  for(i=0;i<n;i++){
    if(a[i].y==a[i+1].y&&a[i].x>a[i+1].x){
      e[++ce]=E(a[i].y,a[i+1].x,a[i].x);
      continue;
    }
    pre=(i-1+n)%n;
    if(a[i].y==a[i+1].y&&a[i].x<a[i+1].x&&a[pre].y<a[i].y){
      e[++ce]=E(a[i].y,a[i].x,inf);
      continue;
    }
    if(a[i].x==a[i+1].x&&a[i].y>a[i+1].y&&a[pre].x<a[i].x){
      e[++ce]=E(a[i].y,a[i].x,inf);
      continue;
    }
  }
  sort(e+1,e+ce+1,cmp);
  T.insert(PII(PI(-inf-1,-inf),-inf));
  T.insert(PII(PI(inf,inf+1),-inf));
  for(i=ce;i;i--){
    if(e[i].r!=inf)cover(e[i].l,e[i].r,e[i].t);
    else addedge(e[i].l,e[i].t,e[i].l,ask(e[i].l));
  }
  //down
  ce=0;
  for(i=0;i<n;i++){
    if(a[i].y==a[i+1].y&&a[i].x<a[i+1].x){
      e[++ce]=E(a[i].y,a[i].x,a[i+1].x);
      continue;
    }
    pre=(i-1+n)%n;
    if(a[i].y==a[i+1].y&&a[i].x>a[i+1].x&&a[pre].y>a[i].y){
      e[++ce]=E(a[i].y,a[i].x,inf);
      continue;
    }
    if(a[i].x==a[i+1].x&&a[i].y<a[i+1].y&&a[pre].x>a[i].x){
      e[++ce]=E(a[i].y,a[i].x,inf);
      continue;
    }
  }
  sort(e+1,e+ce+1,cmp);
  T.clear();
  T.insert(PII(PI(-inf-1,-inf),-inf));
  T.insert(PII(PI(inf,inf+1),-inf));
  for(i=1;i<=ce;i++){
    if(e[i].r!=inf)cover(e[i].l,e[i].r,e[i].t);
    else addedge(e[i].l,e[i].t,e[i].l,ask(e[i].l));
  }
  //left
  ce=0;
  for(i=0;i<n;i++){
    if(a[i].x==a[i+1].x&&a[i].y>a[i+1].y){
      e[++ce]=E(a[i].x,a[i+1].y,a[i].y);
      continue;
    }
    pre=(i-1+n)%n;
    if(a[i].y==a[i+1].y&&a[i].x<a[i+1].x&&a[pre].y<a[i].y){
      e[++ce]=E(a[i].x,a[i].y,inf);
      continue;
    }
    if(a[i].x==a[i+1].x&&a[i].y<a[i+1].y&&a[pre].x>a[i].x){
      e[++ce]=E(a[i].x,a[i].y,inf);
      continue;
    }
  }
  sort(e+1,e+ce+1,cmp);
  T.clear();
  T.insert(PII(PI(-inf-1,-inf),-inf));
  T.insert(PII(PI(inf,inf+1),-inf));
  for(i=1;i<=ce;i++){
    if(e[i].r!=inf)cover(e[i].l,e[i].r,e[i].t);
    else addedge(e[i].t,e[i].l,ask(e[i].l),e[i].l);
  }
  //right
  ce=0;
  for(i=0;i<n;i++){
    if(a[i].x==a[i+1].x&&a[i].y<a[i+1].y){
      e[++ce]=E(a[i].x,a[i].y,a[i+1].y);
      continue;
    }
    pre=(i-1+n)%n;
    if(a[i].y==a[i+1].y&&a[i].x>a[i+1].x&&a[pre].y>a[i].y){
      e[++ce]=E(a[i].x,a[i].y,inf);
      continue;
    }
    if(a[i].x==a[i+1].x&&a[i].y>a[i+1].y&&a[pre].x<a[i].x){
      e[++ce]=E(a[i].x,a[i].y,inf);
      continue;
    }
  }
  sort(e+1,e+ce+1,cmp);
  T.clear();
  T.insert(PII(PI(-inf-1,-inf),-inf));
  T.insert(PII(PI(inf,inf+1),-inf));
  for(i=ce;i;i--){
    if(e[i].r!=inf)cover(e[i].l,e[i].r,e[i].t);
    else addedge(e[i].t,e[i].l,ask(e[i].l),e[i].l);
  }
  for(i=1;i<=cnt;i++)if(F(i)==i&&mark[i])ans+=(g[i]+1)/2;
  printf("%d",ans);
}

  

Tanie linie [A]

重复$k$次,每次取出和最大的子区间,如果它的和非负则把答案加上对应的和,然后将该区间每个数都乘以$-1$,线段树维护。

时间复杂度$O(k\log n)$。

#include<cstdio>
#define N 2097150
typedef long long ll;
int n,k,i,a[N];ll ans;
struct P{
  ll vl,vr,vm,s;int pl,pr,l,r;
  P(){}
  P(int x,ll v){pl=pr=l=r=x,vl=vr=vm=s=v;}
  inline P operator+(P b){
    P c;
    c.s=s+b.s;
    c.vl=vl,c.pl=pl;
    if(s+b.vl>c.vl)c.vl=s+b.vl,c.pl=b.pl;
    c.vr=b.vr,c.pr=b.pr;
    if(vr+b.s>c.vr)c.vr=vr+b.s,c.pr=pr;
    c.vm=vr+b.vl,c.l=pr,c.r=b.pl;
    if(vm>c.vm)c.vm=vm,c.l=l,c.r=r;
    if(b.vm>c.vm)c.vm=b.vm,c.l=b.l,c.r=b.r;
    return c;
  }
}tmp;
struct Q{P mi,ma;bool tag;}T[N];
inline void read(int&a){
  char c;bool f=0;a=0;
  while(!((((c=getchar())>='0')&&(c<='9'))||(c=='-')));
  if(c!='-')a=c-'0';else f=1;
  while(((c=getchar())>='0')&&(c<='9'))(a*=10)+=c-'0';
  if(f)a=-a;
}
inline void rev1(int x){tmp=T[x].mi,T[x].mi=T[x].ma,T[x].ma=tmp,T[x].tag^=1;}
inline void pb(int x){if(T[x].tag)T[x].tag=0,rev1(x<<1),rev1(x<<1|1);}
inline void up(int x){T[x].mi=T[x<<1].mi+T[x<<1|1].mi,T[x].ma=T[x<<1].ma+T[x<<1|1].ma;}
void build(int x,int a,int b){
  if(a==b){T[x].mi=P(a,-::a[a]),T[x].ma=P(a,::a[a]);return;}
  int mid=(a+b)>>1;
  build(x<<1,a,mid),build(x<<1|1,mid+1,b),up(x);
}
void rev(int x,int a,int b,int c,int d){
  if(c<=a&&b<=d){rev1(x);return;}
  pb(x);
  int mid=(a+b)>>1;
  if(c<=mid)rev(x<<1,a,mid,c,d);
  if(d>mid)rev(x<<1|1,mid+1,b,c,d);
  up(x);
}
int main(){
  for(read(n),read(k),i=1;i<=n;i++)read(a[i]);
  build(1,1,n);
  while(k--)if(T[1].ma.vm>0)ans+=T[1].ma.vm,rev(1,1,n,T[1].ma.l,T[1].ma.r);else break;
  printf("%lld",ans);
  return 0;
}

  

Wypożyczalnia nart [A]

令$s[i]$表示前$i$个数的和,则对于固定的左端点$l$,需要找一个点$(r,s[r])$满足$r\geq l$且$\frac{s[r]-s[l-1]}{r-(l-1)}$最大,显然$(r,s[r])$是$(l-1,s[l-1])$到$[r,n]$凸包的切点。

单点修改等价于将一个区间的所有点往上/往下平移,用线段树从左往右维护这些点,每个节点利用可持久Treap维护区间凸包,那么平移可以通过打标记实现。

查询操作只需要在线段树上拆成$O(\log n)$个节点,然后在每个节点对应的凸包上二分找到切点。

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

分块维护凸包时间复杂度为$O(n\sqrt{n\log n})$,卡常数也可以通过。

#pragma GCC optimize("-funsafe-math-optimizations")
#pragma GCC optimize("-freciprocal-math")
#pragma GCC optimize("-fno-trapping-math")
#pragma GCC optimize("-ffinite-math-only")
#pragma GCC optimize("-fno-stack-protector")
#include<cstdio>
typedef long long ll;
const int N=500005,K=1450,M=N/K+3,BUF=40000007,OUT=11500007;
char Buf[BUF],*buf=Buf,Out[OUT],*ou=Out;int Outn[30],Outcnt;
int n,m,i,x,y,a[N],all,at[N],st[M],en[M],tail[M];
ll tag[M],s[N],pre[N],U,F;int D,G,q[N],d[N];
inline void read(int&a){for(a=0;*buf<48;buf++);while(*buf>47)a=a*10+*buf++-48;}
inline char readch(){while(*buf!='P'&&*buf!='Z')buf++;return*buf++;}
inline void write(ll x){
  if(!x)*ou++=48;
  else{
    for(Outcnt=0;x;x/=10)Outn[++Outcnt]=x%10+48;
    while(Outcnt)*ou++=Outn[Outcnt--];
  }
}
inline void change(int x,int y){
  y-=a[x];
  if(!y)return;
  a[x]+=y;
  int X=at[x],i;
  if(x-st[X]>en[X]-x){
    for(i=en[X];i-8>=x;i-=8){
      s[i]+=y;
      s[i-1]+=y;
      s[i-2]+=y;
      s[i-3]+=y;
      s[i-4]+=y;
      s[i-5]+=y;
      s[i-6]+=y;
      s[i-7]+=y;
    }
    for(;i>=x;i--)s[i]+=y;
  }else{
    tag[X]+=y;
    for(i=st[X];i+8<x;i+=8){
      s[i]-=y;
      s[i+1]-=y;
      s[i+2]-=y;
      s[i+3]-=y;
      s[i+4]-=y;
      s[i+5]-=y;
      s[i+6]-=y;
      s[i+7]-=y;
    }
    for(;i<x;i++)s[i]-=y;
  }
  tail[X]=0;
  for(i=X+1;i+8<=all;i+=8){
    tag[i]+=y;
    tag[i+1]+=y;
    tag[i+2]+=y;
    tag[i+3]+=y;
    tag[i+4]+=y;
    tag[i+5]+=y;
    tag[i+6]+=y;
    tag[i+7]+=y;
  }
  for(;i<=all;i++)tag[i]+=y;
}
inline void up(ll A,int B){A*D>U*B?(U=A,D=B):0;}
inline void ask(int o){
  if(!tail[o]){
    int i,l=st[o],r=en[o],L=l,R=l;
    q[L]=l;
    for(i=l+1;i<=r;i++){
      while(L<R&&pre[R]*(i-q[R])<=(s[i]-s[q[R]])*d[R])R--;
      q[++R]=i;
      pre[R]=s[i]-s[q[R-1]];
      d[R]=i-q[R-1];
    }
    tail[o]=R;
  }
  int l=st[o]+1,r=tail[o],mid,t=l-1;ll base=tag[o]-F;
  if(l<=r)if(pre[l]*(q[l]-G)>=(s[q[l]]+base)*d[l])l=(t=l)+1;else r=l-1;
  int k=1;
  while(l+k<=r){
    mid=l+k;
    if(pre[mid]*(q[mid]-G)>=(s[q[mid]]+base)*d[mid])l=(t=mid)+1;else{
      r=mid-1;
      break;
    }
    k<<=1;
  }
  while(l<=r){
    mid=(l+r)>>1;
    if(pre[mid]*(q[mid]-G)>=(s[q[mid]]+base)*d[mid])l=(t=mid)+1;else r=mid-1;
  }
  up(s[q[t]]+base,q[t]-G);
}
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
inline void query(int x){
  F=x>1?s[x-1]+tag[at[x-1]]:0,G=x-1;
  U=a[x],D=1;
  int i,X=at[x],r=en[X];ll cur=a[x];
  for(i=x+1;i<=r;i++)up(cur+=a[i],i-G);
  for(i=X+1;i<=all;i++)ask(i);
  if(!U)D=1;else F=gcd(U,D),U/=F,D/=F;
  write(U),*ou++='/',write(D),*ou++='\n';
}
int main(){
  fread(Buf,1,BUF,stdin),read(n),read(m);
  for(i=1;i<=n;i++)read(a[i]),s[i]=s[i-1]+a[i];
  for(i=1;i<=n;i++)at[i]=i/K;
  all=at[n];
  for(i=1;i<=n;i++)en[at[i]]=i;
  for(i=n;i;i--)st[at[i]]=i;
  while(m--)if(readch()=='P')read(x),read(y),change(x,y);else read(x),query(x);
  fwrite(Out,1,ou-Out,stdout);
}

  

Zima [B]

先把每条边不断重复来回走(即每轮走$2$次),直到遍历次数至少为$d[i]$,可以得到一个满足条件的回路。

假设最终方案对应路径的起点为$S$,终点为$T$,那么:

  • 对于$S$到$T$路径上的边:$d$为偶数的要多支付$1$的代价,$d$为奇数的要少支付$1$的代价。
  • 对于不在$S$到$T$路径上的边,对总代价的贡献不变。

将$d$为奇数的边的权值设为$-1$,$d$为偶数的边的边权设为$1$,树DP找到边权和最小的路径即可。

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

#include<cstdio>
const int N=500005;
int n,i,x,y,z,ans,g[N],v[N<<1],w[N<<1],nxt[N<<1],ed,f[N];long long base;
inline void add(int x,int y,int z){v[++ed]=y;w[ed]=z;nxt[ed]=g[x];g[x]=ed;}
inline void up(int&a,int b){a>b?(a=b):0;}
void dfs(int x,int y){
  for(int i=g[x];i;i=nxt[i]){
    int u=v[i];
    if(u==y)continue;
    dfs(u,x);
    up(ans,f[x]+f[u]+w[i]);
    up(f[x],f[u]+w[i]);
  }
}
int main(){
  scanf("%d",&n);
  for(i=1;i<n;i++){
    scanf("%d%d%d",&x,&y,&z);
    base+=z;
    if(z&1){
      base++;
      add(x,y,-1);
      add(y,x,-1);
    }else{
      add(x,y,1);
      add(y,x,1);
    }
  }
  dfs(1,0);
  printf("%lld",base+ans);
}

  

Round 6:

Dwójkowy zbijak [A]

对于一个$n$,SG值为$i$的棋子数为$(n>>i)-(n>>(i+1))$。

由于SG异或只和奇偶性有关,因此我们只关心$n$二进制中相邻两位是否不同。

二分答案之后数位DP即可。

#include<cstdio>
typedef long long ll;
int K,m,a[70];ll l=1,r=1000000000000000LL,mid,ans,f[70][70][2][2];
inline ll check(ll n){
  int i,j,k,t,x;
  for(m=0;n;n>>=1)a[m++]=n&1;
  for(i=0;i<=m;i++)for(j=0;j<64;j++)for(k=0;k<2;k++)for(t=0;t<2;t++)f[i][j][k][t]=0;
  f[m][0][0][0]=1;
  for(i=m-1;~i;i--)for(j=0;j<64;j++)for(k=0;k<2;k++)for(t=0;t<2;t++)if(f[i+1][j][k][t])for(x=t?1:a[i];~x;x--)f[i][j^(i*(k^x))][x][t|(x<a[i])]+=f[i+1][j][k][t];
  return f[0][0][0][0]+f[0][0][0][1]+f[0][0][1][0]+f[0][0][1][1]-1;
}
int main(){
  scanf("%d",&K);
  while(l<=r)if(check(mid=(l+r)>>1)>=K)r=(ans=mid)-1;else l=mid+1;
  return printf("%lld",ans),0;
}

  

Fosa [A]

首先将重合或相交的线段合并,然后对于每个交点,求出其往上下左右延伸的长度,设

$a[P]$表示$\min(P往下延伸的长度,P往右延伸的长度)$,

$b[P]$表示$\min(P往上延伸的长度,P往左延伸的长度)$。

枚举正方形的对角线,假设对角线左上角是点$P$,右下角是点$Q$,则它们之间横坐标的差值应不超过$\min(a[P],b[Q])$,即找到一对点$(P,Q)$,满足$x[Q]-x[P]$最大,且:

  • $x[P]+a[P]\geq x[Q]$
  • $x[Q]-b[Q]\leq x[P]$

从左上到右下枚举每个点作为$Q$,那么满足$x[Q]-b[Q]\leq x[P]$的$P$是一个后缀,二分找到满足条件的$x[P]$最小的点$P$后:

  • 如果$x[P]+a[P]\geq x[Q]$,则拿$x[Q]-x[P]$更新答案。
  • 否则$x[P]+a[P]<x[Q]$,由于我们按照$x[Q]$从小到大枚举$Q$,因此后续的$Q$也不能拿$P$更新答案,直接将$P$删除后考虑下一个点作为$P$,可以使用并查集实现。

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

#include<cstdio>
#include<algorithm>
using namespace std;
const int N=5005,M=6250007,inf=2000000010;
int _,ca,cb,n,ans,i,j,k,f[M],g[M],p[M],d[M],q[M],fa[M];
struct P{
  int xl,yl,xr,yr;
  P(){}
  P(int _xl,int _xr,int _yl,int _yr){xl=_xl,xr=_xr,yl=_yl,yr=_yr;}
}a[N],b[N],pool[N];
inline bool check(const P&A,const P&B){return B.xl<=A.xl&&A.xl<=B.xr&&A.yl<=B.yl&&B.yl<=A.yr;}
inline bool cmpP(const P&A,const P&B){return A.yl==B.yl?A.xl<B.xl:A.yl<B.yl;}
inline bool cmpd(int A,int B){return d[A]==d[B]?p[A]<p[B]:d[A]<d[B];}
void merge(P*a,int&n){
  int m=0,i,j,x;
  sort(a+1,a+n+1,cmpP);
  for(i=1;i<=n;i=j){
    for(j=i;j<=n&&a[j].yl==a[i].yl;j++);
    int L=-inf,R=-inf;
    for(x=i;x<j;x++){
      if(a[x].xl>R){
        if(L<R)pool[++m]=P(L,R,a[i].yl,a[i].yl);
        L=a[x].xl,R=a[x].xr;
      }else if(R<a[x].xr)R=a[x].xr;
    }
    if(L<R)pool[++m]=P(L,R,a[i].yl,a[i].yl);
  }
  for(n=m,i=1;i<=n;i++)a[i]=pool[i];
}
int F(int x){return fa[x]==x?x:fa[x]=F(fa[x]);}
inline int ask(int l,int r,int o){
  int t=inf,mid;
  while(l<=r)if(p[q[mid=(l+r)>>1]]>=o)r=(t=mid)-1;else l=mid+1;
  return t;
}
int main(){
  scanf("%d",&_);
  while(_--){
    int x1,y1,x2,y2;
    scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
    if(x1==x2&&y1==y2)continue;
    if(x1==x2){
      if(y1>y2)swap(y1,y2);
      a[++ca]=P(x1,x2,y1,y2);
    }else{
      if(x1>x2)swap(x1,x2);
      b[++cb]=P(x1,x2,y1,y2);
    }
  }
  for(i=1;i<=ca;i++)swap(a[i].xl,a[i].yl),swap(a[i].xr,a[i].yr);
  merge(a,ca);
  for(i=1;i<=ca;i++)swap(a[i].xl,a[i].yl),swap(a[i].xr,a[i].yr);
  merge(b,cb);
  for(i=1;i<=ca;i++)for(j=1;j<=cb;j++)if(check(a[i],b[j])){
    n++;
    f[n]=a[i].xl+min(b[j].xr-a[i].xl,b[j].yl-a[i].yl);
    g[n]=a[i].xl-min(a[i].xl-b[j].xl,a[i].yr-b[j].yl);
    p[n]=a[i].xl;
    d[n]=a[i].xl+b[j].yl;
    q[n]=n;
  }
  sort(q+1,q+n+1,cmpd);
  for(i=1;i<=n;i=j){
    for(j=i;j<=n&&d[q[i]]==d[q[j]];j++);
    for(k=i;k<=j;k++)fa[k]=k;
    for(k=i;k<j;k++){
      int x=q[k],B=p[x],o=ask(i,j-1,g[x]);
      while(o<j){
        o=F(o);
        if(o>=j)break;
        if(f[q[o]]<B)fa[o]++;
        else{
          int now=B-p[q[o]];
          if(now>ans)ans=now;
          break;
        }
      }
    }
  }
  if(!ans)puts("NIE");else printf("%d",ans);
}

  

Korniki [B]

当$n$是奇数时,每个人的最优方案一定是同时拿走两端的两个数,可以直接$O(1)$计算答案。

当$n$是偶数时,先手的三个操作中只有一个会导致后续状态的$n$为偶数,剩下两个操作可以$O(1)$计算答案,一共得到$O(n)$个可能的状态,暴力DP即可。

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

#include<cstdio>
typedef long long ll;
const int N=1000010;
int n,i,a[N];ll sum,diff,s[N],f[N];
inline void read(int&a){char c;while(!(((c=getchar())>='0')&&(c<='9')));a=c-'0';while(((c=getchar())>='0')&&(c<='9'))(a*=10)+=c-'0';}
inline void up(ll&a,ll b){a<b?(a=b):0;}
inline ll cal(int l,int r){
  if(l&1)return s[r]-s[l-1];
  return s[l-1]-s[r];
}
int main(){
  read(n);
  for(i=1;i<=n;i++){
    read(a[i]);
    sum+=a[i];
    if(i&1)s[i]=s[i-1]+a[i];else s[i]=s[i-1]-a[i];
  }
  if(n&1)diff=cal(1,n);
  else{
    f[n/2]=a[n/2]+a[n/2+1];
    for(i=n/2-1;i;i--){
      f[i]=a[i]+a[n-i+1]-f[i+1];
      up(f[i],a[i]-cal(i+1,n-i+1));
      up(f[i],a[n-i+1]-cal(i,n-i));
    }
    diff=f[1];
  }
  printf("%lld %lld",(sum+diff)/2,(sum-diff)/2);
}

  

Pająk [B]

$ans=4n-\min(sum,9n-sum)-6$,其中$sum=\sum_{i=1}^n (z_i+1)$。

#include<cstdio>
#include<algorithm>
int n,i,x,sum;
int main(){
  scanf("%d",&n);
  for(i=0;i<n;i++)scanf("%d",&x),sum+=x+1;
  printf("%d",n*4-std::min(sum,n*9-sum)-6);
}

  

Trial Finals:

Niekolejne

$ans=\lfloor\sqrt{n}\rfloor$。

#include<cstdio>
#include<cmath>
long long n,m;
int main(){
  scanf("%lld",&n);
  m=sqrt(n);
  while((m+1)*(m+1)<=n)m++;
  printf("%lld",m);
}

  

Finals:

Two Cakes

考虑DP,设$f[i][j]$表示考虑了$a[1..i]$和$b[1..j]$的最小代价。

若$a[i]==b[j]$,则$f[i][j]=\min(f[i-1][j],f[i][j-1])+1$。

否则找到最大的$t$,满足$x$和$y$往前$t$个均不相等,此时$f[i][j]=f[i-t-1][j-t-1]+t$。

对于$t$,可以通过在相应差值的序列中二分查找得到。

对于DP的计算,可以通过搜索,并将那$n$个$a[i]==b[j]$的状态记忆化。

因为对于每个没有记忆化的状态,均可以在$O(\log n)$的时间内转化为那$n$个状态,所以总时间复杂度为$O(n\log n)$。

#include<cstdio>
const int N=1000010,BUF=13778000;
int n,m,i,j,a[N],b[N],c[N],f[N],g[N<<1],nxt[N],st[N<<1],en[N<<1],q[N];char Buf[BUF],*buf=Buf;
inline void read(int&a){for(a=0;*buf<48;buf++);while(*buf>47)a=a*10+*buf++-48;}
inline void add(int x,int y){nxt[y]=g[x];g[x]=y;}
inline int min(int a,int b){return a<b?a:b;}
inline int pre(int l,int r,int x){
  int t=0,mid;
  while(l<=r)if(q[mid=(l+r)>>1]<=x)l=(t=mid)+1;else r=mid-1;
  return q[t];
}
int dp(int x,int y){
  if(!x||!y)return x+y;
  if(a[x]==b[y])return f[x]?f[x]:f[x]=min(dp(x-1,y),dp(x,y-1))+1;
  int t=pre(st[x-y+n],en[x-y+n],x);
  return t?dp(t,y-x+t)+x-t:(x>y?x:y);
}
int main(){
  fread(Buf,1,BUF,stdin);read(n);
  for(i=1;i<=n;i++)read(a[i]);
  for(i=1;i<=n;i++)read(b[i]),c[b[i]]=i;
  for(i=n;i;i--)add(i-c[a[i]]+n,i);
  for(i=1;i<n+n;en[i++]=m)for(st[i]=m+1,j=g[i];j;j=nxt[j])q[++m]=j;
  return printf("%d",dp(n,n)),0;
}

  

Alien Invasion

等价于选择一些不相邻的城市,满足权值和最大。

#include<cstdio>
typedef long long ll;
const int N=1000010;
int n,i,x;ll s,f[N];
inline void up(ll&a,ll b){a<b?(a=b):0;}
int main(){
  scanf("%d",&n);
  for(i=1;i<=n;i++){
    scanf("%d",&x);
    f[i]=s+x;
    up(s,f[i-1]);
  }
  up(s,f[n]);
  printf("%lld",s);
}

  

Circles

从左往右扫描线,在$x_i-r_i$处插入圆$i$,在$x_i+r_i$处删除圆$i$。

使用set从下到上维护扫描线上的圆,插入/删除圆时检查set中相邻的圆是否相切。

时间复杂度$O(n\log n)$。

#include<cstdio>
#include<algorithm>
#include<set>
using namespace std;
typedef pair<int,int>P;
typedef long long ll;
const int N=500005,inf=1000000010;
int n,m,cnt,ans,i,o;P pool[N*3];set<P>T;set<P>::iterator it,pre,nxt;
struct Circle{int x,y,r;}c[N];
struct E{
  int x,t;
  E(){}
  E(int _x,int _t){x=_x,t=_t;}
}e[N<<1];
inline bool cmp(const E&a,const E&b){
  if(a.x!=b.x)return a.x<b.x;
  return a.t>b.t;
}
inline ll sqr(ll x){return x*x;}
inline void check(int x,int y){
  if(!x||!y)return;
  if(x>y)swap(x,y);
  if(sqr(c[x].x-c[y].x)+sqr(c[x].y-c[y].y)==sqr(c[x].r+c[y].r))pool[++cnt]=P(x,y);
}
int main(){
  scanf("%d",&n);
  for(i=1;i<=n;i++){
    scanf("%d%d%d",&c[i].x,&c[i].y,&c[i].r);
    e[++m]=E(c[i].x-c[i].r,i);
    e[++m]=E(c[i].x+c[i].r,-i);
  }
  sort(e+1,e+m+1,cmp);
  T.insert(P(-inf,0));
  T.insert(P(inf,0));
  for(i=1;i<=m;i++){
    o=e[i].t;
    if(o>0){
      T.insert(P(c[o].y,o));
      pre=nxt=it=T.find(P(c[o].y,o));
      pre--,nxt++;
      check(pre->second,o);
      check(nxt->second,o);
    }else{
      o=-o;
      pre=nxt=it=T.find(P(c[o].y,o));
      pre--,nxt++;
      check(pre->second,nxt->second);
      T.erase(it);
    }
  }
  sort(pool+1,pool+cnt+1);
  for(i=1;i<=cnt;i++)if(pool[i]>pool[i-1])ans++;
  printf("%d",ans);
}

  

Knapsack

经典树形依赖背包,使用bitset加速。

时间复杂度$O(\frac{np}{64})$。

#include<cstdio>
#include<bitset>
using namespace std;
const int N=205;
int n,m,i,o,x,g[N],nxt[N],w[N];bitset<1000005>f[N];
void dfs(int x){for(int i=g[x];i;i=nxt[i])f[i]=f[x]<<w[i],dfs(i),f[x]|=f[i];}
int main(){
  scanf("%d%d",&n,&m);
  for(i=1;i<=n;i++)scanf("%d%d",&x,&w[i]),nxt[i]=g[x],g[x]=i;
  f[0][0]=1;
  dfs(0);
  for(i=m;f[0][i]==0;i--);
  printf("%d",i);
}

  

Tax

一个直观的想法是把每条边拆成两条有向边,同时每条有向边是新图中的一个点。对于两条边$a\rightarrow b$与$b\rightarrow c$,两点之间连有向边,费用为两条边费用的最大值。然后新建源点$S$与汇点$T$,由$S$向所有起点为$1$的边连边,$T$接受所有终点为$n$的边,那么答案就是$S$到$T$的最短路。

这样子的边数为$O(m^2)$,不能承受。

考虑枚举中转点$x$,将所有与它有关的边按费用从小到大排序。对于每条边,从以$x$为终点的点向以$x$为起点的点连边,费用为该边的费用。从以$x$为起点的点向下一条边连边,费用为两条边费用的差值,向上一条边连边,费用为$0$。

这样子建图,边数为$O(m)$,可以承受。

#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
typedef pair<int,ll> PI;
const int N=100010,M=200010;
int n,m,i,j,x,y,z,cnt,t,S,T;
int g[N],en[M<<1],st[M<<1],nxt[M<<1],ed;
int G[M<<1],V[M*6],W[M*6],NXT[M*6],ED;
ll d[M<<1];
priority_queue<PI,vector<PI>,greater<PI> >Q;
struct P{int x,y,z;P(){}P(int _x,int _y,int _z){x=_x,y=_y,z=_z;}}a[M<<1],q[M];
inline bool cmp(const P&a,const P&b){return a.z<b.z;}
inline void read(int&a){char c;while(!(((c=getchar())>='0')&&(c<='9')));a=c-'0';while(((c=getchar())>='0')&&(c<='9'))(a*=10)+=c-'0';}
inline void addedge(int x,int y,int z){en[++ed]=y;st[ed]=z;nxt[ed]=g[x];g[x]=ed;}
inline void add(int x,int y,int z){V[++ED]=y;W[ED]=z;NXT[ED]=G[x];G[x]=ED;}
int main(){
  read(n),read(m);
  while(m--){
    read(x),read(y),read(z);
    a[++cnt]=P(x,y,z);
    a[++cnt]=P(y,x,z);
    addedge(x,cnt,cnt-1);
    addedge(y,cnt-1,cnt);
  }
  for(i=1;i<=n;i++){
    for(t=0,j=g[i];j;j=nxt[j])q[++t]=P(en[j],st[j],a[en[j]].z);
    if(!t)continue;
    sort(q+1,q+t+1,cmp);
    for(j=1;j<=t;j++)add(q[j].x,q[j].y,q[j].z);
    for(j=1;j<t;j++){
      add(q[j].y,q[j+1].y,q[j+1].z-q[j].z);
      add(q[j+1].y,q[j].y,0);
    }
  }
  S=cnt+1;T=S+1;
  for(i=1;i<=cnt;i++){
    if(a[i].x==1)add(S,i,a[i].z);
    if(a[i].y==n)add(i,T,a[i].z);
  }
  for(i=1;i<=T;i++)d[i]=1LL<<60;
  Q.push(PI(d[S]=0,S));
  while(!Q.empty()){
    PI t=Q.top();Q.pop();
    if(d[x=t.second]<t.first)continue;
    for(i=G[x];i;i=NXT[i])if(d[x]+W[i]<d[V[i]])Q.push(PI(d[V[i]]=d[x]+W[i],V[i]));
  }
  return printf("%lld",d[T]),0;
}

  

Parcels

如果x的个数$\geq k$就有解。

每次找到最上面的一排去切,满足切后x的个数$\geq k$,即可构造出合法方案。

#include<cstdio>
const int N=1005;
int n,m,k,i,j,pos,cnt,all,cur,at[N];char a[N][N];
int main(){
  scanf("%d%d",&n,&k);
  for(i=n;i;i--)scanf("%s",a[i]+1);
  for(i=1;i<=n;i++)for(j=1;j<=n;j++)if(a[i][j]=='x')all++;
  m=n;
  while(1){
    for(i=m;i;i--){
      cnt=0;
      for(j=1;j<=n;j++)if(a[i][j]=='x')at[++cnt]=j;
      if(cnt){
        pos=i;
        break;
      }
    }
    if(all==cnt||k==1){
      for(i=1;i<k;i++)printf("%d %d %d %d\n",at[i-1],0,at[i],m);
      printf("%d %d %d %d\n",at[k-1],0,n,m);
      break;
    }
    cur=1;
    while(all-cnt<k-cur)cur++;
    for(i=1;i<cur;i++)printf("%d %d %d %d\n",at[i-1],pos-1,at[i],m);
    printf("%d %d %d %d\n",at[cur-1],pos-1,n,m);
    m=pos-1,all-=cnt,k-=cur;
  }
}

  

Tasowanie

对于置换中的每个循环,通过KMP求出所有可能的匹配位置,它们必然形成一个等差数列,那么对于所有循环分别列出同余方程,然后判断是否有解即可。

将所有模数去重后只有至多$O(\sqrt{n})$个不同的模数,两两判断差值是否为$\gcd$的倍数即可。

时间复杂度$O(n\log n)$。

#include<cstdio>
const int N=1000010;
int n,x,cnt,i,j,p[N],vis[N],q[N],a[N],b[N],S[N],T[N],nxt[N],w[5];
bool flag;int mo[N],rest[N],cp,last[N];
int gcd(int a,int b){return b?gcd(b,a%b):a;}
inline void read(int&a){char c;while(!(((c=getchar())>='0')&&(c<='9')));a=c-'0';while(((c=getchar())>='0')&&(c<='9'))(a*=10)+=c-'0';}
inline void update(int x,int y){
  if(~last[x]){
    if(last[x]!=y)flag=0;
    return;
  }
  last[x]=y;
  mo[++cp]=x;
  rest[cp]=y;
}
inline void solve(int len){
  if(!flag)return;
  int i,j,k,cnt=0;
  for(i=1;i<=len;i++)T[i]=b[q[i]],S[i]=a[q[i]];
  for(nxt[1]=j=0,i=2;i<=len;nxt[i++]=j){
    while(j&&S[j+1]!=S[i])j=nxt[j];
    if(S[j+1]==S[i])j++;
  }
  for(i=1,j=0,k=1;i<=len*3;i++){
    while(j&&S[j+1]!=T[k])j=nxt[j];
    if(S[j+1]==T[k]){
      j++;
      if(j==len){
        w[++cnt]=i-len;
        j=nxt[j];
        if(cnt==2){
          update(w[2]-w[1],w[1]);
          return;
        }
      }
    }
    k++;
    if(k>len)k=1;
  }
  flag=0;
}
inline bool check(){
  if(!flag)return 0;
  for(int i=1;i<=cp;i++)for(int j=1;j<i;j++)if((rest[i]-rest[j])%gcd(mo[i],mo[j]))return 0;
  return 1;
}
int main(){
  read(n);
  for(i=1;i<=n;i++)read(x),p[x]=i;
  for(i=1;i<=n;i++)read(b[i]),a[i]=i;
  for(flag=1,cp=0,i=1;i<=n;i++)last[i]=-1,vis[i]=0;
  for(i=1;i<=n;i++)if(!vis[i]){
    cnt=0;
    for(j=i;!vis[j];j=p[j])vis[j]=1,q[++cnt]=j;
    solve(cnt);
  }
  puts(check()?"TAK":"NIE");
}

  

Transformacje

有解当且仅当字符a的数量以及所有字符a对应的下标和相同。

若$a[1]=b[1]$,则删掉各自的第一位;否则找到最小的$i$满足$a[i]=b[1]$,然后换过来。

需要一个数据结构寻找任意一个不与$i$冲突的子串,利用栈记录所有可能的位置即可,在栈最顶端常数个位置里必然可以找到。

时间复杂度$O(n+ans)$。

#include<cstdio>
const int N=1000010;
int n,i,j,x,y,o,cnt,f[N][2],ans;long long sum;char a[N],b[N];
struct DS{
  bool v[N];int q[N],t;
  void ext(int x){
    if(x<1||x>=n)return;
    if(v[x])return;
    v[q[++t]=x]=1;
  }
  int top(){return q[t];}
  void pop(){v[q[t--]]=0;}
}T[2];
inline void add(int x){for(int i=0;i<2;i++)for(int j=x-1;j<=x+1;j++)T[i].ext(j);}
inline bool check(int x,int y,int o){
  if(x==y||x+1==y||x-1==y)return 0;
  f[++ans][o^1]=x;
  f[ans][o]=y;
  a[x]^=1,a[x+1]^=1,a[y]^=1,a[y+1]^=1;
  add(x);
  add(y);
  return 1;
}
int main(){
  scanf("%d%s%s",&n,a+1,b+1);
  for(i=1;i<=n;i++){
    a[i]-='a';
    if(a[i])cnt++,sum+=i;
    b[i]-='a';
    if(b[i])cnt--,sum-=i;
  }
  if(cnt||sum)return puts("NIE"),0;
  puts("TAK");
  if(n>4000)return 0;
  for(i=1;i<n;i++)for(j=0;j<2;j++)T[j].ext(i);
  for(i=1;i<=n;i++){
    for(o=b[j=i];a[j]!=o;j++);
    while(j>i){
      j--;
      while(1){
        x=T[o].top();
        if(x<i||a[x]!=o||a[x+1]!=(o^1)){
          T[o].pop();
          continue;
        }
        break;
      }
      if(!check(j,x,o)){
        y=x;
        T[o].pop();
        while(1){
          x=T[o].top();
          if(x<i||a[x]!=o||a[x+1]!=(o^1)){
            T[o].pop();
            continue;
          }
          break;
        }
        check(j,x,o);
        T[o].ext(y);
      }
    }
  }
  printf("%d\n",ans);
  for(i=1;i<=ans;i++)printf("%d %d\n",f[i][0],f[i][1]);
}

  

posted @ 2022-01-05 22:02  Claris  阅读(77)  评论(0编辑  收藏  举报