SDOI2016 round1

Day1

T1 储能表

题目大意:已知n、m、k,求sigma(i=0~n-1,j=0~m-1)max((i^j)-k,0)。

思路:数位dp。fi[i][a][b][c]表示到第i位,和n、m的关系分别是a、b(0表示<,1表示=),和k的关系(0表示<,1表示=,2表示>)。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
#define N 100
using namespace std;
LL n,m,k,p,fi[N][2][2][3],gi[N][2][2][3],c1,c2;
int nn,ni[N],mi[N],ki[N];
void pre(){
    int i,a,b,c;LL x;
    memset(ni,0,sizeof(ni));
    memset(mi,0,sizeof(mi));
    memset(ki,0,sizeof(ki));
    for (i=0;i<N;++i)
      for (a=0;a<2;++a)
          for (b=0;b<2;++b)
            for (c=0;c<3;++c) fi[i][a][b][c]=gi[i][a][b][c]=0LL;
    for (x=n;x;x/=2LL) ni[++ni[0]]=x&1LL;
    for (x=m;x;x/=2LL) mi[++mi[0]]=x&1LL;
    for (x=k;x;x/=2LL) ki[++ki[0]]=x&1LL;
    nn=max(ni[0],max(mi[0],ki[0]));
    for (i=1;i<=nn/2;++i){
        swap(ni[i],ni[nn-i+1]);
        swap(mi[i],mi[nn-i+1]);
        swap(ki[i],ki[nn-i+1]);
    }
}
void add(LL &x,LL y){x=(x+y)%p;}
void work(){
    int i,a,b,c,x,y,z,aa,bb,cc;
    c1=c2=0LL;
    gi[0][1][1][1]=1LL;
    for (i=0;i<nn;++i)
      for (a=0;a<2;++a)
        for (b=0;b<2;++b)
          for (c=0;c<3;++c){
              if (gi[i][a][b][c]==0LL&&fi[i][a][b][c]==0LL) continue;
              for (x=0;x<2;++x){
                if (a){
                    if (x>ni[i+1]) continue;
                    if (x==ni[i+1]) aa=1;
                    else aa=0;
                }else aa=0;
              for (y=0;y<2;++y){
                  if (b){
                    if (y>mi[i+1]) continue;
                    if (y==mi[i+1]) bb=1;
                    else bb=0;
                  }else bb=0;
                  z=x^y;
                  if (c==0) cc=0;
                  if (c==1){
                    if (z>ki[i+1]) cc=2;
                    if (z==ki[i+1]) cc=1;
                    if (z<ki[i+1]) cc=0;
                  }if (c==2) cc=2;
                  add(gi[i+1][aa][bb][cc],gi[i][a][b][c]);
                  add(fi[i+1][aa][bb][cc],(fi[i][a][b][c]*2LL+(LL)z*gi[i][a][b][c])%p);
                }
              }
          }
    c1=fi[nn][0][0][2];
    c2=gi[nn][0][0][2];
}
int main(){
    freopen("table.in","r",stdin);
    freopen("table.out","w",stdout);
    
    int t;LL ans;
    scanf("%d",&t);
    while(t--){
        scanf("%I64d%I64d%I64d%I64d",&n,&m,&k,&p);
        pre();work();
        ans=((c1-k%p*c2%p)%p+p)%p;
        printf("%I64d\n",ans);
    }
}
View Code

 

T2 数字配对

题目大意:n种数字,ai这种有bi个,权值ci。如果aj|ai并且ai/aj是质数就可以配对,每一对贡献ci*cj,问贡献不小于0的前提下最多的配对数。

