• Potyczki Algorythmiczne 2010


    Trial Round:

    Rectangles

    枚举子矩阵的长和宽,乘以对应的子矩形数。

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

    #include<cstdio>
    int n,m,p,i,j,ans;
    int main(){
      scanf("%d%d%d",&n,&m,&p);
      for(i=1;i<=n;i++)for(j=1;j<=m;j++)if(2*(i+j)>=p)ans+=(n-i+1)*(m-j+1);
      printf("%d",ans);
    }
    

      

    Round 1:

    Orienteering [B]

    将环倍长,破环成链。递推预处理出每个点往前一路数值不下降/不上升能到达哪里,即可$O(1)$判断每个起点是否合法。

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

    #include<cstdio>
    const int N=200005;
    int n,i,a[N],f[N],g[N];
    int main(){
      scanf("%d",&n);
      for(i=1;i<=n;i++)scanf("%d",&a[i]),a[i+n]=a[i];
      for(i=1;i<=n+n;i++){
        f[i]=i;
        if(i>1&&a[i-1]<=a[i])f[i]=f[i-1];
        g[i]=i;
        if(i>1&&a[i-1]>=a[i])g[i]=g[i-1];
        if(f[i]<=i-n+1||g[i]<=i-n+1)return puts("TAK"),0;
      }
      puts("NIE");
    }
    

      

    Round 2:

    Mushrooms [B]

    最优方案一定是从起点出发往右走到某个点$x$,然后在$x$与$x-1$这两个位置左右横跳,枚举右端点$x$后计算对应方案的值,更新答案。

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

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    const int N=1000005,BUF=8100000;
    int n,i,t,a[N];ll s[N],ans;
    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 ll cal(ll a,ll b,ll k){
      ll ret=(a+b)*(k/2);
      if(k%2)ret+=a;
      return ret;
    }
    int main(){
      fread(Buf,1,BUF,stdin),read(n),read(t);
      for(i=1;i<=n&&i<=t+1;i++){
        read(a[i]),s[i]=s[i-1]+a[i];
        ans=max(ans,s[i]+cal(a[i-1],a[i],t-i+1));
      }
      printf("%lld",ans);
    }
    

      

    Coins [A]

    令字符'O'的值为$1$,字符'R'的值为$-k$,问题转化为求最长的值的总和为$0$的子串。

    求出前缀和后,将所有下标按前缀和分组,每组里面选取最小的下标$+1$作为左端点、最大的下标作为右端点,更新答案。

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

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int N=1000005;
    int n,k,i,j,q[N],ans;long long s[N];char a[N];
    inline bool cmp(int x,int y){return s[x]==s[y]?x<y:s[x]<s[y];}
    int main(){
      scanf("%d%d%s",&n,&k,a+1);
      for(i=1;i<=n;i++){
        if(a[i]=='O')s[i]=s[i-1]+1;else s[i]=s[i-1]-k;
        q[i]=i;
      }
      sort(q,q+n+1,cmp);
      for(i=j=0;i<=n;i=j){
        for(j=i;j<=n&&s[q[i]]==s[q[j]];j++);
        ans=max(ans,q[j-1]-q[i]);
      }
      printf("%d",ans);
    }
    

      

    Round 3:

    Fragments [A]

    对于每个给定的区间$[l,r]$,从高位到低位递归枚举数字的每一位,在递归的过程中,如果发现当前已经脱离了上下界$[l,r]$的限制且之前不全是$0$,那么后面这些位可以任填,此时令前面已经确定的部分对应的数字串为$A$,后面未确定的部分的数字串为$B$,询问串为$S$,则有如下三种情况:

    1. $S$完全出现在$B$中:对应的贡献只和$B$的长度有关,可以很方便地计算出答案。
    2. $S$完全出现在$A$中:暴力枚举$A$的每个长度为$|S|$的子串,更新对应询问串的出现次数。
    3. $A$的某个后缀是$S$的前缀:对所有询问串建立Trie,暴力枚举$A$的每个后缀,那么满足条件的$S$在Trie上对应一个子树,更新子树的答案即可。

    令$l$为数字的位数,$k$为字符集大小,则时间复杂度为$O(nl^2k+ml)$,需要很精细地实现使得时间复杂度不多$l$。

    此外,由于内存限制很紧,需要将Trie的节点数压缩至$O(m)$,且需要将询问分批来做以减小$m$的大小。

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    const int N=5005,M=250005,K=19,E=M*2;
    ll po[K+5];
    int n,m,_m,i,j,cur,cnt[K+5];
    char l[N][K],r[N][K],e[M][K+1],len[M];
    ll ans[M],full[K+5];
    int tot;
    char d[E];//每个点到根的字符数
    int who[E];//每个点到根是哪个串
    int son[E][10];short mask[E];char low[1027];
    int loc[M];
    ll f[E];
    int at[K+5][K+5];
    inline void read(char*a){
      ll t;
      scanf("%lld",&t);
      for(int i=K-1;~i;i--)a[i]=t%10,t/=10;
    }
    inline void ins(int o){
      static char s[K+5];
      scanf("%s",s+1);
      int l=strlen(s+1),i;
      len[o]=l;
      for(i=1;i<=l;i++){
        s[i]-='0';
        e[o][i]=s[i];
      }
      int x=1,y=1,z;
      for(i=1;i<=l;i++){
        char w=s[i];
        if(x==y){
          if(!son[x][w]){
            son[x][w]=++tot;
            d[tot]=l;
            who[tot]=o;
            loc[o]=tot;
            mask[x]|=1<<w;
            return;
          }
          y=son[x][w];
          z=w;
        }
        if(e[who[y]][i]==w){
          if(i==d[y]){
            x=y;
            if(i==l)loc[o]=y;
          }else if(i==l){
            tot++;
            d[tot]=l;
            who[tot]=o;
            loc[o]=tot;
            son[tot][e[who[y]][i+1]]=y;
            son[x][z]=tot;
            mask[tot]|=1<<e[who[y]][i+1];
          }
          continue;
        }
        tot++;
        d[tot]=i-1;
        who[tot]=who[y];
        son[tot][e[who[y]][i]]=y;
        son[x][z]=tot;
        mask[tot]|=1<<e[who[y]][i];
        x=y=tot;
        i--;
      }
    }
    inline int go(int x,int y,int z){
      if(!x)return 0;
      if(d[x]>=y){
        if(e[who[x]][y]==z)return x;
        return 0;
      }
      return son[x][z];
    }
    ll dfs(int o,int x,int el,int er,int fir){
      if(x==K)return 1;
      if(!el&&!er&&fir<K){
        cnt[K-x]++;
        return po[K-x];
      }
      ll ret=0;
      for(int i=0;i<10;i++){
        int nel=el,ner=er,nfir=fir;
        if(el){
          if(i<l[o][x])continue;
          if(i!=l[o][x])nel=0;
        }
        if(er){
          if(i>r[o][x])continue;
          if(i!=r[o][x])ner=0;
        }
        if(fir==K&&i)nfir=x;
        for(int j=nfir;j<=x;j++){
          if(j==x)at[x+1][j]=go(1,1,i);
          else at[x+1][j]=go(at[x][j],x-j+1,i);
        }
        ll tmp=dfs(o,x+1,nel,ner,nfir);
        for(int j=fir;j<x;j++){
          int u=at[x][j];
          if(!u)continue;
          if(d[u]==x-j)f[u]+=tmp;
        }
        ret+=tmp;
      }
      return ret;
    }
    void dfs2(int o,int x,int el,int er,int fir,int u){
      if(((!el&&!er)||(x==K))&&fir<K){
    /*
    A=[0..x)B=[x..K)
    for each suffix T (can be non-empty) of A
    */
        if(u<=1)return;
        if(fir>cur)return;
        f[u]++;
        return;
      }
      for(int i=0;i<10;i++){
        int nel=el,ner=er,nfir=fir,nu=u;
        if(el){
          if(i<l[o][x])continue;
          if(i!=l[o][x])nel=0;
        }
        if(er){
          if(i>r[o][x])continue;
          if(i!=r[o][x])ner=0;
        }
        if(fir==K&&i)nfir=x;
        if(nfir<K&&x>=cur)nu=go(u,x-cur+1,i);
        dfs2(o,x+1,nel,ner,nfir,nu);
      }
    }
    void dfs3(int x){
      for(int i=mask[x];i;i-=i&-i){
        int y=son[x][low[i]];
        f[y]+=f[x];
        dfs3(y);
      }
    }
    inline ll cal(int x){
      ll ret=0;
      for(int i=x;i<=K;i++)ret+=po[i-x]*cnt[i]*(i-x+1);
      return ret;
    }
    int main(){
      scanf("%d%d",&n,&_m);
      for(po[0]=i=1;i<=K;i++)po[i]=po[i-1]*10;
      for(i=1;i<=n;i++){
        read(l[i]);
        read(r[i]);
      }
      for(i=1;i<1024;i++)low[i]=__builtin_ctz(i);
      while(_m){
        m=min(_m,M-5);
        _m-=m;
        tot=1;
        for(i=1;i<=m;i++)ins(i);
        for(i=1;i<=n;i++)dfs(i,0,1,1,K);
        for(i=1;i<=m;i++)ans[i]=f[loc[i]];
        for(cur=0;cur<K;cur++){
          for(i=1;i<=tot;i++)f[i]=0;
          for(i=1;i<=n;i++)dfs2(i,0,1,1,K,1);
          dfs3(1);
          for(i=1;i<=m;i++){
            j=K-cur-len[i];
            if(j>=0)ans[i]+=f[loc[i]]*po[j];
          }
        }
        for(i=1;i<=K;i++)full[i]=cal(i);
        for(i=1;i<=m;i++)printf("%lld\n",ans[i]+full[len[i]]);
        for(i=0;i<=K;i++)cnt[i]=0;
        for(i=0;i<=tot;i++){
          mask[i]=f[i]=0;
          for(j=0;j<10;j++)son[i][j]=0;
        }
      }
    }
    

      

    Squared Words [B]

    $ans=\max_i\left\{LCS(S[1..i],S[i+1..n])\right\}$,共$O(n)$次LCS询问。使用ALCS在$O(n^2)$时间内完成预处理,$O(n)$时间内回答每个询问。

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

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int N=1005;
    int n,i,j,f[N][N],g[N][N],ans;char s[N];
    inline int ask(int o,int l,int r){
      int len=r-l+1,i,j,k,ret=0;
      for(i=1;i<=len;i++){
        j=r-i+1;
        if(j>=f[o+1][j+1]){
          ret++;
          k=f[o+1][j+1]-1;
          if(k>=l)ret--;
        }
      }
      return ret;
    }
    int main(){
      scanf("%d%s",&n,s);
      for(i=1;i<=n;i++)f[0][i]=i;
      for(i=1;i<=n;i++)for(j=1;j<=n;j++){
        if(s[i-1]==s[j-1]){
          f[i][j]=g[i][j-1];
          g[i][j]=f[i-1][j];
        }else{
          f[i][j]=max(f[i-1][j],g[i][j-1]);
          g[i][j]=min(g[i][j-1],f[i-1][j]);
        }
      }
      for(i=0;i<n-1;i++)ans=max(ans,ask(i,i+1,n-1));
      printf("%d",n-ans*2);
    }
    

      

    Round 4:

    Evacuation [A]

    $1$到$n$的边数不超过$3$的路径有如下三种:

    1. $1\rightarrow n$:这种边必须删除。
    2. $1\rightarrow i\rightarrow n$:边$1\rightarrow i$和边$i\rightarrow n$至少要删掉一条。
    3. $1\rightarrow i\rightarrow j\rightarrow n$:边$1\rightarrow i$、边$i\rightarrow j$和边$j\rightarrow n$至少要删掉一条,不难发现删掉中间的边$i\rightarrow j$不优,因此可以视作边$1\rightarrow i$和边$j\rightarrow n$至少要删掉一条。

    建立左右各$n$个点的二分图,将第二类和第三类中涉及的边作为连源点/汇点的边加入,并将对应的二选一限制作为无穷边加入,则问题转化为求最小割,即二分图最大匹配,使用bitset优化的匈牙利算法即可。

    时间复杂度$O(\frac{n^3}{w})$。

    #include<cstdio>
    typedef unsigned int U;
    const int N=1005,M=(N>>5)+5;
    int n,m,l,r,all,i,j,x,y,ans,idl[N],idr[N],who[N];
    bool g[N][N];U f[N][M],v[M];
    inline void flip(U v[],int x){v[x>>5]^=1U<<(x&31);}
    bool find(int x){
      for(int i=0;i<=all;i++)while(1){
        U t=v[i]&f[x][i];
        if(!t)break;
        int y=i<<5|__builtin_ctz(t);
        flip(v,y);
        if(!who[y]||find(who[y]))return who[y]=x,1;
      }
      return 0;
    }
    int main(){
      scanf("%d%d",&n,&m);
      while(m--){
        scanf("%d%d",&x,&y);
        if(x==1&&y==n){ans++;continue;}
        g[x][y]=1;
      }
      for(i=1;i<=n;i++){
        if(g[1][i]&&g[i][n])idl[i]=idr[i]=1;
        for(j=1;j<=n;j++)if(g[1][i]&&g[i][j]&&g[j][n])idl[i]=idr[j]=1;
      }
      for(i=1;i<=n;i++){
        if(idl[i])idl[i]=++l;
        if(idr[i])idr[i]=++r;
      }
      for(i=1;i<=n;i++){
        if(g[1][i]&&g[i][n])flip(f[idl[i]],idr[i]);
        for(j=1;j<=n;j++)if(g[1][i]&&g[i][j]&&g[j][n])flip(f[idl[i]],idr[j]);
      }
      all=r>>5;
      for(i=1;i<=l;i++){
        for(j=0;j<=all;j++)v[j]=~0U;
        if(find(i))ans++;
      }
      printf("%d",ans);
    }
    

      

    Map [B]

    从左往右考虑每个点,维护考虑过的点中$y$坐标的最小值和最大值,即可$O(1)$判断每个点左上角和左下角是否有点,右上角和右下角的判断同理。

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

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int N=1000005;
    int n,d,i,j,l,r,ans,f[N];
    struct P{int x,y,p;}a[N];
    inline bool cmp(const P&a,const P&b){return a.x<b.x;}
    void gao(){
      l=d,r=0;
      for(i=1;i<=n;i=j){
        for(j=i;j<=n&&a[i].x==a[j].x;j++){
          if(l<a[j].y)f[a[j].p]++;
          if(r>a[j].y)f[a[j].p]++;
        }
        for(j=i;j<=n&&a[i].x==a[j].x;j++){
          l=min(l,a[j].y);
          r=max(r,a[j].y);
        }
      }
    }
    int main(){
      scanf("%d%d",&n,&d);
      for(i=1;i<=n;i++){
        scanf("%d%d",&a[i].x,&a[i].y);
        a[i].p=i;
      }
      sort(a+1,a+n+1,cmp);
      gao();
      reverse(a+1,a+n+1);
      gao();
      for(i=1;i<=n;i++)if(f[i]==4)ans++;
      printf("%d",ans);
    }
    

      

    Round 5:

    The Goat [A]

    首先在$O(n^2\log n)$时间内求出$area[i]$表示被恰好$i$个圆覆盖部分的面积,则每个位置在$k$轮中至少被覆盖一次的概率为$1-(1-\frac{i}{n})^k$,将概率乘以$area[i]$后贡献给答案即可。

    #include<cstdio>
    #include<cmath>
    #include<algorithm>
    using namespace std;
    const int N=1010;
    const double eps=1e-9;
    const double PI=acos(-1.0);
    int sgn(double x){
      if(x>eps)return 1;
      if(x<-eps)return -1;
      return 0;
    }
    struct P{
      double x,y;
      P(){}
      P(double _x,double _y){x=_x,y=_y;}
      P operator+(const P&b)const{return P(x+b.x,y+b.y);}
      P operator-(const P&b)const{return P(x-b.x,y-b.y);}
      P operator*(double b)const{return P(x*b,y*b);}
      P operator/(double b)const{return P(x/b,y/b);}
      double det(const P&b)const{return x*b.y-y*b.x;}
      P rot90()const{return P(-y,x);}
      P unit(){return *this/abs();}
      double abs(){return hypot(x,y);}
    };
    struct Circle{
      P o;double r;
      bool contain(const Circle&v,const int&c)const{
        return sgn(r-(o-v.o).abs()-v.r)>c;
      }
      bool disjuct(const Circle&v,const int&c)const{
        return sgn((o-v.o).abs()-r-v.r)>c;
      }
    };
    bool isCC(Circle a,Circle b,P&p1,P&p2){
      if(a.contain(b,0)||b.contain(a,0)||a.disjuct(b,0))return 0;
      double s1=(a.o-b.o).abs();
      double s2=(a.r*a.r-b.r*b.r)/s1;
      double aa=(s1+s2)/2,bb=(s1-s2)/2;
      P mm=(b.o-a.o)*(aa/(aa+bb))+a.o;
      double h=sqrt(max(0.0,a.r*a.r-aa*aa));
      P vv=(b.o-a.o).unit().rot90()*h;
      p1=mm+vv,p2=mm-vv;
      return 1;
    }
    struct EV{
      P p;double ang;int add;
      EV(){}
      EV(const P&_p,double _ang,int _add){p=_p,ang=_ang,add=_add;}
      bool operator<(const EV&a)const{return ang<a.ang;}
    }eve[N*2];
    int E,cnt,C,K,L,i,j;Circle c[N];
    bool g[N][N],overlap[N][N];
    double Area[N],ans;
    int cX[N],cY[N],cR[N];
    bool contain(int i,int j){
      return (sgn(c[i].r-c[j].r)>0||sgn(c[i].r-c[j].r)==0&&i<j)&&c[i].contain(c[j],-1);
    }
    int main(){
      scanf("%d%d%d",&C,&K,&L);
      for(i=0;i<C;i++){
        scanf("%d%d",&cX[i],&cY[i]);
        cR[i]=L;
        c[i].o=P(cX[i],cY[i]);
        c[i].r=cR[i];
      }
      for(i=0;i<=C;i++)Area[i]=0;
      for(i=0;i<C;i++)for(j=0;j<C;j++)overlap[i][j]=contain(i,j);
      for(i=0;i<C;i++)for(j=0;j<C;j++)g[i][j]=!(overlap[i][j]||overlap[j][i]||c[i].disjuct(c[j],-1));
      for(i=0;i<C;i++){
        E=0;cnt=1;
        for(j=0;j<C;j++)if(j!=i&&overlap[j][i])cnt++;
        for(j=0;j<C;j++)if(i!=j&&g[i][j]){
          P aa,bb;
          isCC(c[i],c[j],aa,bb);
          double A=atan2(aa.y-c[i].o.y,aa.x-c[i].o.x);
          double B=atan2(bb.y-c[i].o.y,bb.x-c[i].o.x);
          eve[E++]=EV(bb,B,1);
          eve[E++]=EV(aa,A,-1);
          if(B>A)cnt++;
        }
        if(E==0)Area[cnt]+=PI*c[i].r*c[i].r;
        else{
          sort(eve,eve+E);
          eve[E]=eve[0];
          for(j=0;j<E;j++){
            cnt+=eve[j].add;
            Area[cnt]+=eve[j].p.det(eve[j+1].p)*0.5;
            double theta=eve[j+1].ang-eve[j].ang;
            if(theta<0)theta+=PI*2;
            Area[cnt]+=theta*c[i].r*c[i].r*0.5-sin(theta)*c[i].r*c[i].r*0.5;
          }
        }
      }
      for(i=1;i<=C;i++)ans+=(Area[i]-Area[i+1])*(1-pow(1-1.0*i/C,K));
      return printf("%.15f",ans),0;
    }
    

      

    Map 2 [B]

    将横坐标离散化,对于离散化后的每一段连续的横坐标,预处理出它左边/右边所有点的纵坐标的最小值和最大值,则这一段的纵坐标能取的范围是对应区间的交,将交集大小乘以这一段横坐标对应的横坐标数,贡献给答案即可。

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

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int N=1000005;
    int n,d,i,j,l,r,sufl[N],sufr[N];long long ans;
    struct P{int x,y;}a[N];
    inline bool cmp(const P&a,const P&b){return a.x<b.x;}
    inline int cal(int L,int R){return max(min(R,r)-max(L,l)-1,0);}
    int main(){
      scanf("%d%d",&n,&d);
      for(i=1;i<=n;i++)scanf("%d%d",&a[i].x,&a[i].y);
      sort(a+1,a+n+1,cmp);
      l=d,r=0;
      for(i=n+1;i;i--){
        if(i<=n)l=min(l,a[i].y),r=max(r,a[i].y);
        sufl[i]=l,sufr[i]=r;
      }
      l=d,r=0;
      for(i=1;i<=n;i=j){
        ans+=1LL*(a[i].x-a[i-1].x-1)*cal(sufl[i],sufr[i]);
        for(j=i;j<=n&&a[i].x==a[j].x;j++);
        ans+=cal(sufl[j],sufr[j]);
        for(j=i;j<=n&&a[i].x==a[j].x;j++)l=min(l,a[j].y),r=max(r,a[j].y);
      }
      printf("%lld",ans);
    }
    

      

    Termites [A]

    两个人拿走的石子堆数是定值$cnt$,石子数总和是定值$sum$,考虑求出先手总和$-$后手总和的值$dif$,那么就能推出两人各自的总和。

    对于相邻的三个元素$A,B,C$,如果$A\leq B$且$B\geq C$,那么$A$和$C$一定属于同一个人,$B$一定属于另一个人,可以将这三个数合并为一个数$A-B+C$。不断迭代直至无法继续合并数字。

    迭代完毕后,对于最左侧或者最右侧贴墙的部分,令贴墙的数为$A$,$A$旁边的数为$B$,那么如果$A\geq B$,则这两个数一定是最后由两人轮流拿走,可以将它们消去,把墙推进两格,并给$dif$加上$(-1)^{cnt}\times(B-A)$。不断迭代直至无法继续消除数字。

    如上处理完毕后,可以保证先手每次一定能取到全局最大值,因此将所有数混在一起排序,从大到小贪心取即可。

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

    #include<cstdio>
    #include<algorithm>
    typedef long long ll;
    const int N=1000010;
    int n,m,all,i,j,a[N],cnt;ll st[N],q[N],sum,dif;
    inline void ext(int x){
      st[++cnt]=x;
      while(cnt>2&&st[cnt-2]<=st[cnt-1]&&st[cnt-1]>=st[cnt]){
        st[cnt-2]+=st[cnt]-st[cnt-1];
        cnt-=2;
      }
    }
    inline void solve(int l,int r){
      int i;
      cnt=0;
      if(r==n)for(i=r;i>=l;i--)ext(a[i]);
      else for(i=l;i<=r;i++)ext(a[i]);
      if(l==1||r==n){
        for(i=1;i<cnt;i+=2)if(st[i]>=st[i+1]){
          if(all&1)dif-=st[i+1]-st[i];
          else dif+=st[i+1]-st[i];
        }else break;
        for(;i<=cnt;i++)q[++m]=st[i];
      }else for(i=1;i<=cnt;i++)q[++m]=st[i];
    }
    int main(){
      scanf("%d",&n);
      for(i=1;i<=n;i++)scanf("%d",&a[i]),sum+=a[i],all+=!!a[i];
      for(i=1;i<=n;)if(a[i]){
        for(j=i;j<=n&&a[j];j++);
        solve(i,j-1);
        i=j;
      }else i++;
      std::sort(q+1,q+m+1);
      for(i=m,j=0;i;i--,j^=1)if(!j)dif+=q[i];else dif-=q[i];
      printf("%lld %lld",(sum+dif)/2,(sum-dif)/2);
    }
    

      

    Round 6:

    Byton Tree [B]

    按照可采摘时间区间的右端点$r$从小到大依次考虑每个叶子。

    假设当前考虑到了叶子$x$,暴力找到$x$最高的祖先$y$,满足$y$子树内$l$的最大值不超过$r[x]$,并标记沿途经过的点。

    若一个点已经被标记过则说明$x$已经被摘走,否则此时需要在$y$处进行一次采摘。

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

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int N=1000005;
    int n,m,i,l[N],r[N],f[N],q[N],ans;bool vis[N];
    inline bool cmp(int x,int y){return r[x]<r[y];}
    void init(int y){
      int k,x=++n;
      f[x]=y;
      scanf("%d",&k);
      if(!k){
        scanf("%d%d",&l[x],&r[x]);
        q[++m]=x;
      }
      while(k--)init(x);
    }
    inline void gao(int x){
      int o=r[x];
      vis[x]=1;
      while(f[x]&&l[f[x]]<=o){
        x=f[x];
        if(vis[x])return;
        vis[x]=1;
      }
      ans++;
    }
    int main(){
      init(0);
      for(i=n;i;i--)l[f[i]]=max(l[f[i]],l[i]);
      sort(q+1,q+m+1,cmp);
      for(i=1;i<=m;i++)gao(q[i]);
      printf("%d",ans);
    }
    

      

    Firm [B]

    按深度离线后,使用树状数组支持单点修改以及子树点数查询。

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

    #include<cstdio>
    #include<vector>
    using namespace std;
    typedef vector<int>V;
    #define rep(x) for(V::iterator it=x.begin();it!=x.end();it++)
    const int N=100005,M=N*2;
    int m,i,x,y,e[N][2],ans[N],d[N],st[N],en[N],dfn,f[N];
    V g[N],G[M];char op[N][5];
    void dfs(int x){
      st[x]=++dfn;
      rep(g[x])dfs(*it);
      en[x]=dfn;
    }
    inline void add(int x,int p){for(;x<=dfn;x+=x&-x)f[x]+=p;}
    inline int ask(int x){int t=0;for(;x;x-=x&-x)t+=f[x];return t;}
    int main(){
      scanf("%d",&m);
      for(i=1;i<=m;i++){
        scanf("%s%d%d",op[i],&x,&y);
        e[i][0]=x,e[i][1]=y;
        if(op[i][0]=='Z'){
          d[x]=d[y]+1;
          g[y].push_back(x);
          G[d[x]].push_back(x);
        }else{
          G[d[x]+y+1].push_back(-i);
        }
      }
      dfs(1);
      for(i=0;i<M;i++){
        rep(G[i]){
          x=*it;
          if(x>0)add(st[x],1);
          else{
            y=e[-x][0];
            ans[-x]=ask(en[y])-ask(st[y]-1);
          }
        }
        rep(G[i])if(*it>0)add(st[*it],-1);
      }
      for(i=1;i<=m;i++)if(op[i][0]=='P')printf("%d\n",ans[i]);
    }
    

      

    Planning the Roadworks [A]

    Kosaraju求出SCC后,缩点得到一个DAG。

    对于DAG上的某条边$x\rightarrow y$,如果去掉这条边后$x$仍然能到达$y$,那么可以删掉这条边,即要判断能否通过拓扑序介于$x$和$y$之间的点从$x$间接到达$y$。按拓扑序DP出$can[x][y]$表示$x$能否到$y$,对于每个点按照另一个端点的拓扑序枚举边进行转移,假设现在枚举到了边$x\rightarrow y$,则拓扑序介于它们之间的点已经更新过$can[x][y]$,利用$can[x][y]$即可判断出去掉边$x\rightarrow y$后$x$是否仍然能到达$y$。使用bitset优化,时间复杂度$O(\frac{nm}{w})$。

    对于SCC内部的边,由于Kosaraju只用到了不超过$2n$条边,留下这些边后对于每条边暴力检查去掉它后还能否从$x$到达$y$即可,时间复杂度$O(n^2)$。

    总时间复杂度为$O(\frac{nm}{w}+n^2)$。

    #include<cstdio>
    #include<bitset>
    #include<vector>
    #include<algorithm>
    using namespace std;
    typedef pair<int,int>P;
    typedef vector<P>V;
    #define rep(it,v) for(V::iterator it=v.begin();it!=v.end();it++)
    const int N=5005,M=100005;
    int n,m,cnt,i,j,x,y,e[M][2],q[N],t,f[N],pool[N],tot,ans;
    V g[N],h[N];
    bool vis[N],used[M],del[M];
    bitset<N>can[N];
    void dfs1(int x){
      vis[x]=1;
      rep(it,g[x])if(!vis[it->first])used[it->second]=1,dfs1(it->first);
      q[++t]=x;
    }
    void dfs2(int x){
      vis[x]=0,f[x]=cnt;
      pool[++tot]=x;
      rep(it,h[x])if(vis[it->first])used[it->second]=1,dfs2(it->first);
    }
    void dfs3(int x){
      vis[x]=1;
      rep(it,g[x])if(!vis[it->first]&&!del[it->second])dfs3(it->first);
    }
    int main(){
      scanf("%d%d",&n,&m);
      for(i=1;i<=m;i++){
        scanf("%d%d",&x,&y);
        e[i][0]=x,e[i][1]=y;
        g[x].push_back(P(y,i));
        h[y].push_back(P(x,i));
      }
      for(i=1;i<=n;i++)if(!vis[i])dfs1(i);
      for(i=n;i;i--)if(vis[q[i]])cnt++,dfs2(q[i]);
      for(i=1;i<=n;i++)g[i].clear();
      for(i=1;i<=n;i++)rep(it,h[pool[i]]){
        x=f[pool[i]],y=f[it->first];
        if(x==y)continue;
        g[y].push_back(P(x,it->second));
      }
      for(i=1;i<=cnt;i++)can[i][i]=1;
      for(i=cnt;i;i--)rep(it,g[i]){
        if(can[i][it->first])del[it->second]=1;
        else can[i]|=can[it->first];
      }
      for(i=1;i<=n;i++)g[i].clear();
      for(i=1;i<=m;i++){
        x=e[i][0],y=e[i][1];
        if(f[x]!=f[y])continue;
        if(!used[i]){del[i]=1;continue;}
        g[x].push_back(P(y,i));
      }
      for(i=1;i<=m;i++){
        x=e[i][0],y=e[i][1];
        if(f[x]!=f[y])continue;
        if(!used[i])continue;
        del[i]=1;
        for(j=1;j<=n;j++)vis[j]=0;
        dfs3(x);
        if(!vis[y])del[i]=0;
      }
      for(i=1;i<=m;i++)if(del[i])ans++;
      printf("%d\n",ans);
      for(i=1;i<=m;i++)if(del[i])printf("%d\n",i);
    }
    

      

    Riddle [A]

    注意到“每个集合恰好选择一个点”可以放宽成“每个集合最多选择一个点”,对于最后求出的方案里,如果某个集合没选点,任选一个就好了。

    考虑2-SAT建图,有两类边:

    1. 对于每条给定的边$(u,v)$:如果不选$u$就必须选$v$,如果不选$v$就必须选$u$。
    2. 对于每个集合:如果选了一个点就不能选其它所有点。

    第二类边不能直接建图,但是在Kosaraju算法中DFS图的时候,每个点$x$和$x$所在集合内除了$x$之外的所有点都连了一条第二类边,需要用一个数据结构跳过那些已经搜过的且不是$x$的点。用一个支持双端pop的队列维护就可以了,如果这个集合不是只剩$x$没搜过,那么两端至少可以消费一个点。

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

    #include<cstdio>
    const int N=2000010,M=1000010,BUF=25000000;
    char Buf[BUF],*buf=Buf;
    int n,m,K,o,i,j,x,y,S[M],T[M],st[M],en[M],pool[M],tot,at[M];
    int e[M][2],g[N],v[N],nxt[N],ed;
    int q[N],t,f[N];
    bool vis[N];
    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){v[++ed]=y;nxt[ed]=g[x];g[x]=ed;}
    void dfs1(int x){
      if(vis[x])return;
      vis[x]=1;
      for(int i=g[x];i;i=nxt[i])dfs1(v[i]);
      if(x>n)while(S[at[x-n]]<=T[at[x-n]]){
        if(pool[S[at[x-n]]]!=x-n)dfs1(pool[S[at[x-n]]++]);
        else if(pool[T[at[x-n]]]!=x-n)dfs1(pool[T[at[x-n]]--]);
        else break;
      }
      q[++t]=x;
    }
    void dfs2(int x){
      if(!vis[x])return;
      vis[x]=0,f[x]=o;
      for(int i=g[x];i;i=nxt[i])dfs2(v[i]);
      if(x<=n)while(S[at[x]]<=T[at[x]]){
        if(pool[S[at[x]]]!=x)dfs2(pool[S[at[x]]++]+n);
        else if(pool[T[at[x]]]!=x)dfs2(pool[T[at[x]]--]+n);
        else break;
      }
    }
    int main(){
      fread(Buf,1,BUF,stdin);read(n),read(m),read(K);
      for(i=1;i<=m;i++){
        read(x),read(y);
        e[i][0]=x,e[i][1]=y;
        add(x,y+n),add(y,x+n);
      }
      for(i=1;i<=K;i++){
        read(y);
        st[i]=tot+1;
        while(y--){
          read(x);
          at[x]=i;
          pool[++tot]=x;
        }
        en[i]=tot;
      }
      for(i=1;i<=K;i++)S[i]=st[i],T[i]=en[i];
      for(i=1;i<=n+n;i++)if(!vis[i])dfs1(i);
      for(ed=0,i=1;i<=n+n;i++)g[i]=0;
      for(i=1;i<=m;i++){
        x=e[i][0],y=e[i][1];
        add(y+n,x),add(x+n,y);
      }
      for(i=1;i<=K;i++)S[i]=st[i],T[i]=en[i];
      for(i=t;i;i--)if(vis[q[i]])o++,dfs2(q[i]);
      for(i=1;i<=n;i++)if(f[i]==f[i+n])return puts("NIE"),0;
      puts("TAK");
      for(i=1;i<=K;i++)st[i]=pool[st[i]];
      for(i=1;i<=n;i++)if(f[i]<f[i+n])st[at[i]]=i;
      for(i=1;i<=K;i++)printf("%d ",st[i]);
      return 0;
    }
    

      

    Trial Finals:

    Variable Subsequences

    设$f_i$表示以$i$为结尾的合法子序列数,枚举上一个点转移,由于只有一类转移不合法,可以使用总和减去那一类的贡献来进行$O(1)$转移。

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

    #include<cstdio>
    const int N=500005,P=1000000007;
    int n,s,t,x,f[N];
    int main(){
      s=1;
      scanf("%d",&n);
      while(n--){
        scanf("%d",&x);
        t=(s-f[x]+P)%P;
        f[x]=(f[x]+t)%P;
        s=(s+t)%P;
      }
      printf("%d",(s+P-1)%P);
    }
    

      

    Rectangles 2

    同Rectangles。

    Finals:

    Sweets

    假设最后分成的三组糖果分别为$A$个、$B$个、$C$个,其中$A\leq B\leq C$,则要最小化$C-A$。

    将$n$箱糖果分成前一半和后一半,对于每一半暴力枚举出所有$O(3^{\frac{n}{2}})$个可能的分组方案,对于一个方案记录$a=B-A,b=C-B$,那么需要在前一半方案中找到一个方案$i$,在后一半方案中找到一个方案$j$,满足$A\leq B\leq C$,即$B-A\geq 0$且$C-B\geq 0$,也就是:

    • $a_i+a_j\geq 0$
    • $b_i+b_j\geq 0$

    并最小化$C-A$,即$(C-B)+(B-A)=a_i+a_j+b_i+b_j=(a_i+b_i)+(a_j+b_j)$。

    将所有方案按$a$排序,枚举前一半的方案$i$,双指针出满足$a_i+a_j\geq 0$的$j$的范围,按$b$建立树状数组,查询区间$a_j+b_j$的最大值,更新答案。

    时间复杂度$O(n3^{\frac{n}{2}})$。

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    const int N=27,M=535005;
    const ll inf=1LL<<60;
    int n,m,ca,cb,i,j,w[N];ll ans,tmp,c[M],f[M];
    struct P{ll x,y;}a[M],b[M];
    inline bool cmp(const P&a,const P&b){return a.x<b.x;}
    void dfsl(int o,ll x,ll y){
      if(o>m){
        a[++ca].x=-x;
        a[ca].y=-y;
        return;
      }
      dfsl(o+1,x-w[o],y);
      dfsl(o+1,x+w[o],y-w[o]);
      dfsl(o+1,x,y+w[o]);
    }
    void dfsr(int o,ll x,ll y){
      if(o>n){
        b[++cb].x=x;
        b[cb].y=y;
        return;
      }
      dfsr(o+1,x-w[o],y);
      dfsr(o+1,x+w[o],y-w[o]);
      dfsr(o+1,x,y+w[o]);
    }
    inline void up(ll&a,ll b){a>b?(a=b):0;}
    inline void add(int x,ll p){for(;x<=ca;x+=x&-x)up(f[x],p);}
    inline ll ask(int x){ll t=inf;for(;x;x-=x&-x)up(t,f[x]);return t;}
    int main(){
      scanf("%d",&n);
      for(i=1;i<=n;i++)scanf("%d",&w[i]);
      m=n/2;
      dfsl(1,0,0);
      dfsr(m+1,0,0);
      ans=inf;
      for(i=1;i<=ca;i++)c[i]=a[i].y,f[i]=inf;
      sort(c+1,c+ca+1);
      sort(a+1,a+ca+1,cmp);
      sort(b+1,b+cb+1,cmp);
      for(i=j=1;i<=cb;i++){
        while(j<=ca&&a[j].x<=b[i].x){
          add(lower_bound(c+1,c+ca+1,a[j].y)-c,-a[j].x-a[j].y);
          j++;
        }
        up(ans,ask(upper_bound(c+1,c+ca+1,b[i].y)-c-1)+b[i].x+b[i].y);
      }
      printf("%lld",ans);
    }
    

      

    Acyclic Decomposition

    DFS判环,若不存在环则答案为$1$。

    否则可以构造答案为$2$的一组解:第一组包含所有满足$u<v$的边,第二组包含所有满足$u>v$的边。

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

    #include<cstdio>
    const int N=100005;
    int n,m,i,x,y,ca,cb,a[N],b[N],g[N],v[N],nxt[N],vis[N],in[N];
    bool dfs(int x){
      if(vis[x])return 0;
      vis[x]=in[x]=1;
      for(int i=g[x];i;i=nxt[i]){
        if(in[v[i]])return 1;
        if(dfs(v[i]))return 1;
      }
      return in[x]=0;
    }
    int main(){
      scanf("%d%d",&n,&m);
      for(i=1;i<=m;i++){
        scanf("%d%d",&x,&y);
        if(x<y)a[++ca]=i;else b[++cb]=i;
        v[i]=y,nxt[i]=g[x],g[x]=i;
      }
      for(i=1;i<=n;i++)if(dfs(i)){
        puts("2");
        for(printf("%d",ca),i=1;i<=ca;i++)printf(" %d",a[i]);puts("");
        for(printf("%d",cb),i=1;i<=cb;i++)printf(" %d",b[i]);puts("");
        return 0;
      }
      puts("1");
      printf("%d",m);
      for(i=1;i<=m;i++)printf(" %d",i);
    }
    

      

    Divisors

    Pollard-Rho分解质因数,计算结果唯一当且仅当每个质因数的计算结果唯一。

    对于每个质因数,将所有变量分别设为最小和最大值,计算出式子的结果,判断是否相同即可。

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    typedef long long ll;
    const int C=2730,S=5,N=600005;
    int Case,m,cnt,i,j,k,tot,l[N],r[N],f[N],g[N];
    ll n,q[100],v[N];
    char s[2111111];
    ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
    inline ll mul(ll a,ll b,ll n){
      a%=n,b%=n;
      if(n<=1100000000)return a*b%n;
      return(a*b-(ll)(a/(long double)n*b+1e-8)*n+n)%n;
    }
    inline ll pow(ll a,ll b,ll n){
      ll d=1;
      a%=n;
      while(b){
        if(b&1)d=mul(d,a,n);
        a=mul(a,a,n);
        b>>=1;
      }
      return d;
    }
    inline bool check(ll a,ll n){
      ll m=n-1,x,y;int i,j=0;
      while(!(m&1))m>>=1,j++;
      x=pow(a,m,n);
      for(i=1;i<=j;x=y,i++){
        y=pow(x,2,n);
        if((y==1)&&(x!=1)&&(x!=n-1))return 1;
      }
      return y!=1;
    }
    inline bool miller_rabin(int times,ll n){
      ll a;
      if(n==1)return 0;
      if(n==2)return 1;
      if(!(n&1))return 0;
      while(times--)if(check(rand()%(n-1)+1,n))return 0;
      return 1;
    }
    inline ll pollard_rho(ll n,int c){
      ll i=1,k=2,x=rand()%n,y=x,d;
      while(1){
        i++,x=(mul(x,x,n)+c)%n,d=gcd(y-x,n);
        if(d>1&&d<n)return d;
        if(y==x)return n;
        if(i==k)y=x,k<<=1;
      }
    }
    void findfac(ll n,int c){
      if(n==1)return;
      if(miller_rabin(S,n)){
        q[++cnt]=n;
        return;
      }
      ll m=n;
      while(m==n)m=pollard_rho(n,c--);
      findfac(m,c),findfac(n/m,c);
    }
    int build(){
      int x=++tot;
      l[x]=r[x]=v[x]=0;
      scanf("%s",s);
      if(s[0]>='a'&&s[0]<='z')return x;
      if(s[0]>='0'&&s[0]<='9'){
        int len=strlen(s);
        for(int i=0;i<len;i++)(v[x]*=10)+=s[i]-'0';
        return x;
      }
      if(s[2]=='D')v[x]=1;else v[x]=2;
      l[x]=build();
      r[x]=build();
      return x;
    }
    inline bool solve(){
      scanf("%lld",&n);
      tot=0;
      build();
      cnt=0;
      findfac(n,C);
      sort(q+1,q+cnt+1);
      for(i=1;i<=cnt;i=j){
        for(j=i;j<=cnt&&q[i]==q[j];j++);
        ll A=q[i];int B=j-i;
        for(k=tot;k;k--){
          if(l[k]){
            if(v[k]==1){//gcd
              f[k]=min(f[l[k]],f[r[k]]);
              g[k]=min(g[l[k]],g[r[k]]);
            }else{
              f[k]=max(f[l[k]],f[r[k]]);
              g[k]=max(g[l[k]],g[r[k]]);
            }
          }else if(v[k]){
            ll C=v[k];int D=0;
            while(C%A==0)C/=A,D++;
            f[k]=g[k]=D;
          }else{
            f[k]=0;
            g[k]=B;
          }
        }
        if(f[1]!=g[1])return 0;
      }
      return 1;
    }
    int main(){
      scanf("%d",&Case);
      while(Case--)puts(solve()?"TAK":"NIE");
    }
    

      

    Byteball Match

    枚举每个球队$S$,判断$S$能否成为冠军。

    由于每个球队都还没结束比赛,因此只要在剩下比赛里都比对方领先$+\infty$个球即可达到最高预期分数,那么只需要判断剩下的比赛能否合理分配$2$分给双方使得每个队伍的分数都不超过$S$的最高预期分数。

    将剩下每个球队看作点,点权表示$S$的最高预期分数减去该球队已有的分数。

    将剩下还未进行的比赛看作边,那么类似于Hall定理,这有解当且仅当任何一个点集都满足点权和$\geq $诱导子图边数$\times2$,即$\min(点权和-诱导子图边数\times 2)\geq 0$,需要找到一个点集$V$最小化点权和$-$诱导子图边数$\times 2$。

    注意到诱导子图边数$\times 2=V$中所有点的度数之和$-$横跨$V$内外的边数,因此问题等价于最小化"每个点的(点权$-$度数)之和$+$横跨$V$内外的边数",对该问题建立最小割模型:

    • 源点到每个点的边的边权为$n+$点权$-$度数,割掉这条边表示将这个点选入$V$。
    • 每个点到汇点的边边权为$n$,割掉这条边表示不将这个点选入$V$。
    • 每条边正边反边的代价都是$1$,表示如果一方选了且另一方没选,则要割掉这条边。

    利用HLPP算法$O(n^3)$求最大流判断最小割是否$\geq (n-1)\times n$即可,总时间复杂度$O(n^4)$。

    #include<cstdio>
    #include<cstring>
    #include<queue>
    #include<stack>
    #include<algorithm>
    using namespace std;
    namespace HLPP{
    const int N = 105, M = 120000, INF = 0x3f3f3f3f;
    int n, m, s, t;
    struct E{
      int nex, t, v;
    };
    E e[M * 2 + 1];
    int h[N + 1], cnt;
    inline void add_path(int f, int t, int v) { e[++cnt] = (E){h[f], t, v}, h[f] = cnt; }
    inline void add_flow(int f, int t, int v) {
      add_path(f, t, v);
      add_path(t, f, 0);
    }
    int ht[N + 1], ex[N + 1],
        gap[N];  // 高度; 超额流; gap 优化 gap[i] 为高度为 i 的节点的数量
    stack<int> B[N];       // 桶 B[i] 中记录所有 ht[v]==i 的v
    int level;         // 溢出节点的最高高度
    inline void init(int _n,int _s,int _t){
      n=_n,s=_s,t=_t;
      cnt=1;
      level=0;
      memset(h, 0, sizeof(h));
      memset(ht, 0, sizeof(ht));
      memset(ex, 0, sizeof(ex));
      memset(gap, 0, sizeof(gap));
      for(int i=0;i<N;i++)while(!B[i].empty())B[i].pop();
    }
    inline int push(int u) {      // 尽可能通过能够推送的边推送超额流
      bool init = u == s;  // 是否在初始化
      for (int i = h[u]; i; i = e[i].nex) {
        const int &v = e[i].t, &w = e[i].v;
        if (!w || init == false && ht[u] != ht[v] + 1)  // 初始化时不考虑高度差为1
          continue;
        int k = init ? w : min(w, ex[u]);
        // 取到剩余容量和超额流的最小值,初始化时可以使源的溢出量为负数。
        if (v != s && v != t && !ex[v]) B[ht[v]].push(v), level = max(level, ht[v]);
        ex[u] -= k, ex[v] += k, e[i].v -= k, e[i ^ 1].v += k;  // push
        if (!ex[u]) return 0;  // 如果已经推送完就返回
      }
      return 1;
    }
    inline void relabel(int u) {  // 重贴标签(高度)
      ht[u] = INF;
      for (int i = h[u]; i; i = e[i].nex)
        if (e[i].v) ht[u] = min(ht[u], ht[e[i].t]);
      if (++ht[u] < n) {  // 只处理高度小于 n 的节点
        B[ht[u]].push(u);
        level = max(level, ht[u]);
        ++gap[ht[u]];  // 新的高度,更新 gap
      }
    }
    inline bool bfs_init() {
      memset(ht, 0x3f, sizeof(ht));
      static int q[N + 5];
      int head = 0, tail = 0;
      ht[q[0] = t] = 0;
      while (head <= tail) {  // 反向 BFS, 遇到没有访问过的结点就入队
        int u = q[head++];
        for (int i = h[u]; i; i = e[i].nex) {
          const int &v = e[i].t;
          if (e[i ^ 1].v && ht[v] > ht[u] + 1) ht[v] = ht[u] + 1, q[++tail] = v;
        }
      }
      return ht[s] != INF;  // 如果图不连通,返回 0
    }
    // 选出当前高度最大的节点之一, 如果已经没有溢出节点返回 0
    inline int select() {
      while (B[level].size() == 0 && level > -1) level--;
      return level == -1 ? 0 : B[level].top();
    }
    inline int hlpp() {                  // 返回最大流
      if (!bfs_init()) return 0;  // 图不连通
      memset(gap, 0, sizeof(gap));
      for (int i = 1; i <= n; i++)
        if (ht[i] != INF) gap[ht[i]]++;  // 初始化 gap
      ht[s] = n;
      push(s);  // 初始化预流
      int u;
      while ((u = select())) {
        B[level].pop();
        if (push(u)) {  // 仍然溢出
          if (!--gap[ht[u]])
            for (int i = 1; i <= n; i++)
              if (i != s && i != t && ht[i] > ht[u] && ht[i] < n + 1)
                ht[i] = n + 1;  // 这里重贴成 n+1 的节点都不是溢出节点
          relabel(u);
        }
      }
      return ex[t];
    }
    }
    const int N=105;
    int n,m,i,x,y,A,B,f[N],v[N][N],mx,id[N],deg[N];
    inline bool check(int S){
      int i,j,now=f[S],cnt=2;
      for(i=1;i<=n;i++)if(!v[S][i])now+=2;
      if(now<mx)return 0;
      for(i=1;i<=n;i++)if(i!=S)id[i]=++cnt,deg[i]=0;
      HLPP::init(n+1,1,2);
      for(i=1;i<=n;i++)for(j=1;j<=n;j++)if(!v[i][j]&&i!=S&&j!=S){
        HLPP::add_flow(id[i],id[j],1);
        deg[i]++;
      }
      for(i=1;i<=n;i++)if(i!=S){
        HLPP::add_flow(1,id[i],N+now-f[i]-deg[i]);
        HLPP::add_flow(id[i],2,N);
      }
      return HLPP::hlpp()>=(n-1)*N;
    }
    int main(){
      scanf("%d%d",&n,&m);
      for(i=1;i<=n;i++)v[i][i]=1;
      while(m--){
        scanf("%d%d%d%d",&x,&y,&A,&B);
        v[x][y]=v[y][x]=1;
        if(A==B)f[x]++,f[y]++;
        if(A>B)f[x]+=2;
        if(A<B)f[y]+=2;
      }
      mx=n-1;
      for(i=1;i<=n;i++)mx=max(mx,f[i]);
      for(i=1;i<=n;i++)if(check(i))printf("%d ",i);
    }
    

      

    Army Training

    选择最左边的点$O$作为原点,将所有点按$O$极角排序,求出每个点的排名$rk[i]$。

    对于任意一个三角形$OAB$ ($rk[A]<rk[B]$),预处理出严格在它内部的点数,即$rk$在$(rk[A],rk[B])$之间且在直线$AB$一侧的点数。枚举$A$后将所有点按$A$极角排序,转一圈的同时用树状数组维护$rk$。

    对于每个询问,首先由预处理的三角形信息乘以叉积的正负得到一个初步的答案。这里存在一些顶点$P$满足射线$OP$与多边形交点为奇数,没有被正确地减掉。

    枚举多边形上相邻的三个点$X,Y,Z$,根据$X,Y,Z$的相对关系分类讨论判断$Y$是否是这样多算的点即可。

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

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    const int N=1005;
    int n,m,q,i,j,x,y,z,k,ans,rk[N],f[N],w[N],g[N][N],id[N];bool vis[N];
    struct P{
      int x,y,p;
      P(){}
      P(int _x,int _y){x=_x,y=_y;}
      P operator-(const P&b)const{return P(x-b.x,y-b.y);}
      int sgn()const{return x?x>0:y>0;}
    }a[N],b[N],c[N<<1],pivot;
    inline ll cross(const P&a,const P&b){return 1LL*a.x*b.y-1LL*a.y*b.x;}
    inline bool cmp(const P&a,const P&b){return cross(a-pivot,b-pivot)>0;}
    inline bool cmp2(const P&a,const P&b){
      if(a.sgn()!=b.sgn())return a.sgn()<b.sgn();
      return cross(a,b)>0;
    }
    inline void ins(int x){for(;x<n;x+=x&-x)f[x]++;}
    inline int ask(int x){int t=0;for(;x;x-=x&-x)t+=f[x];return t;}
    inline bool check(int x,int y,int z){
      if(!rk[y])return 0;
      if(!rk[x])return rk[z]>rk[y];
      if(!rk[z])return rk[x]<rk[y];
      if(rk[x]<rk[y]&&rk[y]<rk[z])return 1;
      if((rk[x]<rk[y]&&rk[z]<rk[y])||(rk[x]>rk[y]&&rk[z]>rk[y]))return cross(a[y]-a[x],a[z]-a[y])>0;
      return 0;
    }
    int main(){
      scanf("%d%d",&n,&q);
      for(i=1;i<=n;i++)scanf("%d%d",&a[i].x,&a[i].y),a[i].p=i;
      for(pivot=a[1],i=2;i<=n;i++)if(a[i].x<pivot.x)pivot=a[i];
      for(i=1;i<=n;i++)if(a[i].p!=pivot.p)b[++m]=a[i];
      sort(b+1,b+n,cmp);
      for(i=1;i<n;i++)rk[b[i].p]=i;
      for(i=1;i<n-1;i++){
        for(m=0,j=i+1;j<n;j++){
          c[++m]=b[j];
          c[m].x-=b[i].x;
          c[m].y-=b[i].y;
        }
        for(j=1;j<=m;j++){
          c[j+m]=c[j];
          c[j+m].x*=-1;
          c[j+m].y*=-1;
          c[j+m].p*=-1;
        }
        sort(c+1,c+m+m+1,cmp2);
        for(j=1;j<=n;j++)f[j]=w[j]=vis[j]=0;
        for(j=1;j<=m+m;j++){
          k=c[j].p;
          if(k>0){
            ins(rk[k]);
            w[k]-=ask(rk[k]-1);
            vis[k]=1;
          }else{
            k*=-1;
            w[k]+=ask(rk[k]-1);
            if(!vis[k])w[k]+=rk[k]-i-1;
          }
        }
        for(j=i+1;j<n;j++){
          k=w[b[j].p];
          if(cross(a[b[i].p]-pivot,a[b[j].p]-pivot)>0)k*=-1;
          g[b[i].p][b[j].p]=k;
          g[b[j].p][b[i].p]=-k;
        }
      }
      while(q--){
        scanf("%d",&m);
        for(i=0;i<m;i++)scanf("%d",&id[i]);
        for(ans=i=0;i<m;i++){
          x=id[i],y=id[(i+1)%m],z=id[(i+2)%m];
          ans+=g[x][y];
          if(check(x,y,z))ans--;
        }
        printf("%d\n",ans);
      }
    }
    

      

    Blindfold Nim

    全$0$时先手胜率为$0$,否则最优策略一定是从上限最大的那一堆拿走一个石子。

    假设上限最大的那一堆的石子数在$[0,k]$之间随机,则先手在这一轮的操作有$\frac{k}{k+1}$的概率合法,然后交换先后手,因此令之后子问题的先手胜率为$nxt$,则答案为$\frac{k}{k+1}(1-nxt)$,可以倒推求出原始局面的先手胜率。

    时间复杂度$O(n+\sum a)$。

    #include<cstdio>
    const int N=1000000;
    int n,i,c[N+5],f[N+5];long double ans;
    int main(){
      scanf("%d",&n);
      while(n--)scanf("%d",&i),c[i]++;
      for(i=N,n=0;i;i--)while(c[i])c[i]--,c[i-1]++,f[++n]=i;
      for(i=n;i;i--)ans=(1-ans)*f[i]/(f[i]+1);
      printf("%.15f",(double)ans);
    }
    

      

    Termites 2

    按时间顺序从前往后考虑每条边,看作$n$个独立点然后依次加入每条边将它们逐渐连通,那么每个连通块的游戏是独立的,且对于每个连通块,只有一个点可以留下来。

    因此对于$x$点所在的连通块,设$a[x]$表示玩家1可以保证最后留下来的点集,设$b[x]$表示玩家2可以保证最后留下来的点集。

    按时间顺序从前往后考虑到边$(u,v)$时,不妨设现在轮到了玩家1,令合并$u,v$后的连通块对应的信息为$a',b'$:

    • 如果$a[u]$不能保证留下$u$且$a[v]$不能保证留下$v$,那么游戏结束。
    • 如果$a[u]$能保证留下$u$且$a[v]$能保证留下$v$,那么如果吃掉$u$,则可以保全$a[v]$;如果吃掉$v$,则可以保全$a[u]$,而对手保全不了任何点,因此$a'=a[u]\ or\ a[v]$,$b'=NULL$。
    • 如果$a[u]$能保证留下$u$但是$a[v]$不能保证留下$v$,那么只能吃掉$u$和$v$,保全$a[v]$和$b[v]-v$,因此$a'=a[v]$,$b'=b[v]-v$。

    用数组记录每个点是否能被两个玩家保全,并查集维护连通块,建二叉树表示集合的or关系。在清除一个集合时,暴力DFS对应子树修正数组的值即可。

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

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int N=500005;
    int n,m,i,x,y,X,Y,f[N<<1],a[N],b[N],ga[N<<1][2],gb[N<<1][2];
    int F(int x){return f[x]==x?x:f[x]=F(f[x]);}
    void dfsa(int x){
      if(!x)return;
      dfsa(ga[x][0]);
      dfsa(ga[x][1]);
      if(x<=n)a[x]=0;
    }
    void dfsb(int x){
      if(!x)return;
      dfsb(gb[x][0]);
      dfsb(gb[x][1]);
      if(x<=n)b[x]=0;
    }
    int main(){
      scanf("%d",&n);
      m=n;
      for(i=1;i<=n;i++)a[i]=b[i]=f[i]=i;
      for(i=1;i<n;i++){
        scanf("%d%d",&x,&y);
        ++m;
        f[m]=m;
        if(i&1){
          if(!a[x]&&!a[y])return printf("%d",i),0;
          if(a[x]&&a[y]){
            X=F(x),Y=F(y);
            //a(M)=a(X) or a(Y), b(M)=NULL
            ga[m][0]=X;
            ga[m][1]=Y;
            dfsb(X);
            dfsb(Y);
          }else{
            if(!a[x])swap(x,y);
            X=F(x),Y=F(y);
            //a(M)=a(Y), b(M)=b(Y)-y
            b[y]=0;
            ga[m][0]=gb[m][0]=Y;
            dfsa(X);
            dfsb(X);
          }
        }else{
          if(!b[x]&&!b[y])return printf("%d",i),0;
          if(b[x]&&b[y]){
            X=F(x),Y=F(y);
            //b(M)=b(X) or b(Y), a(M)=NULL
            gb[m][0]=X;
            gb[m][1]=Y;
            dfsa(X);
            dfsa(Y);
          }else{
            if(!b[x])swap(x,y);
            X=F(x),Y=F(y);
            //b(M)=b(Y), a(M)=a(Y)-y
            a[y]=0;
            ga[m][0]=gb[m][0]=Y;
            dfsa(X);
            dfsb(X);
          }
        }
        f[X]=f[Y]=m;
      }
      puts("-1");
    }
    

      

  • 相关阅读:
    java Vamei快速教程02 方法和数据成员
    java Vamei快速教程01
    二叉树
    高效通信模型之
    高效通信模型之
    线程间通信与同步
    线程
    进程
    C++面试知识点总结
    windows下UDP服务器和客户端的实现
  • 原文地址:https://www.cnblogs.com/clrs97/p/15824652.html
Copyright © 2020-2023  润新知