• Since04-01(189)


    2017-04-01周六(165)

    ▲22:28:43 BZOJ1497 最小割模型 注意边数*2!!!


    2017-04-10周一(166)

    学考考完我又是一条好汉!!十月再战!!

    ▲21:30:56 WC2007 剪刀石头布 费用流建模/正难则反  题目求最多有多少个"石头剪刀布"的情况.

    对于(a,b,c),满足的情况是三个点的出度都是1.  即a->b,b->c,c->a

    我们考虑不满足情况的a,b,c的连边方式:  比如 a->b,b->c,a->c或者  a->b,b->a,c->a,这类方案有很多,但是有一个共同点,(a,b,c)的度数都是0,1,2,但是顺序可以改变.这是最关键的地方.

    那么我们就可以把这个组合抽象出来,假如点x的出度(赢的场次)为w,那么含x的特定不符合的组合有w*(w-1)/2种,那么总可行组合数=所有三个点的组合-∑w*(w-1)/2

    这样就把问题转化成 求cost=Min{∑w*(w-1)/2}

    已知∑w=n*(n-1)/2 ,可以通过这个条件想到最大流最小费用,建模每个st-en的流表示一场比赛,而费用就表示这场比赛对于cost的影响.

    对于一个点x,当w[x]+1时,它对结果的影响会加上w[x].因此x到en的边不能用单一的费用,要连n-1条边,费用分别是0~n-2,w[x]对cost的影响为0+1+2+3+..+(w[x]-1) =w[x]*(w[x]-1)

    此题还有一个bug!!就是要输出方案!!!

    不能用贪心或者随意构造方法来得到方案,这样可能不合法.正确的做法是根据残余网络,确定方案选择了哪些路线,从而得到解.

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    const int M=50205;
    const int oo=1e9+6;
    int n,ec=0,tot=0,st,en,head[M],nxt[M],cap[M],cost[M],to[M],dis[M];
    int Q[M],pre[M],cnt[M],los[M],res[105][105],ans;
    int A[M],num[M];
    bool in[M];
    inline void rd(int &res){
        res=0;char c;
        while(c=getchar(),c<48);
        do res=(res<<1)+(res<<3)+(c^48);
        while(c=getchar(),c>=48);
    }
    void sc(int a){
        if(a)putchar('1');
        else putchar('0');
    //    putchar(' ');
    }
    void ins(int a,int b,int f,int co){
        to[ec]=b;cap[ec]=f;cost[ec]=co;nxt[ec]=head[a];head[a]=ec++;
        to[ec]=a;cap[ec]=0;cost[ec]=-co;nxt[ec]=head[b];head[b]=ec++;
    }
    bool SPFA(){
        int x,y,i,l=0,r=0,f=oo;
        for(i=1;i<=tot;i++)in[i]=0,dis[i]=oo;
        dis[st]=0;
        Q[r++]=st;
        while(l<r){
            x=Q[l++],in[x]=0;
            for(i=head[x];~i;i=nxt[i]){
                int y=to[i];
                if(cap[i]&&dis[y]>dis[x]+cost[i]){
                    dis[y]=dis[x]+cost[i];
                    if(!in[y])Q[r++]=y,in[y]=true;
                    pre[y]=i;
                }
            }
        }
        if(dis[en]>=oo)return false;
    //    cnt[to[pre[en]^1]]++;//表示每个人赢的次数 
        for(x=en;x!=st;x=to[pre[x]^1])f=min(f,cap[pre[x]]);
        for(x=en;x!=st;x=to[y^1]){
            y=pre[x];
            cap[y]-=f,cap[y^1]+=f;
        }
    //    printf("%d %d
    ",f,dis[en]);
        ans-=dis[en]*f;
        return true;
    }
    void solve(){
        int i,j,k,a,b;
        rd(n);tot=n+2,st=n+1,en=n+2;
        //点数 n+2+n*(n-1)/2    102+50*100 =5102 
        //边数  [n*(n-1)/2 + n*(n-1)/2*2 + n*(n-1) ] *2
        // n^2  1+2+2  ->5    5w 
        //st=n+1,en=n+2   contest numbered from n+3
        //n*2+tot;
        ans=n*(n-1)*(n-2)/6;//所有的方案数 
        for(i=n*n+tot;~i;i--)head[i]=-1;
        for(i=1;i<=n;i++){
            for(j=1;j<=n;j++){
    //            rd(a);
                rd(res[i][j]);
                if(i<=j)continue;//i>j
                a=res[i][j];
                ++tot;
                num[tot]=i+j;
                ins(st,tot,1,0);//容量为1,费用为0
                if(a==2){//还没比赛 
                    ins(tot,i,1,0);
                    ins(tot,j,1,0);
                }
                else if(a==1)ins(tot,i,1,0);//i赢了 
                else ins(tot,j,1,0);//j赢了 
                //*/
            }//[0,n-2]一共n-1条边 
            for(j=n-2;~j;j--)ins(i,en,1,j);
        }
        while(SPFA());
        for(i=n+3;i<=tot;i++){
            for(j=head[i];~j;j=nxt[j]){
                if(to[j]==st||cap[j])continue;
                int x=to[j],y=num[i]-to[j];
                res[x][y]=1,res[y][x]=0;
            }
        }
        printf("%d
    ",ans);
        for(i=1;i<=n;i++){
            for(j=1;j<=n;j++){
                sc(res[i][j]);
                putchar(" 
    "[j==n]);
            }
        }
    }
    int main(){
    //    freopen("da.in","r",stdin);
        solve();
        return 0;
    }
    View Code

    2017-04-11周二(169)

    ▲16:33:48 BZOJ3996 线性代数 最大流最小割 两点之间建模  先把式子化简得到一个关于Ai的式子,然后考虑Ai的两种取值,以及Ai和Aj的关系进行建模,然后得到最小割即可.注意边数!!!

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<iostream>
    using namespace std;//edge = n+n+n^2   4*n+n^2 =252000
    const int N=555,M=(N*N+2500)*2;
    const int oo=2e9;
    int ec=0,n,C[N],st,en,B[N][N],tot=0,head[N],to[M],nxt[M],cap[M],dis[N],Q[M];
    bool BFS(){
        int L=0,R=0,i,j,k;
        for(i=1;i<=en;i++){
            dis[i]=-1;
        }
        dis[st]=0;
        Q[R++]=st;
        while(L<R){
            int x=Q[L++],y;
            for(i=head[x];~i;i=nxt[i]){
                y=to[i];
                if(cap[i]&&dis[y]==-1){
                    dis[y]=dis[x]+1;
                    Q[R++]=y;
                }
            }
        }
        return dis[en]>=0;
    }
    int dfs(int x,int f){
        if(x==en)return f;
        int k,res=0,y,i;
        for(i=head[x];~i;i=nxt[i]){
            y=to[i];
            if(!cap[i]||dis[y]!=dis[x]+1)continue;
            k=dfs(y,min(cap[i],f-res));
            if(k){
                res+=k;
                cap[i]-=k,cap[i^1]+=k;
                if(res==f)return res;
            }
        }
        return res;
    }
    void ins(int a,int b,int c){
        to[ec]=b;nxt[ec]=head[a];cap[ec]=c;head[a]=ec++;
        to[ec]=a;nxt[ec]=head[b];cap[ec]=0;head[b]=ec++;
    }
    inline void rd(int &res){
        res=0;char c;
        while(c=getchar(),c<48);
        do res=(res<<1)+(res<<3)+(c^48);
        while(c=getchar(),c>=48);
    }
    void solve(){
        int i,k,j,c,ans=0;
        rd(n);
        st=n+1,en=n+2;
        for(i=1;i<=en;i++)head[i]=-1;
        for(i=1;i<=n;i++){
            for(j=1;j<=n;j++)rd(B[i][j]);
        }
        for(i=1;i<=n;i++){
            for(j=1;j<i;j++){
                k=B[i][j]+B[j][i];
                C[i]+=k,C[j]+=k;
                ins(i,j,k);
                ins(j,i,k);
                ans+=(k<<1);
            }
            ans+=B[i][i]*2;
        }//最后答案/4 
        for(i=1;i<=n;i++){
            rd(c);c<<=1;
            ins(i,en,c);
            ins(st,i,C[i]+B[i][i]*2);
        }
    //  printf("%d
    ",ans);
        while(BFS())ans-=dfs(st,oo);
    //  printf("%d
    ",ans);
        printf("%d
    ",ans>>1);
    }
    int main(){
    //  freopen("da.in","r",stdin);
        solve();
        return 0;
    }
    View Code

     ▲18:11:49 BZOJ3931 Dijkstra+最大流模板题

    ▲21:54:36 BZOJ1797 最大流+Tarjan  最小割的唯一性判定.判断一条边是否一定/可能在最小割中:先跑一遍最大流,对残余网络强连通缩点,对于边(a,b),假如不是满流,肯定不能在最下个上,若id[a]!=id[b],那么它可能在最小割上,如果id[a]=id[st],id[b]=id[en],一定在最小割中.

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<iostream>
    using namespace std;//先求出maxflow 然后再强连通缩点
    const int N=4005,M=120005,oo=1e9; 
    int head[N],ec=0,n,m,st,en,cap[M],to[M],nxt[M],Q[N],dis[N];
    int stk[N],in[N],dfn[N],low[N],clo=0,scc=0,id[N],top=0;
    inline void rd(int &res){
        res=0;char c;
        while(c=getchar(),c<48);
        do res=(res<<1)+(res<<3)+(c^48);
        while(c=getchar(),c>=48);
    }
    void ins(int a,int b,int c){
        to[ec]=b;nxt[ec]=head[a];cap[ec]=c;head[a]=ec++;
        to[ec]=a;nxt[ec]=head[b];cap[ec]=0;head[b]=ec++;
    }
    bool BFS(){
        int L=0,R=0,i,j,k;
        for(i=1;i<=n;i++)dis[i]=-1;
        dis[st]=0;
        Q[R++]=st;
        while(L<R){
            int x=Q[L++],y;
            for(i=head[x];~i;i=nxt[i]){
                y=to[i];
                if(cap[i]&&dis[y]==-1){
                    dis[y]=dis[x]+1;
                    Q[R++]=y;
                }
            }
        }
        return dis[en]>=0;
    }
    int dfs(int x,int f){
        if(x==en)return f;
        int k,res=0,y,i;
        for(i=head[x];~i;i=nxt[i]){
            y=to[i];
            if(!cap[i]||dis[y]!=dis[x]+1)continue;
            k=dfs(y,min(cap[i],f-res));
            if(k){
                res+=k;
                cap[i]-=k,cap[i^1]+=k;
                if(res==f)return res;
            }
        }
        return res;
    }
    void tarjan(int x){
        dfn[x]=low[x]=++clo;
        stk[++top]=x;
        in[x]=1;
        int y;
        for(int i=head[x];~i;i=nxt[i]){
            if(!cap[i])continue;
            y=to[i];
            if(!dfn[y])tarjan(y),low[x]=min(low[x],low[y]);
            else if(in[y])low[x]=min(low[x],dfn[y]);
        }
        if(low[x]==dfn[x]){
            id[x]=++scc;
            in[x]=0;
            while(stk[top]!=x){
                y=stk[top];
                id[y]=scc,in[y]=0;
                top--;
            }
            top--;
        }
    }
    void solve(){
        int i,j,k,a,b,c;
        rd(n);rd(m);rd(st);rd(en);
        for(i=1;i<=n;i++)head[i]=-1;
        for(i=1;i<=m;i++){
            rd(a),rd(b),rd(c);
            ins(a,b,c);
        }
        while(BFS())a=dfs(st,oo);
        for(i=1;i<=n;i++){
            if(!dfn[i])tarjan(i);
        }
        for(i=0;i<ec;i+=2){
            a=to[i^1],b=to[i];
            if(cap[i]||id[a]==id[b]){
                putchar('0'),putchar(' '),putchar('0');
            }
            else {
                putchar('1');putchar(' ');
                if(id[a]==id[st]&&id[b]==id[en])putchar('1');
                else putchar('0');
            } 
            putchar('
    ');
        }
    }
    int main(){
    //      freopen("da.in","r",stdin);
        solve();
        return 0;
    }
    View Code 

    2017-04-12周三(173)

    ▲08:14:50 BZOJ2127 happiness 最小割 两点之间建模 dinic的优化

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const  int N=105,M=120505,oo=1e9;
    int n,m,id[N][N],tot=2,st=1,en=2;
    int A[N][N],B[N][N],upA[N][N],upB[N][N],leA[N][N],leB[N][N];
    int head[N*N],hd[N*N],to[M],nxt[M],cap[M],Q[M],dis[N*N],ec=2;
    bool vis[N*N];
    inline void rd(int &res){
        res=0;char c;
        while(c=getchar(),c<48);
        do res=(res<<1)+(res<<3)+(c^48);
        while(c=getchar(),c>=48);
    }
    void ins(int a,int b,int c){
        to[ec]=b;nxt[ec]=head[a];cap[ec]=c;head[a]=ec++;
        to[ec]=a;nxt[ec]=head[b];cap[ec]=0;head[b]=ec++;
    }
    bool BFS(){
        int L=0,R=0,i,j,k;
        for(i=1;i<=tot;i++)dis[i]=-1;
        dis[st]=0;
        Q[R++]=st;
        while(L<R){
            int x=Q[L++],y;
            for(i=head[x];i;i=nxt[i]){
                y=to[i];
                if(cap[i]&&dis[y]==-1){
                    dis[y]=dis[x]+1;
                    Q[R++]=y;
                }
            }
        }
        return dis[en]>=0;
    }
    int dfs(int x,int f){
    //    vis[x]=1;
        if(x==en)return f;
        int k,y,res=0;
        for(int i=head[x];i;i=nxt[i]){
            y=to[i];
            if(!cap[i]||dis[y]!=dis[x]+1)continue;
            k=dfs(y,min(cap[i],f-res));
            if(k){
                cap[i]-=k,cap[i^1]+=k;
                res+=k;
                if(res==f)return res;
            }
        }
        if(!res)dis[x]=-1;
        return res;
    }
    void RD(int s[N][N],int a,int b){
        for(int i=1;i<=a;i++)    
            for(int j=1;j<=b;j++)rd(s[i][j]);
    }
    void solve(){
        int i,j,k,a,b,ans=0;
        rd(n);rd(m);
        RD(A,n,m),RD(B,n,m);
        RD(upA,n-1,m),RD(upB,n-1,m);
        RD(leA,n,m-1),RD(leB,n,m-1);
        
        for(i=1;i<=n;i++){
            for(j=1;j<=m;j++){
                id[i][j]=++tot;
                ans+=A[i][j]+B[i][j];
                a=A[i][j]<<1,b=B[i][j]<<1;
                a+=upA[i-1][j]+upA[i][j],b+=upB[i-1][j]+upB[i][j];
                a+=leA[i][j-1]+leA[i][j],b+=leB[i][j-1]+leB[i][j];
                ins(st,tot,a);
                ins(tot,en,b);
            }
        }
        for(i=1;i<n;i++){//考虑上下好朋友   
            for(j=1;j<=m;j++){
                ans+=upA[i][j]+upB[i][j];
                ins(id[i][j],id[i+1][j],upA[i][j]+upB[i][j]);
                ins(id[i+1][j],id[i][j],upA[i][j]+upB[i][j]);
            }
        }
        for(i=1;i<=n;i++){//考虑上下好朋友   
            for(j=1;j<m;j++){
                ans+=leA[i][j]+leB[i][j];
                ins(id[i][j],id[i][j+1],leA[i][j]+leB[i][j]);
                ins(id[i][j+1],id[i][j],leA[i][j]+leB[i][j]);
            }
        }
        ans<<=1;
        while(BFS())ans-=dfs(st,oo);
        ans>>=1;
        printf("%d
    ",ans);    
    }
    int main(){
    //    freopen("da.in","r",stdin);
        solve();
        return 0;
    }
    View Code

     ▲10:10:47 BZOJ3894 文理分科 最小割

    ▲14:49:58 BZOJ1927 星际竞速 费用流 要求每个点恰好经过一次->这个条件可以看成 到达每个点一次,从每个点出发一次,这个条件可以通过拆点连边流量为1来解决.现在问最短时间,我们可以把最终路线的每一段都拆开考虑->要么是跳跃,要么是一个点走单向边到另一个点.注意第一种方式的起点根本不重要,所以从st->x'连边,费用为定位时间,走这条边说明用了跳跃;从u->v'连边,表示走图中的边,费用为边权值,再让st和u连边费用为0.最后让x'与en连边,只有到达x'才说明到达过这个点.跑最小费用最大流即可.

    19:35:06 BZOJ3876 支线剧情 有下界的费用流

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<iostream>
    using namespace std;
    const int oo=1e9,N=305,M=25005;
    int ans=0,n,m,st,en,pre[N],to[M],head[N],nxt[M],ec=2,cap[M],cost[M],dis[M],Q[M],in[M],deg[N];
    inline void rd(int &res){
        res=0;char c;
        while(c=getchar(),c<48);
        do res=(res<<1)+(res<<3)+(c^48);
        while(c=getchar(),c>=48);
    }
    void ins(int a,int b,int c,int d){
        to[ec]=b,nxt[ec]=head[a],cap[ec]=c,cost[ec]=d;head[a]=ec++;
    //    if(cap[ec-1]<0)printf("hey %d
    ",cap[ec-1]);
        to[ec]=a,nxt[ec]=head[b],cap[ec]=0,cost[ec]=-d,head[b]=ec++;
        
    }
    bool SPFA(){
        int f=oo,i,x,y,L=0,R=0;
        for(i=1;i<=en;i++)dis[i]=oo;
        dis[st]=0,Q[R++]=st;
        while(L<R){
            x=Q[L++];in[x]=0;
            for(i=head[x];i;i=nxt[i]){
                y=to[i];
    //            printf("%d %d
    ",x,y);
                if(cap[i]&&dis[y]>dis[x]+cost[i]){
                    dis[y]=dis[x]+cost[i];
                    pre[y]=i;
                    if(!in[y])in[y]=1,Q[R++]=y;
                }
            }
        }
        if(dis[en]>=oo)return false;
    //    for(x=en;x!=st;x=to[pre[x]^1])f=min(f,cap[pre[x]]);
        for(x=en;x!=st;x=to[pre[x]^1]){
            cap[pre[x]]--,cap[pre[x]^1]++;
    //        printf("%d ",x);
        }
        ans+=dis[en];
    //    printf("
    val %d 
    ",dis[en]);
    //    puts("-----------");
        return true;
    }
    void solve(){
        int i,j,k,a,b,c;
        rd(n);
        st=n+1,en=n+2;
        for(i=1;i<=n;i++){
            rd(a);
            if(a)ins(i,en,a,0);//a是出度 
            ins(i,1,oo,0);
            while(a--){
                rd(b),rd(c);
                ins(i,b,oo,c);
                ins(st,b,1,c);
            }
        }
        while(SPFA());
        printf("%d
    ",ans);
    }
    int main(){
    //    freopen("da.in","r",stdin);
        solve();
        return 0;
    }
    View Code

    2017-04-13周四(176)

    ▲10:30:37 BZOJ2756 奇怪的游戏 终态分析->分类讨论/二分/最大流

    ①确定了最后统一的数字,就可以确定操作的次数.

    ②对于n*m为偶数的情况,此时解满足二分的性质,即假设x为合法的统一数字,则任何y>=x都是可行的,所以可以通过二分枚举得到最小满足的x.判定一个解是否可行->进行网络流建模

    由于网格图是二分图,所以模型很简单,白色点连向st,流量为需要修改的大小,黑色的连向en,同理.黑白点相邻的连接即可.

    ③对于n,m为奇数的情况:可以通过一个式子转化,x*cnt黑-sum黑色点=x*cnt白-sum白色点  ->由于nm为奇数,cnt黑!=cnt白,因此x可解,所以直接判断x即可.

    启示->当某一个情况不好解决时,考虑是否能够利用信息直接解出来,把问题变为判定型.

    ▲16:25:41 SPOJ839 最大流 这道题好神!!

    ①题目是异或值,每一位之间的值是互不影响的,所以每一位分开考虑,分别求最小值.那现在只有0和1的区别了.

    ②最小割建模,建模-> 一条边(u,v),流量为1;假如点x权值已经确定,当前位是1,就和en连接 ,流量为oo,否则和st连接,流量oo.然后求最小割即可.

    ③对于第二问 有一个巧妙的建模方式-> 为了让点权和最小,就要让点尽可能在st集合,将点和st连一条流量为1的边,并且把原图的边的流量改为10000,这样可以保证在满足边权和最小的情况下,点权和最小.得到的最小割val,val/10000就是边权和,val%10000就是点数.

    但是如果要求出每个点具体的权值,可以采用另一种贪心的方式:从en开始,遍历到的(没有经过割边的)每个点设为1.也就是找到离汇点最近的割,这样能让更少的点进入en集.

    ▲20:50:00 长郡中学省选模拟T2 DP+分类讨论+前缀/后缀最值

    从最基本的DP入手,发现表达式是这样的  f[i]=Max{f[j]+(t[i]-t[j]-1)/step} 

    对于(a-b)/c 变量分离  a=a1*c+a2,b=b1*c+b2   

    (a-b)/c=  (a1-b1)*c+a2-b2    

    根据a2和b2的大小关系得到两个值,所以每个点根据t[i]mod step分类,离散出大小关系 用树状数组维护前缀和后缀的最值即可.

    注意有一个小bug!!这里的起点,也就是t[0]是st-1不是0!!!!!


    2017-04-14 (177)

    ▲15:02:29 长郡中学省选模拟T1 最小割 把每个点的权值从w改为1000-w,这样就把问题转化为求权值和最小.考虑建模,有两个约束条件:

    ①一个炮台只能攻击一个目标

    ②两个炮台路线不相交.

    而且题目中又给出了一个条件:保证一个炮台不会被另一个攻击,那么若两个炮台路线相交,只有可能是一个横着的和一个竖着的相交.

    假如有一个向左的炮台(i,j),让它与st相连,再和(i,j-1),(i,j-1)再和(i,j-2)....形成一条链,(i,1)再和en相连.在没有其他炮台的影响下,这条路径的最小割就是答案.对于两个炮台可能相交的情况:在两个炮台对应的链的两个交点之间连边,这样保证不能同时选中某个部分的边.


    2017-04-15 (178)

    ▲11:15:16 长郡中学省选模拟T3 莫比乌斯反演/积性函数求和/推推推

    啊啊啊好神奇啊我居然做到了~~~(≧▽≦)/

    本来只是想写个60分,结果想出了优化hhh

    首先可以求出所有数对(i,j)的lcm之和设为X,这是一个经典问题.

    X=∑d*F(A/d,B/d) 枚举数对的gcd为d,问题转化成求 i<=A/d,j<=B/d的互素数对的乘积和设为Y.这个也是一个经典问题.

    Y=∑i^2*μ(i)*S(A/(d*i))*S(B/(d*i))

    这个问题可以在O(n)时间内解决,后来一想,假如i,j的gcd=d含平方因子,那就不算入答案里即可.只要修改一下d的前缀和就可以在O(n)时间内求解原问题的答案了.

    原题有优化多case的方法.

    不妨枚举p=d*i.

    问题转化成ans=∑S(A/p)*S(B/p)*f(p)*p

    f[p]=∑μ(q)*q*μ(p/q)^2  

    假如能够求出f[p]就能够在O(sqrtn)内解决一个case.

    假如p的某个素因子次数>=3,f[p]=0,这是显然的.

    假如把p写成a^2*b的形式,再枚举b的组成,是可以卡过去的.

    更优的解法:由于f[p]使一个积性函数,可以直接线性筛出f[p].

    假如p的所有素因子次数都是1,那么f[p]=f[i]*f[pri[j]].否则分类考虑:①假如i已经包含两个pri[j]了或者f[i]=0,那么f[i*pri[j]]显然为0 .

    ②否则i中有一个pri[j],现在又来一个,相当于给a多了一个素因子,给b少了一个,f[i*pri[j]]=-f[i/pri[j]]*pri[j]

    这样可以O(n)求出f,从而解决多case问题.

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<iostream>
    using namespace std;
    const int M=4e6+5,P=(1<<30),ful=P-1;
    int A,B,mu[M],sum[M],pri[300000],tot=0;//[0,7] 可用 
    bool mark[M];
    inline void rd(int &res){
        res=0;char c;
        while(c=getchar(),c<48);
        do res=(res<<1)+(res<<3)+(c^48);
        while(c=getchar(),c>=48);
    }
    void print(int x){
        if(!x)return ;
        print(x/10);
        putchar((x%10)^48);
    }
    void sc(int x){
        if(x<0){x=-x;putchar('-');}
        print(x);
        if(!x)putchar('0');
        putchar('
    ');
    }
    void Add(int &x,int y){
        x+=y;
        if(x<0)x+=P;
        else x&=ful;
    }
    void init(){//求出 mu,r,phi->sum 
        int i,j,k;
        mu[1]=sum[1]=1;
        for(i=2;i<M;i++){
            if(!mark[i]){
                pri[++tot]=i;
                mu[i]=-1;
                sum[i]=1-i+P;
            }
            for(j=1;j<=tot&&pri[j]*i<M;j++){
                mark[i*pri[j]]=1;
                if(i%pri[j]==0){
                    mu[i*pri[j]]=0;
                    if(sum[i]==0||i/pri[j]%pri[j]==0)sum[i*pri[j]]=0;
                    else sum[i*pri[j]]=1ll*(-sum[i/pri[j]]+P)*pri[j]&ful;
                    break;
                }
                mu[i*pri[j]]=-mu[i];
                sum[i*pri[j]]=1ll*sum[i]*sum[pri[j]]&ful;
            }
        }
        for(i=2;i<M;i++){
            sum[i]=1ll*sum[i]*i&ful;
            sum[i]=(sum[i-1]+sum[i])&ful;
        }
    }
    int f(int a){
        return (1ll*a*(a+1)>>1)&ful;
    }
    void solve(){
        int ans=0,i,a,b,en;
        rd(A),rd(B);
        if(A>B)swap(A,B);//默认A较小 
        for(i=1;i<=A;i=en+1){
            a=A/i,b=B/i;
            en=min(A/a,B/b);
            ans=(ans+((1ll*f(a)*f(b)&ful)*(sum[en]-sum[i-1]+P)&ful))&ful;
        }
        sc(ans);
    }
    int main(){
    //    freopen("lcm.in","r",stdin);
    //    freopen("lcm.out","w",stdout);
    //    printf("%d
    ",P);
        init();
        int cas;
        rd(cas);
        while(cas--)solve();
        return 0;
    } 
    View Code

    2017-04-16(179)

    ▲14:17:14 BZOJ1449 最小费用流 对于比赛输赢的建模,如果输赢都考虑非常难搞,所以可以考虑先把答案都设为输,那么现在每场比赛只用考虑赢的人就可以啦.这样就方便很多.

    ▲14:38:05 模拟赛(by phillipsweng)T1 启发式合并+倍增/并查集+LCT

    比赛时写的是第一种做法,启合+倍增.但是因为有一个小地方写错,狗带了...

    因为这种做法是O(nlogn^2)的,所以要卡一波常数,因此在更新倍增数组的时候这样写了

    for(i=1;i<S&&fa[x][i-1];i++)fa[x][i]=fa[fa[x][i-1]][i-1]

    因为有可能原来的dis更长,导致fa[x][i-1]=0 但是fa[x][i]还有数字,但是更新就停止了,后面的没有清空,导致LCA求错...然后狗带.

    后来发现其实这个优化没有什么卵用,没有优化的反而快点..

    两种写法的具体思路是一样的:维护每个联通块的直径端点,并且能够迅速求出两点之间距离.

    对于可以离线的情况:可以先把最后的树建立出来,这样就可以方便求出两点之间的距离,然后再启发式合并维护每个联通块的直径即可.

    并查集+LCT的做法:用LCT维护加边操作和两点距离即可.一开始脑子短路了,考虑怎么记录每个联通块,因为LCT中联通块的根经常变,无法有一个确定的数字,所以用到并查集!!并查集只维护点的联通情况,保证一个联通块内的点都连向同一个点rt即可,用rt记录联通块的直径端点.剩下的就是LCT连边和求两点距离的功能了.注意:连边(x,y)的时候是先把x make_rt,再把x的fa设置为y,再access x.

    树上加边,删边->LCT大法!!

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    using namespace std;
    inline void rd(int &res){
        res=0;char c;
        while(c=getchar(),c<48);
        do res=(res<<1)+(res<<3)+(c^48);
        while(c=getchar(),c>=48);
    }
    void print(int x){
        if(!x)return ;
        print(x/10);
        putchar((x%10)^48);
    }
    void sc(int x){
        if(x<0){x=-x;putchar('-');}
        print(x);
        if(!x)putchar('0');
        putchar('
    ');
    }
    const int M=3e5+15;
    int n,m,fa[M],tmp[M];
    int type,dx[M],dy[M],sz[M],D[M],ch[M][2],rev[M],par[M];
    int get(int a){
        if(par[a]!=a)return par[a]=get(par[a]);
        return a;
    }
    void up(int x){
        sz[x]=sz[ch[x][0]]+sz[ch[x][1]]+1;
    }
    void down(int x){
        if(rev[x]){
            swap(ch[x][0],ch[x][1]);
            rev[ch[x][0]]^=1,rev[ch[x][1]]^=1;
            rev[x]=0;
        }
    }
    bool chk(int x){
        return ch[fa[x]][0]==x||ch[fa[x]][1]==x;
    }
    void rotate(int x){
        int y=fa[x],z=fa[fa[x]],son;
        bool a=ch[y][1]==x,b=ch[z][1]==y;
        son=ch[x][a^1];
        if(chk(y))ch[z][b]=x;fa[x]=z;
        ch[y][a]=son,fa[son]=y;
        ch[x][a^1]=y,fa[y]=x;
        up(y);
    }
    void splay(int x){
        int y=x,z,tot=0;
        while(chk(y))tmp[++tot]=y,y=fa[y];
        tmp[++tot]=y;
        while(tot)down(tmp[tot--]);
        while(chk(x)){
            y=fa[x],z=fa[fa[x]];
            if(chk(y)){
                if(ch[y][0]==x^ch[z][0]==y)rotate(x);
                else rotate(y);
            }
            rotate(x);
        }
        up(x);
    }
    void access(int x){
        for(int t=0;x;t=x,x=fa[x]){
            splay(x),ch[x][1]=t,up(x);
        }
    }
    void make_rt(int a){
        access(a),splay(a);rev[a]^=1;
    }
    void link(int a,int b){ 
        make_rt(b);fa[b]=a;access(b);
    }
    int Dis(int a,int b){
        if(a==b)return 0;
        make_rt(a);access(b);splay(b);
        return sz[b]-1;
    }
    void upd_D(int c,int v,int a,int b){
        if(v>D[c])D[c]=v,dx[c]=a,dy[c]=b;
    }
    void upd(int a,int b){// 加边(a,b)
        int y,x,c,d;
        c=get(a),d=get(b);
    
        link(a,b);//把id设置为c 
        x=dx[c],y=dy[c];
        
        upd_D(c,D[d],dx[d],dy[d]);
        upd_D(c,Dis(x,dx[d]),x,dx[d]);
        upd_D(c,Dis(x,dy[d]),x,dy[d]);
        upd_D(c,Dis(y,dx[d]),y,dx[d]);
        upd_D(c,Dis(y,dy[d]),y,dy[d]);
        
        par[d]=c;
    }
    int qry(int a){
        int b=get(a);
        return max(Dis(a,dx[b]),Dis(a,dy[b]));
    }
    void solve(){
        int i,j,k,op,ans=0,a,b;
        for(i=1;i<=n;i++){
            fa[i]=0,sz[i]=1;
            dx[i]=i,dy[i]=i;
            par[i]=i;
        }
        while(m--){
            rd(op);
            if(op==1){
                rd(a),rd(b);
                if(type)a^=ans,b^=ans;
                upd(a,b);
            }
            else{
                rd(a);
                if(type)a^=ans;
                ans=qry(a);
                sc(ans);
            }
        }
    }
    int main(){
    //    freopen("hike.in","r",stdin);
    //    freopen("hike.out","w",stdout);
        rd(type);
        rd(n);rd(m);
        solve();
        return 0;
    }
    LCT
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #include<ctime>
    #include<cstdlib>
    #include<cmath>
    #include<string>
    #include<vector>
    #include<map>
    #include<queue>
    #include<bitset>
    #define ll long long
    #define debug(x) cout<<#x<<"  "<<x<<endl;
    #define db(x,y)cout<<#x<<" "<<#y<<" "<<x<<" "<<y<<endl;
    using namespace std;
    inline void rd(int &res){
        res=0;char c;
        while(c=getchar(),c<48);
        do res=(res<<1)+(res<<3)+(c^48);
        while(c=getchar(),c>=48);
    }
    void print(int x){
        if(!x)return ;
        print(x/10);
        putchar((x%10)^48);
    }
    void sc(int x){
        if(x<0){x=-x;putchar('-');}
        print(x);
        if(!x)putchar('0');
        putchar('
    ');
    }
    inline void Max(int &x,int y){if(x<y)x=y;}
    inline void Min(int &x,int y){if(x>y)x=y;}
    const int M=6e5+15,S=20;//30*20w=600w
    int n,m,to[M],nxt[M],ec=2,head[M],fa[M/2][20],mp[1<<S];
    int type,id[M],dis[M],dx[M],dy[M],sz[M],D[M],lg_[M];
    void ins(int a,int b){
        to[ec]=b;nxt[ec]=head[a];head[a]=ec++;
        to[ec]=a;nxt[ec]=head[b];head[b]=ec++;
    }
    int flag=0;
    namespace P2{
        int cnt[M];
        void dfs(int x,int par,int d){
             
            fa[x][0]=par;
            for(int i=1,j=lg_[max(dis[x],d)]+1;i<=j;i++)fa[x][i]=fa[fa[x][i-1]][i-1];
            dis[x]=d;
            id[x]=id[par];
            for(int i=head[x];i;i=nxt[i]){
                if(to[i]!=par)dfs(to[i],x,d+1);
            }
        }
        void Up(int &x,int step){
            while(step){
                int k=step&-step;
                x=fa[x][mp[k]];
                step-=k;
            }
        } 
        int LCA(int a,int b){
            if(dis[a]<dis[b])swap(a,b);
            Up(a,dis[a]-dis[b]);
            if(a==b)return a;
            int i,st=lg_[dis[a]]+1;
            for(i=st;i>=0;i--){
                if(fa[a][i]!=fa[b][i])a=fa[a][i],b=fa[b][i];
            }
            return fa[a][0];
        }
        int Dis(int a,int b){
            return dis[a]+dis[b]-2*dis[LCA(a,b)];
        }
        void upd_D(int &x,int &y,int c,int v,int a,int b){
            if(v>D[c])D[c]=v,x=a,y=b;
        }
        void upd(int a,int b){// 加边(a,b)   默认a较大 把b加到a里去 
            int i,y,k,x,c,d;
             
            if(sz[id[a]]<sz[id[b]])swap(a,b);
             
            c=id[a],d=id[b];
            sz[c]+=sz[d];
            dfs(b,a,dis[a]+1);//得到fa,dis
            x=dx[c],y=dy[c];
            upd_D(x,y,c,D[d],dx[d],dy[d]);
            upd_D(x,y,c,Dis(dx[c],dx[d]),dx[c],dx[d]);
            upd_D(x,y,c,Dis(dx[c],dy[d]),dx[c],dy[d]);
            upd_D(x,y,c,Dis(dy[c],dx[d]),dy[c],dx[d]);
            upd_D(x,y,c,Dis(dy[c],dy[d]),dy[c],dy[d]);
            dx[c]=x,dy[c]=y;
            ins(a,b);
        }
        int qry(int a){
            return max(Dis(a,dx[id[a]]),Dis(a,dy[id[a]]));
        }
        void solve(){
            int i,j,k,op,ans=0,a,b;
            for(i=1,j=1;i<=n;i++){
                lg_[i]=j;
                if((i&(i-1))==0)j++;
            }
            for(i=0;i<S;i++)mp[1<<i]=i;
            for(i=1;i<=n;i++){
                id[i]=i;
                sz[i]=1;
                dx[i]=dy[i]=i;
            }
            while(m--){
                rd(op);
                if(op==1){
                    rd(a),rd(b);
                    if(type)a^=ans,b^=ans;
                    upd(a,b);
                }
                else{
                    rd(a);
                    if(type)a^=ans;
                    ans=qry(a);
                    sc(ans);
                }
            }
        }
    }
    int main(){
    //  freopen("hike.in","r",stdin);
    //  freopen("hike.out","w",stdout);
        rd(type);
        rd(n);rd(m);
        P2::solve();
        return 0;
    }
    启发式合并+倍增 

    2017-04-17周一(187)

    ▲11:22:08 模拟赛T2 斜率优化/DP

    暴力做法: 经典01背包+滚动数组优化 复杂度n*K

    C=V 时候,相当于求是否能从物品中选出一些价值和为k. 我用bitset来乱搞hhh

    正解:切入点在于每个物品的价格非常小,最多只要300!!所以我们把价格相同的东西,放在一起,假设最终选取了a个,那肯定是选价值最大的a个,因此可以处理出前缀和,v[s][a]表示价格为s的东西前a大的前缀和.v是一个斜率单调不增的函数,这对于后面的影响很重要!!

    那么原问题可以这样求解:f[s][i]表示考虑价格为前s的物品,花了i价格,得到的最大价值.

    f[s][i]=Max{f[s-1][i-k*s]+v[s][k]}  复杂度是O(s*K*n)

    可以发现对于s,i的转移,之和与imods同余的点有关,因此我们对于一个s,将所有i根据mod s的 余数分类,每一类单独解决.

    假设当前要解决的i/s=y,现在要找到对应的x,使得  f[s-1][x*s+mod]+v[s][y-x]最大,考虑x的变化.

    假如x2>x1,且x2对应的值比x1更优时,y应该满足什么条件,由于v函数的上凸性质,我们可以通过二分,得到y的范围,是形如[y',oo]的结构.那么我们就用队列维护相邻两个x的y'值.假设队列最后的元素为a,b,现在要加一个c,如果bc的y比ab的小于等于,那么b点就可以删掉..一次类推.那么队列就是一个单调递增的东西.对于当前的y,把队首不合法(包括个数太大或者不是最优)的删掉,队首的当前元素就是最优的x了.注意二分的时候要判断y的初始范围.s的个数是有限制的啊!!

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<vector>
    #define ll long long
    using namespace std;
    const int N=(int)1e6+5;
    const int M=50005;
    inline void rd(int &res){
        res=0;char c;
        while(c=getchar(),c<48);
        do res=(res<<1)+(res<<3)+(c^48);
        while(c=getchar(),c>=48);
    }
    void pt(ll x){
        if(!x)return;
        pt(x/10);
        putchar((x%10)^48);
    }
    void sc(ll x){
        if(x)pt(x);
        else putchar('0');
    //    putchar(' ');
    }
    int sum=0,n,m,Q[M],h[M],p,s,tot;
    bool cur=0;
    ll v[N],f[2][M+500];//v[i] 表示选前i个的价值和  f[i]表示当前花了i 得到的最多价值 
    vector<int>A[305];
    int pos(int x1,int x2,int mod){//求临界点  x2>x1 
        int a=p,L=0,R=min(tot,p-x2),t=x2-x1;
        if(t>tot)return p+1;
        double k=(double)(1.0*(f[cur^1][x2*s+mod]-f[cur^1][x1*s+mod])/(x2-x1));
        ll g=(ll)(k*t);
        while(L<=R){
            int mid=L+R>>1;
            if(v[mid+t]-v[mid]<=g){
                a=mid,R=mid-1;
            }else L=mid+1;
        }
        return a+x2;
    }
    void work(){//s为单价 tot表示个数 
        int L,R,i,j,k,x,y;//滚动数组
        cur^=1;
        sum+=s*tot;
        p=min(sum,m)/s;//能够买的上界 
        for(j=0;j<s;j++){//枚举mod s的余数 
            L=R=1;Q[R]=0;//x=0//Q[i]记录的是x的值 h再记录它和上一个的临界点y' 
            f[cur][j]=f[cur^1][j];
            for(y=1;y<=p;y++){//f[y*s+j]应该是队列的首元素  y-Q[L]是这次用的个数 
                while(L<=R&&(h[y]=pos(Q[R],y,j))<=h[Q[R]])R--;//删掉队尾非最优的 
                if(L<=R)h[y]=pos(Q[R],y,j);
                else h[y]=0;
                Q[++R]=y;//插入 
                while(L<R&&(y-Q[L]>tot||h[Q[L+1]]<=y))L++;//删掉队首非最优的 
                //y=2 y-Q[L]=1 -> Q[L]=1 
                f[cur][y*s+j]=f[cur^1][Q[L]*s+j]+v[y-Q[L]];
            }
        }
    }
    bool cmp(int a,int b){return a>b;}
    void solve(){
        int i,j,k,c,val;
        rd(n);rd(m);
        for(i=1;i<=n;i++){
            rd(c),rd(val);
            A[c].push_back(val);
        }
        for(s=1;s<=300;s++){
            tot=A[s].size();
            if(!tot)continue;
            sort(A[s].begin(),A[s].end(),cmp);
            for(j=0;j<tot;j++)v[j+1]=v[j]+A[s][j];
            work();
        }
        for(i=1;i<=m;i++){
            f[cur][i]=max(f[cur][i],f[cur][i-1]);
            sc(f[cur][i]);
            putchar(" 
    "[i==m]);
        }
    }
    int main(){
    //    freopen("jewelry.in","r",stdin);
    //    freopen("jewelry.out","w",stdout);
        solve();
        return 0;
    }
    View Code

     ▲16:12:55 BZOJ1597 斜率优化 一开始卡了半天我去,对于可包含的情况不用考虑了 那么现在的序列就是一个根据y升序的序列,x必定是降序的,这样就很显然了.在想题目的时候,要把冗余去掉,把条件越精简越好.

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #define ll long long
    using namespace std;
    const int M=5e4+5;
    struct node{
        int x,y;
        bool operator<(const node &tmp)const{
            if(y!=tmp.y)return y<tmp.y;
            return x<tmp.x;
        }
    }A[M];
    int m,n=0,Q[M],h[M];
    ll f[M];
    inline void rd(int &res){
        res=0;char c;
        while(c=getchar(),c<48);
        do res=(res<<1)+(res<<3)+(c^48);
        while(c=getchar(),c>=48);
    }
    int pos(int x1,int x2){//k<v[y] 的最小的y    假如k=3.2   
        int res=n+1,L=x2+1,R=n;
        ll k=(f[x2]-f[x1]+A[x1+1].x-A[x2+1].x-1)/(A[x1+1].x-A[x2+1].x);
        while(L<=R){
            int mid=L+R>>1;
            if(A[mid].y>=k){
                res=mid;
                R=mid-1;
            }else L=mid+1;
        }
        return res;
    }
    void solve(){
        int i,j,k;
        rd(m);
        for(i=1;i<=m;i++)rd(A[i].x),rd(A[i].y);
        sort(A+1,A+1+m);
        for(i=1;i<=m;i++){
            while(n&&A[n].x<=A[i].x)n--;
            A[++n]=A[i];
        }
        int L=1,R=1;
        Q[1]=0; 
        for(i=1;i<=n;i++){
            while(L<R&&h[Q[L+1]]<=i)L++; 
            f[i]=f[Q[L]]+1ll*A[Q[L]+1].x*A[i].y;
            while(L<=R&&(h[i]=pos(Q[R],i))<=h[Q[R]])R--;
            Q[++R]=i;
        }
        cout<<f[n]<<endl;
    }
    int main(){
    //    freopen("da.in","r",stdin);
        solve();
        return 0;
    }
    斜率优化

     ▲16:42:52 BZOJ1911 APIO2010 特别行动队  斜率优化 DP推式子.

    ▲19:01:59 BZOJ1096 ZJOI2007 仓库建设 斜率优化

    ▲19:22:49 BZOJ3156 防御准备 斜率优化 裸题

    ▲20:43:52 BZOJ1010 斜率优化裸题 注意注意注意  变量名不要重复啦!!!尤其是二分的L,R 和全局变量是否重复!!!

    ▲21:17:55  BZOJ 3437 斜率优化 裸题...

    ▲22:22:42 BZOJ 3675 APIO2014 斜率优化+终态枚举/推结论


    2017-04-18 周二 (189)

    ▲11:08:42 HDU5956 树上DP+斜率优化. 明显的斜率优化,只是把dp从序列上搬到树上去了,所以需要还原现场,记录队列的左右指针位置以及删除的信息即可.

    注意斜率优化有一个优化: 在求决策点时,假如A[mid]的单调的,就不用二分啦!!直接记录这个值就可以啦.

    ▲16:16:39 BZOJ1492 NOI2007 CDQ分治+斜率优化.

    根据题目给出的每次都可以"全部买,全部卖掉"这个条件,可以把操作过程简化:

    假设f[i]为i天操作完毕之后得到的最多人民币数量.那么第i天有两种可能,什么都不干,得到f[i-1],也就是上一天的人民币数量;或者用金券升值,假设在第k天买金券 就有f[i]=第k天最多的人民币来换得当天的金券 ,金券保留到第i天,然后卖掉 收获人民币.

    f[i]=Max{f[i-1],(A[i]*R[k]+B[i])*f[k]/(A[k]*R[k]+B[k])},暴力就是n^2的.

    对于这个式子并不能直接用斜率优化来搞.

    按照一般的优化思路,有:

    设x[i]=f[i]/(A[i]*R[i]+B[i])

    x2>x1 且x2优于x1 时的i,满足:  A[i]*(R[x2]*x[x2]-R[x1]*x[x1])+B[i]*(x[x2]-x[x1])>=0

    对于有两个未知数的情况,可以通过移项相除的方式,把未知数变为一个->  A[i]*(R[x2]*x[x2]-R[x1]*x[x1])>=B[i]*(x[x1]-x[x2])

    如果要相除,要确定(x[x1]-x[x2])的符号,假如是负的,那么就可以转化了.但是现在的x并不是有序的.那么我们把它构造成有序的.

    可以利用CDQ分治,维护下标,那么每次就可以将左右部分 分别按照x和B/A排序,这样就可以完成优化了.

    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<iostream>
    #include<algorithm>
    #define db double
    using namespace std;
    const int M=1e5+5;
    const db eps=1e-8,oo=1e18;
    int Q[M],n,s[M];
    db f[M],x[M],A[M],B[M],Rate[M],h[M],ab[M];
    bool cmp(int a,int b){
        return ab[a]<ab[b];
    }
    bool cmp2(int a,int b){
        return x[a]<x[b];
    }
    db calc(int a,int b){
        if(fabs(x[b]-x[a])<eps)return oo;
        return (Rate[b]*x[b]-Rate[a]*x[a])/(x[a]-x[b]);
    }
    void work(int L,int R){
        if(L==R){
            f[L]=max(f[L],f[L-1]);
            x[L]=f[L]/(A[L]*Rate[L]+B[L]);
            return;
        }//已经更新完了
        int i,j,t=0,l=1,r=0,mid=L+R>>1;//先处理左边 
        work(L,mid);
        h[L]=-oo;
        for(i=L;i<=mid;i++)s[t++]=i;
        sort(s,s+t,cmp2);
        for(i=0;i<t;i++){
            while(l<=r&&(h[s[i]]=calc(Q[r],s[i]))<=h[Q[r]])r--;
            Q[++r]=s[i];
        }
        for(i=mid+1,t=0;i<=R;i++)s[t++]=i;
        sort(s,s+t,cmp);
        for(i=0;i<t;i++){
            while(l<r&&h[Q[l+1]]<=ab[s[i]])l++;
            f[s[i]]=max(f[s[i]],(A[s[i]]*Rate[Q[l]]+B[s[i]])*x[Q[l]]);
        }
        work(mid+1,R);
    }
    int main(){
    //    freopen("da.in","r",stdin);
        int i,j,k;
        scanf("%d %d",&n,&k);
        f[0]=(db)k;//第一天的初始人民币 
        for(i=1;i<=n;i++){
            scanf("%lf %lf %lf",&A[i],&B[i],&Rate[i]);
            ab[i]=B[i]/A[i];
        }
        work(1,n);
        printf("%.3lf
    ",f[n]);
        return 0;
    }
    View Code

    CDQ分治可以通过分治下标或者别的信息,来维护dp或者操作的转移->每个点会被所有它前面的点都更新到.同时,又可以进行排序来维护其他维度.


    2017-04-19周三(191)

    ▲14:19:48 NOI2014 购票 点分治/CDQ分治/斜率优化.

    首先写一个简单的dp,可以得到

    f[x]=Min{f[y]-dis[y]*p[x]}+dis[x]*p[x]+q[x] 且dis[x]-dis[y]<=lim[x],y是x的祖先.

    假如没有lim[x]的限制,就可以通过斜率优化,对于当前的p[x]二分出最优答案.

    假如有lim[x]的限制,就可以无法满足直接用斜率优化,这样可能漏掉最优解.因此需要想方法把lim这个条件去掉.

    排序是一种方法,待更新的点和可用来更新的点按照能够去的最远和dis分别排序,一边求值一边更新,这样就可以保证当前凸包里的东西都是合法的.

    那么假如是在序列上,我们可以直接用cdq分治来解决这个问题了.用左儿子来更新右儿子,这个就是链的做法.

    对于一棵树,也用类似的方法,对于每一条链都类似于一个序列,但是又不能每条链分别考虑,而每条链都有多多少少的重叠部分,所以可以用点分治.在这里包含根的子树就相当于下标更小,需要先完成.对于一个以x为根的子树,找到重心cg,对于重心连向x那条边的那个子树需要最先处理,于是先处理以x为根的剩下部分...以此类推,保证cg到x的这部分都处理完了,那么剩下的部分就是cg的子树们了,用cg到x之间的点来更新cg的子树即可.类似于序列上的做法.

    好神啊!!!对于某一维有条件限制的情况可以通过分治来排序降维!!!CDQ!!!

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<iostream>
    #define ll long long
    using namespace std;
    const int M=2e5+5;
    const ll oo=1e18;
    int vis[M],Q[M],ty,n,ec=2,to[M],nxt[M],sz[M],head[M],fa[M],p[M],A[M];
    ll dis[M],h[M],lim[M],w[M],q[M],f[M];
    void Min(ll &a,ll b){if(a>b)a=b;}
    inline void rd(int &res){
        res=0;char c;
        while(c=getchar(),c<48);
        do res=(res<<1)+(res<<3)+(c^48);
        while(c=getchar(),c>=48);
    }
    inline void Rd(ll &res){
        res=0;char c;
        while(c=getchar(),c<48);
        do res=(res<<1)+(res<<3)+(c^48);
        while(c=getchar(),c>=48);
    }
    void print(ll x){
        if(!x)return ;
        print(x/10);
        putchar((x%10)^48);
    }
    void sc(ll x){
        if(x<0){x=-x;putchar('-');}
        print(x);
        if(!x)putchar('0');
        putchar('
    ');
    }
    void ins(int a,int b,ll c){
        to[ec]=b;nxt[ec]=head[a];w[ec]=c;head[a]=ec++;
    }
    void dfs(int x){
        for(int i=head[x];i;i=nxt[i]){
            dis[to[i]]=dis[x]+w[i];
            dfs(to[i]);
        }
    }
    void find_cg(int x,int tot,int &cg){//找到当前联通块的重心 ->  没有子树sz>tot/2 
        sz[x]=1;
        int f=1;
        for(int i=head[x];i;i=nxt[i]){
            if(!vis[i]){
                find_cg(to[i],tot,cg);
    
                sz[x]+=sz[to[i]];
                if(sz[to[i]]>(tot>>1))f=0;
            }
        }
        if(tot-sz[x]>(tot>>1))f=0;
        if(f)cg=x;
    }
    void rdfs(int x,int &m){
        A[++m]=x;
        for(int i=head[x];i;i=nxt[i]){
            if(!vis[i])rdfs(to[i],m);
        }
    }
    bool cmp(int a,int b){//按照能够上去的最远点 从低到高排序 
        return dis[a]-lim[a]>dis[b]-lim[b];
    }
    ll calc(int a,int b){//b比a优 
        if(f[a]>=f[b])return (f[a]-f[b])/(dis[a]-dis[b]); 
        return -(f[b]-f[a]+dis[a]-dis[b]-1)/(dis[a]-dis[b]); 
    }
    int find(int l,int r,int a){
        int res=-1;
        while(l<=r){
            int mid=l+r>>1;
            if(a<=h[Q[mid]]){
                res=Q[mid];
                l=mid+1; 
            }else r=mid-1;
        }
        return res;
    }
    void solve(int rt,int tot){
        if(tot==1)return;
        int l=1,r=0,cg=-1,i,j,k,m=0;
        find_cg(rt,tot,cg);//找到了cg 
        
        for(i=head[cg];i;i=nxt[i])vis[i]=1;//分离子树
        solve(rt,tot-sz[cg]+1);//还包括cg这个点的,这样就把cg到根以上的所有点都solve好了 
        
        for(i=head[cg];i;i=nxt[i])rdfs(to[i],m);
        
        sort(A+1,A+1+m,cmp);
        h[cg]=oo;
        for(i=1,j=cg;i<=m;i++){
            for(;j!=fa[rt]&&dis[j]>=dis[A[i]]-lim[A[i]];j=fa[j]){//可以搞 
                while(l<=r&&(h[j]=calc(Q[r],j))>=h[Q[r]])r--;//找到最小的>=p的 
                Q[++r]=j;
            }
            k=find(l,r,p[A[i]]);//找到最大的<=p的 Q[i]
            if(~k)Min(f[A[i]],f[k]+(dis[A[i]]-dis[k])*p[A[i]]+q[A[i]]); 
        }
            
        for(i=head[cg];i;i=nxt[i])solve(to[i],sz[to[i]]);
    }
    int main(){
    //    freopen("da.in","r",stdin);
    //   freopen("my.out","w",stdout);
        int i,j,k,a,b;
        ll c;
        rd(n);rd(ty);
        for(i=2;i<=n;i++){
            rd(fa[i]),Rd(c),rd(p[i]),Rd(q[i]),Rd(lim[i]);
            ins(fa[i],i,c);
            f[i]=oo;
        }
        dfs(1);//得到dis
        solve(1,n);// rt/sz 
        for(i=2;i<=n;i++)sc(f[i]);
        return 0;
    }
    View Code

     ▲ 22:35:05 HNOI2017 D1T1 找结论  

    首先确定平衡二叉树有一个性质:每个点的子树对应的值是一个区间.

    ①模拟单旋最值得操作,进行单旋提根之后,假设最值为x,除了x的儿子,其他点的深度都会增加+1,x的深度变为1.

    ②模拟单旋删除最值操作,最值为x,x的子树的点的深度会-1.

    ④插入操作: 加入一个点值为x,一定插在当前树中x的前驱或者x的后继的下面,代价是fa的深度+1.

    所以现在我们需要维护每个点的深度,再求前驱后继即可.

    用set求前驱后继,用线段树维护深度,进行单点赋值,区间更新.


    2017-04-20(194)

    ▲10:39:59 HNOI2017 D1T3 枚举 暴力想法是枚举旋转的位置,然后求最优的c.首先为了简化旋转匹配,把一个串倍长.

    假如A与B的第j个开始匹配,代价就是∑(A[i]-B[j+i]-c)^2

    分解之后发现是: ∑(Ai-B[j+i])^2+nc^2-2*∑(A[i]-B[j+i])c 

    求出c的一次项系数,那么关于c的是一个二次函数,可以O(1)求出最值.复杂度是O(n^2).

    再把前面的展开  ∑Ai^2 +∑B[j+i]^2-2*∑Ai*B[i+j]

    前面两项和c的系数都可以用前缀和O(1),但是第三项就很恶心了....考虑如何快速求出第三项,第三项是一个和.是两个数列对应项相乘的和.有点像卷积,但是又不是,但是我们可以通过反转B,写成卷积形式,然后用ntt优化~~注意!!因为这里的A,B值有点大,但又不是特别大,所以尽量不要用fft,会丢失精度.

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #include<cmath>
    #define ll long long
    #define db double
    using namespace std;
    inline void rd(int &res){
        res=0;char c;
        while(c=getchar(),c<48);
        do res=(res<<1)+(res<<3)+(c^48);
        while(c=getchar(),c>=48);
    }
    inline void Max(int &x,int y){if(x<y)x=y;}
    inline void Min(int &x,int y){if(x>y)x=y;}
    const int M=200005;
    const int oo=1e9+5;
    const int g=3,P=479*(1<<21)+1;
    int ans=oo,A[M],B[M],n,m;
    int qry(int b){
        b=abs(b);
        double p=1.0*b/(2*n);
        int c=(int)(p+0.5);
        return n*c*c-b*c; 
    }
    db pi=acos(-1);
    namespace P3{
        int pw(int a,int b){
            int res=1;
            while(b){
                if(b&1)res=1ll*res*a%P;
                a=1ll*a*a%P;
                b>>=1;
            }return res;
        }
        void Add(int &x,int y){
            x+=y;
            if(x<0)x+=P;
            else if(x>=P)x-=P;
        }
        int a[M],b[M];
        int R[M],C[M],D[M],len,tot,m;//cd 记录平方和,x,y记录和 
        void fft(int a[],int f){
            int i,j,k,len;
            for(i=0;i<tot;i++)if(i<R[i])swap(a[R[i]],a[i]);
            for(i=1;i<tot;i<<=1){
                len=i<<1;
                int w,wn=pw(g,(P-1)/len);
                if(f)wn=pw(wn,P-2);
                for(j=0;j<tot;j+=len){
                    for(w=1,k=0;k<i;k++,w=1ll*w*wn%P){
                        int t=a[j+k],y=1ll*w*a[j+k+i]%P;
                        Add(a[j+k]=t,y);
                        Add(a[j+k+i]=t,-y);
                    }
                }
            }
        }
        void work(){
            int i,j,k,c=0,d=0;
            for(i=1;i<=m;i++){
                D[i]=D[i-1]+B[i]*B[i];
                C[i]=C[i-1]+B[i];
                if(i<=n)d+=A[i]*A[i],c+=A[i];
            }
            a[0]=b[0]=0;
            for(i=1;i<=n;i++)a[i]=A[i];
            for(i=n+1;i<tot;i++)a[i]=0;
             
            for(i=1;i<m;i++)b[i]=B[m-i];
            for(i=m;i<tot;i++)b[i]=0;
             
            fft(a,0);
            fft(b,0);
             
            for(i=0;i<tot;i++)a[i]=1ll*a[i]*b[i]%P;
             
            fft(a,1);
            int inv=pw(tot,P-2);
            for(i=0;i<n;i++){
                int sum=0,val=1ll*a[m-i]*inv%P;
                sum=d+D[n+i]-D[i]+qry(2*(c-C[n+i]+C[i]))-2*val; 
                ans=min(ans,sum);
            }
        }
        void solve(){
            m=n*2;
            int i;
            for(tot=1;tot<=m;tot<<=1,len++);
            for(i=0;i<tot;i++)R[i]=(R[i>>1]>>1)|((i&1)<<len-1);
             
            for(i=1;i<=n;i++)rd(A[i]),A[i+n]=A[i];
            for(i=1;i<=n;i++)rd(B[i]),B[i+n]=B[i];
            work();
             
            printf("%d
    ",ans);
        }
    }
    int main(){
    //  freopen("gift.in","r",stdin);
    //  freopen("gift.out","w",stdout);
        rd(n);rd(m);
        P3::solve();
        return 0;
    }
    View Code

    ▲16:42:22 HNOI2017 D1T2 猜结论!!终态枚举!!  对于第一种情况 ,i,j分别是[i,j]的最大值,这个不好枚举,但是可以把这个条件转化一下:[i+1,j-1]中的最大数字<i,j那么我们是不是可以枚举[i+1,j-1]中的最大数字呢??假如枚举为i,那么对应的l,r就是左边右边第一个比ai大的数字!!

    那么这个问题就可以解决了.

    考虑第二种.对于第二种情况,假设L是最大值,那么次大值为L+k,那么保证[L+k+1,R]不包含比L+k大的数字,这个和上面的结构有点像?

    对于i,假如以l[i]作为左端点,那么次大值为i时,保证右端点在[i+1,r[i]-1]就可以保证满足p2条件.按照这样的方式能够枚举到所有的p2,因为对于任意一对最大和次大都被枚举到了.

    现在知道解的构成,就考虑如何求出解.首先只考虑最大值在左端点的情况(因为同理可以求出最大值在右端点的答案):

    对于p2:每个右端点对应一个区间,可以将询问按照L从大到小,并且同时从大到小进行[L,a,b]的更新(表示,以L为左端点,右端点在[a,b]可行).这样保证现在更新到的都是满足这个询问的条件的.然后问题就变成了区间更新,区间求和了,用线段树可以简单求解.

    对于p1,可以看做每次的a,b都相等.但是由于p1,p2可能不同,所以在线段树上存储个数不便,不如直接存权值,就是个数*p1或p2后的值,这样只要进行加减即可.

    同理右端点也可以这么求,然后问题就解决了~

    #include<cstdio> 
    #include<cstring>
    #include<algorithm>
    #include<iostream>
    #define ll long long
    using namespace std;
    inline void rd(int &res){
        res=0;char c;
        while(c=getchar(),c<48);
        do res=(res<<1)+(res<<3)+(c^48);
        while(c=getchar(),c>=48);
    }
    void print(ll x){
        if(!x)return ;
        print(x/10);
        putchar((x%10)^48);
    }
    void sc(ll x){
        if(x<0){x=-x;putchar('-');}
        print(x);
        if(!x)putchar('0');
        putchar('
    ');
    }
    const int M=2e5+5;
    int p1,p2,n,m,l[M],r[M],stk[M],A[M],tot,u[M],v[M];
    ll res[M];
    struct node{
        int id,a,b,c; 
    }q[M<<2];
    struct Seg{
        int add[M<<2];
        ll t[M<<2];
        void down(int L,int R,int mid,int p){
            if(!add[p])return;
            t[p<<1]+=1ll*add[p]*(mid-L+1);
            t[p<<1|1]+=1ll*add[p]*(R-mid);
            add[p<<1]+=add[p];
            add[p<<1|1]+=add[p];
            add[p]=0;
        }
        void up(int p){
            t[p]=t[p<<1]+t[p<<1|1];
        }
        void upd(int L,int R,int l,int r,int a,int p){
            if(L==l&&R==r){
                t[p]+=1ll*a*(R-L+1);
                add[p]+=a;
                return;
            }
            int mid=L+R>>1;
            down(L,R,mid,p);
            if(r<=mid)upd(L,mid,l,r,a,p<<1);
            else if(l>mid)upd(mid+1,R,l,r,a,p<<1|1);
            else upd(L,mid,l,mid,a,p<<1),upd(mid+1,R,mid+1,r,a,p<<1|1);
            up(p);
        }
        ll qry(int L,int R,int l,int r,int p){
            if(L==l&&R==r)return t[p];
            int mid=L+R>>1;
            down(L,R,mid,p);
            if(r<=mid)return qry(L,mid,l,r,p<<1);
            else if(l>mid)return qry(mid+1,R,l,r,p<<1|1);
            else return qry(L,mid,l,mid,p<<1)+qry(mid+1,R,mid+1,r,p<<1|1);
        }
        void init(int L,int R,int p){
            add[p]=t[p]=0;
            if(L==R)return;
            int mid=L+R>>1;
            init(L,mid,p<<1);
            init(mid+1,R,p<<1|1);
        }
    }T;
    bool cmpL(node x,node y){
        if(x.a!=y.a)return x.a>y.a;
        return x.id<y.id;//更新在询问之前 
    }
    bool cmpR(node x,node y){
        if(x.b!=y.b)return x.b<y.b;//
        return x.id<y.id;//更新在询问之前 
    }
    void solve(){
        int i,j,k,top=0;
        rd(n);rd(m);rd(p1);rd(p2);
        for(i=1;i<=n;i++)rd(A[i]),r[i]=n+1;
        //找到左边第一个比我大的点l[i]
        for(i=1;i<=n;i++){
            while(top&&A[stk[top]]<A[i]){
                r[stk[top]]=i;
                top--;
            }
            l[i]=stk[top];
            stk[++top]=i;
        } 
        for(i=1;i<=m;i++){
            q[i].id=i,rd(q[i].a),rd(q[i].b);
            u[i]=q[i].a,v[i]=q[i].b;
        }
        tot=m;
        for(i=1;i<=n;i++){//[li,x] x<-[i+1,r[i]-1]
            if(l[i]&&i+1<=r[i]-1)q[++tot]=(node){0,l[i],i+1,r[i]-1};
        }
        sort(q+1,q+1+tot,cmpL);
        for(i=1;i<=tot;i++){
            if(!q[i].id)T.upd(1,n,q[i].b,q[i].c,p2,1); 
            else res[q[i].id]+=T.qry(1,n,q[i].a,q[i].b,1);//区间求和 
        }
        T.init(1,n,1);
        for(i=1,tot=m;i<=m;i++)q[i]=(node){i,u[i],v[i]};
        for(i=1;i<=n;i++){
            if(r[i]<=n&&l[i]+1<=i-1)q[++tot]=(node){0,l[i]+1,r[i],i-1};
            if(l[i]&&r[i]<=n)q[++tot]=(node){-1,l[i],r[i],l[i]};
        }
        sort(q+1,q+1+tot,cmpR);
        for(i=1;i<=tot;i++){
            if(q[i].id==-1)T.upd(1,n,q[i].a,q[i].a,p1,1);
    
            else if(!q[i].id)T.upd(1,n,q[i].a,q[i].c,p2,1);
        
            else res[q[i].id]+=T.qry(1,n,q[i].a,q[i].b,1);
        }
        for(i=1;i<=m;i++){
            res[i]+=p1*(v[i]-u[i]);
            sc(res[i]);
        }
    }
    int main(){
    //    freopen("sf.in","r",stdin);
    //    freopen("sf.out","w",stdout);
        solve();
        return 0;
    }
    View Code

     ▲22:25:04 模拟赛T2  KMP大法好~~遇到一个模式串和匹配串的问题可以通过KMP解决啊~~就是在判断匹配的时候根据条件变通一下~


     2017-04-23周日(196)

    ▲14:22:06 模拟赛T1 概率DP  把每个东西的贡献分开考虑->m=1  当m=2时,把x^2看成一种win了x场的方案中任选两个有序可重复的ai的方案数!

    那么我们考虑任意一个二元组对答案的贡献:考虑任意一个二元组被选中的概率,对于相同元素的二元组,被选中的概率就是赢的概率,可计算;否则(a,b)就是a赢了并且b赢了的概率.但要去掉两者冲突的情况,然后推出式子用前缀和优化一下就好了.

    对于m很大但是n较小的情况 考虑用Dp来求出赢得x场的方案数.

    我们现在要确保有x场赢得比赛,假如设f[i][j]为前i个人赢了j场的方案数,因为不确定对方还剩下那些人所以不好求,既然这不好求,那么我们就忽略这个部分!只要确定有x人赢了就好,其他人不管了.

    我们从小到大dp,这样保证可以确定出第i个人赢的选择.从而求出dp[i][j],前i个人,赢了j场,其他人不管的方案数.

    但是这个并不是答案,要算出最终的答案g[i]表示赢i场的方案数还要继续算.  对于f[n][i],考虑其他输掉的n-i个人的情况,它们一共有(n-i)!种选择,但是某些选择会让这n-i中有些人也赢,我们还要去掉这些方案.只要枚举一共多了多少人赢假设有j(j>i)个人赢了  -g[j]*C[j][i]就是方案数.

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #include<ctime>
    #include<cstdlib>
    #include<cmath>
    #include<string>
    #include<vector>
    #include<map>
    #include<queue>
    #include<bitset>
    #define ll long long
    #define debug(x) cout<<#x<<"  "<<x<<endl;
    #define db double
    using namespace std;
    inline void rd(int &res){
        res=0;char c;
        while(c=getchar(),c<48);
        do res=(res<<1)+(res<<3)+(c^48);
        while(c=getchar(),c>=48);
    }
    void print(int x){
        if(!x)return ;
        print(x/10);
        putchar((x%10)^48);
    }
    void sc(int x){
        if(x<0){x=-x;putchar('-');}
        print(x);
        if(!x)putchar('0');
        putchar('
    ');
    }
    inline void Max(int &x,int y){if(x<y)x=y;}
    inline void Min(int &x,int y){if(x>y)x=y;}
    const int P=1e9+7;
    const int M=1e6+5;
    int n,m,A[M],B[M];
    int pw(int a,int b){
        int res=1;
        while(b){
            if(b&1)res=1ll*a*res%P;
            a=1ll*a*a%P;
            b>>=1;
        }
        return res;
    }
    void Add(int &x,int y){
        x+=y;
        if(x>=P)x-=P;
    }
    namespace P1{
        void solve(){
            int i,j,k,inv=pw(n,P-2),ans=0;
            for(i=0;i<n;i++)rd(A[i]);
            for(i=0;i<n;i++)rd(B[i]);
            for(i=0,j=0;i<n;i++){
                while(j<n&&B[j]<=A[i])j++;
                Add(ans,j);
            }
            ans=1ll*ans*inv%P;
            printf("%d
    ",ans);
        }
    }
    namespace P2{// m=2
        void solve(){
            int i,j,k,ans=0,inv=pw(n,P-2),sum=0;//  1/n*(n-1)  *  (w[i]-1)*∑w[j]
            for(i=1;i<=n;i++)rd(A[i]);
            for(i=0;i<n;i++)rd(B[i]);                
            for(i=1,j=0;i<=n;i++){
                while(j<n&&A[i]>=B[j])j++;
                if(j)Add(ans,1ll*(j-1)*sum%P);
                Add(sum,j);
            }
            ans=1ll*ans*inv%P;
            ans=1ll*ans*pw(n-1,P-2)%P;
            ans=ans*2%P;
            Add(ans,1ll*sum*inv%P);
            printf("%d
    ",ans);
        }
    }
    namespace P3{
        const int M=2005;
        int C[M][M];//400w
        int f[M][M],g[M],fac[M];
        void solve(){
            int i,j,k,ans=0;
            for(i=1;i<=n;i++)rd(A[i]);
            for(i=0;i<n;i++)rd(B[i]);
            //f[i][j]=f[i-1][j]+f[i-1][j-1]*p
            C[0][0]=fac[0]=1;
            f[0][0]=1;
            for(k=0,i=1;i<=n;i++){
                while(k<n&&A[i]>=B[k])k++;
                for(j=0;j<=i;j++){
                    f[i][j]=f[i-1][j];
                    if(k-j+1>0)Add(f[i][j],1ll*f[i-1][j-1]*(k-j+1)%P);
                }
                C[i][0]=1;
                for(j=1;j<=i;j++)Add(C[i][j]=C[i-1][j],C[i-1][j-1]);
                fac[i]=1ll*fac[i-1]*i%P;
            }
            for(i=n;i>=1;i--){//f[n][i]*(n-i)!-去掉不小心赢的=∑g[j]*C[j][i] 
                g[i]=1ll*f[n][i]*fac[n-i]%P;
                for(j=i+1;j<=n;j++)Add(g[i],-1ll*g[j]*C[j][i]%P+P);
                Add(ans,1ll*g[i]*pw(i,m)%P);
            }
            ans=1ll*ans*pw(fac[n],P-2)%P;
            printf("%d
    ",ans);
        }
    }
    int main(){
    //  freopen("duel.in","r",stdin);
    //  freopen("duel.out","w",stdout);
        rd(n);rd(m);
        if(m==1)P1::solve();
        else if(m==2)P2::solve();
        else
        P3::solve();
        return 0;
    }
    View Code

    ▲19:01:21 模拟赛T3  扫描线 若存在相切的情况,一定会保证是在同一列中相邻的两个圆的情况,所以根据圆心从上到下排序,然后根据圆从左到右出现进行扫描线排序.然后维护上下方向的前驱后继.但是有一个bug就是在删除一个点的时候会修改一对前驱后继,因此需要判断这一对是否已经计算在答案中了.用map来维护即可.

    #include<cstdio>
    #include<cstring>
    #include<map>
    #include<algorithm>
    #include<iostream>
    #include<set>
    #define ll long long
    #define mkp(a,b) make_pair(a,b) 
    using namespace std;
    const int M=5e5+5;
    inline void rd(int &res){
        res=0;char c;int k=1;
        while(c=getchar(),c<48&&c!='-');
        if(c=='-')c=getchar(),k=-1;
        do res=(res<<1)+(res<<3)+(c^48);
        while(c=getchar(),c>=48);
        res*=k;
    }
    struct CIRCLE{
        int x,y,r;
        bool operator<(const CIRCLE &tmp)const{
            if(y!=tmp.y)return y<tmp.y;
            return x<tmp.x;
        }
    }a[M];
    struct node{
        int x,id;//id<0  表示删除 
        bool operator<(const node &tmp)const{
            if(x!=tmp.x)return x<tmp.x;
            return id<tmp.id;
        }
    }b[M<<1];//每个圆有两个动作 
    typedef pair<int,int> pii;
    map<pii,bool>mp;
    set<int>s;
    set<int>::iterator it;
    int n,ans=0;// 
    ll sq(int a){return (ll)a*a;}
    void chk(int p,int q){
        pii f=mkp(p,q);
        if(mp.find(f)!=mp.end()){return;}
        bool t=(sq(a[p].x-a[q].x)+sq(a[p].y-a[q].y)==sq(a[p].r+a[q].r));
        ans+=t;
        mp[f]=t;
    }
    void solve(){
        int tot=0,i,j,k;
        rd(n);
        for(i=1;i<=n;i++){
            rd(a[i].x),rd(a[i].y),rd(a[i].r);
            mp[mkp(a[i].x+a[i].r,a[i].y)]=true;
        }
        sort(a+1,a+1+n);
        for(i=1;i<=n;i++){//先要考虑两个圆竖直相切的情况> 
            if(mp.find(mkp(a[i].x-a[i].r,a[i].y))!=mp.end())ans++;
            b[++tot]=(node){a[i].x-a[i].r,i};
            b[++tot]=(node){a[i].x+a[i].r,-i};
        }
        mp.clear();
        sort(b+1,b+1+tot);
        int pre,nxt;
        for(i=1;i<=tot;i++){
            int id=b[i].id,pre=-1,nxt=-1;
            if(id>0){//找到前驱后继  
                it=s.upper_bound(id);//
                if(it!=s.end()){
                    nxt=*it;
                }
                if(it!=s.begin())pre=*(--it);
                if(~pre)chk(pre,id);
                if(~nxt)chk(id,nxt);
                s.insert(id);
            }
            else{//删除操作 
                it=s.find(-id);it++;
                if(it!=s.end())nxt=*it;
                it--;
                if(it!=s.begin())pre=*(--it);
                if(~pre&&~nxt)chk(pre,nxt);
                s.erase(s.find(-id));
    //            printf("%d %d %d %d
    ",id,pre,nxt,ans);
            }
        }
        printf("%d
    ",ans);
    }
    int main(){
    //    freopen("bubble.in","r",stdin);
        solve();
        return 0;
    }
    View Code
  • 相关阅读:
    【MySQL】MySQL如何高效地归档数据
    【MySQL】MySQL导出用户权限信息
    【SQLServer】并行执行计划中的分支和线程
    【MySQL】加速MySQL中大表的删除
    SaToken一个轻量级 java 权限认证框架,让鉴权变得简单、优雅!
    Windows版Redis7.04
    使用 Redis 源码编译发布 Windows 版 Redis For Windows 发行包
    【转】实战!工作中常用到哪些设计模式
    logback日志实践相关
    vue通过代理devServer 解决跨域问题 与 配置代理后打包部署报错404的解决方法
  • 原文地址:https://www.cnblogs.com/Lay-with-me/p/6733273.html
Copyright © 2020-2023  润新知