思路:先判断出所有的配对关系,然后二分+费用流判断。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 500
#define M 1000005
#define len 1000000
#define inf 1000000000
#define LL long long
using namespace std;
struct use{int u,v,va;LL co;}ed[M];
int point[N],next[M],que[len+1],ai[N],bi[N],aa[N],az=0,n,ss,tt,tot,flow,a[N],pre[N];
LL dis[N],ci[N],cost;
bool ct[N][N],vi[N]={false};
int ab(int x){return (x<0 ? -x : x);}
bool prej(int x,int y){
    if (ab(y)%ab(x)||!x) return false;
    int i;y/=x;
    if (y<=1) return false;
    for (i=2;i*i<=y;++i)
        if (y%i==0) return false;
    return true;
}
void add(int u,int v,int vv,LL cc){
    next[++tot]=point[u];point[u]=tot;ed[tot]=(use){u,v,vv,cc};
    next[++tot]=point[v];point[v]=tot;ed[tot]=(use){v,u,0,-cc};
}
bool spfa(){
    int i,u,v,head,tail;
    head=tail=0;vi[que[++tail]=ss]=true;
    memset(dis,128,sizeof(dis));
    dis[ss]=0LL;a[ss]=inf;
    while(head!=tail){
        vi[u=que[head=head%len+1]]=false;
        for (i=point[u];i;i=next[i])
            if (ed[i].va&&dis[v=ed[i].v]<dis[u]+ed[i].co){
                dis[v]=dis[u]+ed[i].co;
                a[v]=min(a[u],ed[i].va);
                pre[v]=i;
                if (!vi[v]) vi[que[tail=tail%len+1]=v]=true;
            }
    }if (dis[tt]==dis[N-1]) return false;
    flow+=a[tt];cost+=(LL)a[tt]*dis[tt];
    for (i=tt;i!=ss;i=ed[pre[i]].u){
        ed[pre[i]].va-=a[tt];
        ed[pre[i]^1].va+=a[tt];
    }return true;
}
bool judge(int x){
    int i,j;ss=0;tt=2*n+3;tot=1;
    memset(point,0,sizeof(point));
    add(ss,ss+1,x<<1,0LL);
    add(tt-1,tt,x<<1,0LL);
    for (i=1;i<=n;++i)
        for (j=1;j<=n;++j)
            if (ct[ai[i]][ai[j]]||ct[ai[j]][ai[i]]) add(i+1,j+n+1,inf,ci[i]*ci[j]);
    for (i=1;i<=n;++i){
        add(ss+1,i+1,bi[i],0LL);
        add(i+n+1,tt-1,bi[i],0LL);
    }flow=0;cost=0LL;
    while(spfa());
    if (flow==x*2&&cost>=0LL) return true;
    return false;
}
int main(){
    freopen("pair.in","r",stdin);
    freopen("pair.out","w",stdout);
    
    int i,j,l=0,r=0,mid,ans=0;
    scanf("%d",&n);
    for (i=1;i<=n;++i){
        scanf("%d",&ai[i]);
        aa[++az]=ai[i];
    }for (i=1;i<=n;++i){scanf("%d",&bi[i]);r+=bi[i];}
    for (i=1;i<=n;++i) scanf("%I64d",&ci[i]);
    sort(aa+1,aa+az+1);
    az=unique(aa+1,aa+az+1)-aa-1;
    memset(ct,false,sizeof(ct));
    for (i=1;i<=az;++i){
          for (j=1;j<=az;++j)
              if (prej(aa[i],aa[j])) ct[i][j]=true;
    }for (i=1;i<=n;++i) ai[i]=upper_bound(aa+1,aa+az+1,ai[i])-aa-1;
    while(l<=r){
        mid=(l+r)>>1;
        if (judge(mid)){l=mid+1;ans=mid;}
        else r=mid-1;
    }printf("%d\n",ans);
}
View Code

(其实可以费用流的时候直接看费用和0的关系,因为费用流是贪心选费用最大的路先走)

 

