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); } }
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); }
(其实可以费用流的时候直接看费用和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)); } }
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); } }
这题其实可以用后缀自动机暴力做,一个点对应的没出现过的子串个数是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); } }
正解是容斥(其实原题是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])); }
继续加油!!!