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\n",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\n",ans);
    for(i=1;i<=n;i++){
        for(j=1;j<=n;j++){
            sc(res[i][j]);
            putchar(" \n"[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\n",ans);
    while(BFS())ans-=dfs(st,oo);
//  printf("%d\n",ans);
    printf("%d\n",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('\n');
    }
}
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\n",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\n",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\n",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("\nval %d \n",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\n",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('\n');
}
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\n",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('\n');
}
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('\n');
}
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(" \n"[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\n",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('\n');
}
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\n",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('\n');
}
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('\n');
}
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\n",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\n",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\n",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\n",id,pre,nxt,ans);
        }
    }
    printf("%d\n",ans);
}
int main(){
//    freopen("bubble.in","r",stdin);
    solve();
    return 0;
}
View Code

 

posted @ 2017-04-19 14:19  LIN452  阅读(33)  评论(0编辑  收藏  举报