T3 游戏(!!!

题目大意:一棵树,两种操作:(1)对s~t路径上的点i上放一个权值dis(s,i)*a+b;(2)查询s~t路径上点上的最小权值。

思路:相当于维护每个点的最小值,考场上只写了35分暴力。正解是nlog^3n链剖。对于每次放点的操作相当于在线段树的区间里加上一个一次函数,修改的时候暴力下放(线段树维护区间最小值、一次函数和有没有一次函数。每次修改只会改一个区间的左右儿子中的一个,因为如果这个一次函数和之前的相交了,如果新的一次函数在左和右的一部分最优,可以通过交换新函数和旧函数使得只修改右儿子的另一部分,所以线段树的复杂度是log^2n)。在链剖上对lca之前之后的分别处理一下。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100005
#define up 20
#define LL long long
#define inf 123456789123456789LL
using namespace std;
struct line{LL a,b;};
struct use{LL mn;line x;bool ex;}tr[N<<2];
int point[N],next[N<<1],en[N<<1],id[N],dep[N]={0},dt=0,n,tot=0;
LL va[N<<1],dis[N]={0LL};
bool vi[N]={false};
int in(){
    char ch=getchar();int x=0,f=1;
    while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-'){ch=getchar();f=-1;}
    while(ch>='0'&&ch<='9'){
        x=x*10+ch-'0';ch=getchar();
    }return x*f;}
void add(int u,int v,LL w){
    next[++tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=w;
    next[++tot]=point[v];point[v]=tot;en[tot]=u;va[tot]=w;}
void build(int i,int l,int r){
    tr[i]=(use){inf,(line){0,0},false};
    if (l==r) return;
    int mid=(l+r)>>1;
    build(i<<1,l,mid);build(i<<1|1,mid+1,r);}
LL getf(LL x,line y){return y.a*x+y.b;}
void ins(int i,int l,int r,int ll,int rr,line x){
    if (ll<=l&&r<=rr){
        tr[i].mn=min(tr[i].mn,min(getf(dis[l],x),getf(dis[r],x)));
        if (!tr[i].ex){
            tr[i].ex=true;
            tr[i].x=x;return;
        }if (l==r){
            if (getf(dis[l],x)<getf(dis[l],tr[i].x)) tr[i].x=x;
            return;
        }int a,b,c,mid=(l+r)>>1;
        a=(getf(dis[l],x)<getf(dis[l],tr[i].x));
        b=(getf(dis[mid],x)<getf(dis[mid],tr[i].x));
        c=(getf(dis[r],x)<getf(dis[r],tr[i].x));
        if (a&&c){tr[i].x=x;return;}
        if ((!a)&&(!c)) return;
        if (b){
            swap(tr[i].x,x);
            a^=1;c^=1;
        }if (a) ins(i<<1,l,mid,ll,rr,x);
        if (c) ins(i<<1|1,mid+1,r,ll,rr,x);
        return;
    }int mid=(l+r)>>1;
    if (ll<=mid) ins(i<<1,l,mid,ll,rr,x);
    if (rr>mid) ins(i<<1|1,mid+1,r,ll,rr,x);
    tr[i].mn=min(tr[i].mn,min(tr[i<<1].mn,tr[i<<1|1].mn));
}
LL query(int i,int l,int r,int ll,int rr){
    if (ll<=l&&r<=rr) return tr[i].mn;
    LL mn=inf;int mid=(l+r)>>1;
    if (tr[i].ex) mn=min(mn,min(getf(dis[max(l,ll)],tr[i].x),getf(dis[min(r,rr)],tr[i].x)));
    if (ll<=mid) mn=min(mn,query(i<<1,l,mid,ll,rr));
    if (rr>mid) mn=min(mn,query(i<<1|1,mid+1,r,ll,rr));
    return mn;}
struct lp{
    int top[N],tid[N],fa[N][up],son[N],siz[N];
    LL dd[N];
    void dfs1(int u,int f){
        int i,v,mz=0;siz[u]=1;fa[u][0]=f;
        son[u]=0;dep[u]=dep[f]+1;
        for (i=1;i<up;++i) fa[u][i]=fa[fa[u][i-1]][i-1];
        for (i=point[u];i;i=next[i]){
            if ((v=en[i])==f) continue;
            dd[v]=dd[u]+va[i];
            dfs1(v,u);siz[u]+=siz[v];
            if (siz[v]>mz){mz=siz[v];son[u]=v;}
        }
    }
    void dfs2(int u,int anc){
        int i,v;vi[u]=true;
        top[u]=anc;id[tid[u]=++dt]=u;
        dis[dt]=dd[u];
        if (son[u]) dfs2(son[u],anc);
        for (i=point[u];i;i=next[i]){
            if (vi[v=en[i]]) continue;
            dfs2(v,v);
        }
    }
    int lca(int u,int v){
        int i;if (dep[u]<dep[v]) swap(u,v);
        for (i=up-1;i>=0;--i)
            if (dep[fa[u][i]]>=dep[v]) u=fa[u][i];
        if (u==v) return u;
        for (i=up-1;i>=0;--i)
            if (fa[u][i]!=fa[v][i]){
                u=fa[u][i];v=fa[v][i];
            }
        return fa[u][0];}
    void tch(int u,int v,LL a,LL b){
        line ll[2];int cur=0,lc=lca(u,v);
        ll[0]=(line){-a,a*dd[u]+b};
        ll[1]=(line){a,a*(dd[u]-2LL*dd[lc])+b};
        while(top[u]!=top[v]){
            if (dep[top[u]]<dep[top[v]]){
                swap(u,v);cur^=1;
            }ins(1,1,n,tid[top[u]],tid[u],ll[cur]);
            u=fa[top[u]][0];
        }if (dep[u]>dep[v]) swap(u,v);
        else cur^=1;
        ins(1,1,n,tid[u],tid[v],ll[cur]);
    }
    LL ask(int u,int v){
        LL mn=inf;
        while(top[u]!=top[v]){
            if (dep[top[u]]<dep[top[v]]) swap(u,v);
            mn=min(mn,query(1,1,n,tid[top[u]],tid[u]));
            u=fa[top[u]][0];
        }if (dep[u]>dep[v]) swap(u,v);
        mn=min(mn,query(1,1,n,tid[u],tid[v]));
        return mn;
    }
}tree;
int main(){
    freopen("game.in","r",stdin);
    freopen("game.out","w",stdout);
    
    int m,i,u,v,op;LL a,b,w;
    n=in();m=in();
    for (i=1;i<n;++i){
        u=in();v=in();w=(LL)in();
        add(u,v,w);
    }tree.dd[0]=0LL;tree.dfs1(1,0);
    tree.dfs2(1,1);build(1,1,n);
    for (i=1;i<=m;++i){
        op=in();u=in();v=in();
        if (op==1){
            a=(LL)in();b=(LL)in();
            tree.tch(u,v,a,b);
        }else printf("%I64d\n",tree.ask(u,v));
    }
}
View Code

 

Day2

T1 生成魔咒(!!!

题目大意:给定一个字符串,每次在最后加入一个新数,问当前串的子串个数。

思路:如果给定一个字符串,子串个数是l*(l+1)/2-sigma(i=1~l-1)height[i]。现在要动态在最后加一个数,可以建原串倒着的后缀数组,每次相当于插入一个新的前缀,放在rank里面跟左右的更新一下答案。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100005
#define up 20
#define inf 2100000000
#define LL long long
using namespace std;
struct use{int mx,mn;}tr[N<<2];
int ss[N],ci[N],n,m,x1[N],x2[N],lo[N],sm[N][up],sa[N],rank[N],height[N];
bool cmp(int *y,int a,int b,int k){
    int aa,bb;
    aa=(a+k>=n ? -1 : y[a+k]);
    bb=(b+k>=n ? -1 : y[b+k]);
    a=y[a];b=y[b];
    return (a==b&&aa==bb);}
void buildsa(){
    int i,k,p;int *x=x1;int *y=x2;
    for (i=0;i<m;++i) ci[i]=0;
    for (i=0;i<n;++i) ++ci[x[i]=ss[i]];
    for (i=1;i<m;++i) ci[i]+=ci[i-1];
    for (i=n-1;i>=0;--i) sa[--ci[x[i]]]=i;
    for (k=1;k<=n;k<<=1){
        for (p=0,i=n-k;i<n;++i) y[p++]=i;
        for (i=0;i<n;++i) if (sa[i]>=k) y[p++]=sa[i]-k;
        for (i=0;i<m;++i) ci[i]=0;
        for (i=0;i<n;++i) ++ci[x[y[i]]];
        for (i=1;i<m;++i) ci[i]+=ci[i-1];
        for (i=n-1;i>=0;--i) sa[--ci[x[y[i]]]]=y[i];
        swap(x,y);m=1;x[sa[0]]=0;
        for (i=1;i<n;++i) x[sa[i]]=(cmp(y,sa[i],sa[i-1],k) ? m-1 : m++);
        if (m>=n) break;
    }
}
void pre(){
    int i,j,k=0;
    for (i=0;i<n;++i) rank[sa[i]]=i;
    for (i=0;i<n;++i){
        if (!rank[i]) continue;
        if (k) --k; j=sa[rank[i]-1];
        for (;ss[j+k]==ss[i+k];++k);
        height[rank[i]]=k;
    }memset(sm,127/3,sizeof(sm));
    for (i=0;i<n;++i) sm[i][0]=height[i];
    for (i=1;i<up;++i)
        for (j=0;j+(1<<(i-1))<n;++j)
            sm[j][i]=min(sm[j][i-1],sm[j+(1<<(i-1))][i-1]);
    for (j=0,i=1;i<=n;++i){
        if ((1<<(j+1))<=i) ++j;
        lo[i]=j;
    }
}
void build(int i,int l,int r){
    tr[i].mx=-inf;tr[i].mn=inf;
    if (l==r) return;
    int mid=(l+r)>>1;
    build(i<<1,l,mid);build(i<<1|1,mid+1,r);}
use updata(use x,use y){
    x.mx=max(x.mx,y.mx);
    x.mn=min(x.mn,y.mn);
    return x;}
use ask(int i,int l,int r,int ll,int rr){
    if (ll<=l&&r<=rr) return tr[i];
    int mid=(l+r)>>1;use x1,x2;
    bool f1,f2;f1=f2=false;
    if (ll<=mid){x1=ask(i<<1,l,mid,ll,rr);f1=true;}
    if (rr>mid){x2=ask(i<<1|1,mid+1,r,ll,rr);f2=true;}
    if (!f1) return x2;
    if (!f2) return x1;
    return updata(x1,x2);}
void ins(int i,int l,int r,int x){
    if (l==r){tr[i]=(use){l,l};return;}
    int mid=(l+r)>>1;
    if (x<=mid) ins(i<<1,l,mid,x);
    else ins(i<<1|1,mid+1,r,x);
    tr[i]=updata(tr[i<<1],tr[i<<1|1]);}
int lcp(int l,int r){
    int k=lo[r-l];++l;
    return min(sm[l][k],sm[r-(1<<k)+1][k]);
}
int main(){
    freopen("incantation.in","r",stdin);
    freopen("incantation.out","w",stdout);
    
    int i,lm,rm;LL ans=0LL;use cc;scanf("%d",&n);
    for (i=n-1;i>=0;--i){scanf("%d",&ss[i]);ci[i]=ss[i];}
    sort(ci+1,ci+n+1);m=unique(ci+1,ci+n+1)-ci-1;
    for (i=0;i<n;++i) ss[i]=upper_bound(ci+1,ci+m+1,ss[i])-ci-2;
    buildsa();pre();build(1,0,n-1);
    for (i=n-1;i>=0;--i){
        ans+=(LL)(n-i);
        if (rank[i]){
            cc=ask(1,0,n-1,0,rank[i]-1);
            lm=cc.mx;
        }else lm=-inf;
        if (rank[i]<n-1){
            cc=ask(1,0,n-1,rank[i]+1,n-1);
            rm=cc.mn;
        }else rm=inf;
        if (lm!=-inf) ans-=(LL)lcp(lm,rank[i]);
        if (rm!=inf) ans-=(LL)lcp(rank[i],rm);
        if (lm!=-inf&&rm!=inf) ans+=(LL)lcp(lm,rm);
        ins(1,0,n-1,rank[i]);
        printf("%I64d\n",ans);
    }
}
View Code

这题其实可以用后缀自动机暴力做,一个点对应的没出现过的子串个数是mx[i]-mx[fa[i]],插入的时候更新答案。

 

T2 排列计数

题目大意:求1~n的排列,其中恰好有m个位置上的数和位置一样。

思路:选出m个一样的,剩下的全都错排就可以了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 1000005
#define LL long long
#define p 1000000007LL
using namespace std;
LL fac[N],inv[N],gi[N];
LL mi(LL x,LL y){
    LL a=1LL;
    for (;y;y>>=1LL){
        if (y&1LL) a=a*x%p;
        x=x*x%p;
    }return a;}
LL getc(int n,int m){return fac[n]*inv[m]%p*inv[n-m]%p;}
int main(){
    freopen("permutation.in","r",stdin);
    freopen("permutation.out","w",stdout);
    
    int i,j,t,n,m;scanf("%d",&t);
    for (fac[0]=1LL,i=1;i<N;++i) fac[i]=fac[i-1]*(LL)i%p;
    inv[N-1]=mi(fac[N-1],p-2LL);
    for (i=N-2;i>=0;--i) inv[i]=inv[i+1]*(LL)(i+1)%p;
    gi[0]=1LL;gi[1]=0LL;gi[2]=1LL;
    for (i=3;i<N;++i) gi[i]=(gi[i-1]+gi[i-2])%p*(LL)(i-1)%p;
    while(t--){
        scanf("%d%d",&n,&m);
        if (m>n) printf("0\n");
        else printf("%I64d\n",getc(n,m)*gi[n-m]%p);
    }
}
View Code

正解是容斥(其实原题是m个位置上的数ai=i-1或i+1,这样似乎只能容斥?)

 

T3 征途

题目大意:给定n条线段,分成m份,使方差*m^2最小。

思路:方差*m^2的式子化完之后是m*(x1^2+x2^2+...+xm^2)-(sigma(i=1~n)ai)^2,之后的是定值,所以就是求平方和最小,可以斜率优化。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
#define N 3005
using namespace std;
LL fi[N][N],ai[N]={0LL};
int que[N][N],h[N],t[N];
LL sqr(LL x){return x*x;}
LL getup(int i,int k,int j){return (fi[i][k]+sqr(ai[k]))-(fi[i][j]+sqr(ai[j]));}
LL getdown(int k,int j){return ai[k]-ai[j];}
LL getdp(int i,int k,int j){return fi[i][k]+sqr(ai[j]-ai[k]);}
int main(){
    freopen("journey.in","r",stdin);
    freopen("journey.out","w",stdout);
    
    int n,m,i,j,k;
    scanf("%d%d",&n,&m);
    for (i=1;i<=n;++i){
        scanf("%I64d",&ai[i]);
        ai[i]+=ai[i-1];
    }memset(fi,127/3,sizeof(fi));
    fi[0][0]=0LL;que[0][h[0]=t[0]=1]=0;
    if (n<=100){
        for (i=1;i<=m;++i)
            for (j=1;j<=n;++j)
                for (k=0;k<=j;++k) fi[i][j]=min(fi[i][j],fi[i-1][k]+sqr(ai[j]-ai[k]));
    }else{
        for (i=1;i<=m;++i){
            fi[i][0]=0LL;
            que[i][h[i]=t[i]=1]=0;
            for (j=1;j<=n;++j){
                while(h[i-1]<t[i-1]&&getup(i-1,que[i-1][h[i-1]+1],que[i-1][h[i-1]])<getdown(que[i-1][h[i-1]+1],que[i-1][h[i-1]])*2LL*ai[j]) ++h[i-1];
                fi[i][j]=getdp(i-1,que[i-1][h[i-1]],j);
                while(h[i]<t[i]&&getup(i,j,que[i][t[i]])*getdown(que[i][t[i]],que[i][t[i]-1])<
                    getup(i,que[i][t[i]],que[i][t[i]-1])*getdown(j,que[i][t[i]])) --t[i];
                que[i][++t[i]]=j;
            }for (j=1;j<=n;++j) fi[i][j]=min(fi[i][j],fi[i-1][j]);
        }
    }printf("%I64d\n",fi[m][n]*(LL)m-sqr(ai[n]));
}
View Code

 

继续加油!!!

posted @ 2016-04-11 17:49  Rivendell  阅读(662)  评论(0编辑  收藏  举报