AMPPZ-2015 (MIPT Workshop Open 1)
A. Album of Numbers
设$cnt[i]$表示数字$i$的个数,则$ans=\frac{\sum_{i} i\times cnt[i]\prod_{j>i}(cnt[j]+1)}{\prod_{i}(cnt[i]+1)-1}$。
不妨忽略分母的减$1$,那么只需要维护出答案以及$cnt+1$的乘积即可得到最终答案。
用线段树维护每个区间内的答案以及$cnt+1$的乘积,注意到乘积很大时对答案的影响变化很小,所以可以直接对$10^{100}$取$\min$。
时间复杂度$O(n\log n)$。
#include<cstdio> #include<algorithm> using namespace std; const int N=555555; const double inf=1e100; int m,n,i,a[N],b[N];char op[N][9]; double s[N],p[N]; void build(int x,int a,int b){ p[x]=1; if(a==b)return; int mid=(a+b)>>1; build(x<<1,a,mid); build(x<<1|1,mid+1,b); } void change(int x,int a,int b,int c,int o){ if(a==b){ p[x]+=o; s[x]=::a[a]*(p[x]-1)/p[x]; return; } int mid=(a+b)>>1; if(c<=mid)change(x<<1,a,mid,c,o);else change(x<<1|1,mid+1,b,c,o); p[x]=min(p[x<<1]*p[x<<1|1],inf); s[x]=s[x<<1]+s[x<<1|1]/p[x<<1]; } int main(){ scanf("%d",&m); for(i=1;i<=m;i++){ scanf("%s%d",op[i],&b[i]); a[i]=b[i]; } sort(a+1,a+m+1); for(i=1;i<=m;i++)if(i==1||a[i]!=a[i-1])a[++n]=a[i]; build(1,1,n); for(i=1;i<=m;i++){ change(1,1,n,lower_bound(a+1,a+n+1,b[i])-a,op[i][0]=='+'?1:-1); printf("%.15f\n",s[1]*(1.0+1.0/(p[1]-1))); } }
B. Well Off
对$x[i]$和$-x[i]$建图,若$a+b>0$,则连边$a>-b$,$b>-a$,拓扑排序判断是否存在环。
#include<cstdio> const int N=200010,M=1000010; int n,m,i,g[N],v[M],nxt[M],ed,d[N],q[N],h,t; inline void add(int x,int y){v[++ed]=y;nxt[ed]=g[x];g[x]=ed;d[y]++;} inline int read(){ char s[9]; int x; scanf("%s%d",s,&x); x--; x<<=1; if(s[0]=='-')x++; return x; } int main(){ scanf("%d%d",&n,&m); while(m--){ int x=read(); int y=read(); add(x,y^1); add(y,x^1); } n*=2; for(h=1,i=0;i<n;i++)if(!d[i])q[++t]=i; while(h<=t){ int x=q[h++]; for(i=g[x];i;i=nxt[i])if(!(--d[v[i]]))q[++t]=v[i]; } puts(t<n?"NIE":"TAK"); }
C. Accurate Shots (8Mb TL!)
若$m>\sqrt{n}$,则可以直接枚举$m$的所有倍数暴力判断。
否则考虑折半爆搜,枚举左半边对$m$的余数,就知道右半边对$m$的余数,分别枚举出所有解即可。
时间复杂度$O(\sqrt{n})$。
D. Prom
排序后双指针。
#include<cstdio> #include<algorithm> using namespace std; const int N=250010; int n,m,lim,i,j,k,a[N],b[N];long long ans; int main(){ scanf("%d%d%d",&n,&m,&lim); for(i=1;i<=n;i++)scanf("%d",&a[i]); for(i=1;i<=m;i++)scanf("%d",&b[i]); sort(a+1,a+n+1); sort(b+1,b+m+1); for(i=j=k=1;i<=n;i++){ while(j<=m&&b[j]<=a[i]+lim)j++; while(k<=m&&b[k]<a[i]-lim)k++; ans+=j-k; } printf("%lld",ans); }
E. Impressive Graphs
在$O(nk\log n)$的时间里维护出杨氏图表的前$k$行,则最终个数即为前$k$行的元素个数。
输出方案部分留坑。
F. Pen
用栈进行括号匹配,栈为空时往左补充,最后将栈中未配对左括号配上右括号。
#include<cstdio> #include<cstring> #include<cstdlib> using namespace std; const int N=1000010; int n,i,t,cb;char a[N],q[N],b[N]; void NO(){ puts("NIE"); exit(0); } int main(){ scanf("%s",a+1); n=strlen(a+1); for(i=1;i<=n;i++){ if(a[i]=='('||a[i]=='{'||a[i]=='['){ q[++t]=a[i]; } if(a[i]==')'){ if(t){ if(q[t]=='(')t--; else NO(); }else{ b[++cb]='('; } } if(a[i]=='}'){ if(t){ if(q[t]=='{')t--; else NO(); }else{ b[++cb]='{'; } } if(a[i]==']'){ if(t){ if(q[t]=='[')t--; else NO(); }else{ b[++cb]='['; } } } for(i=cb;i;i--)putchar(b[i]); for(i=1;i<=n;i++)putchar(a[i]); for(i=t;i;i--){ if(q[i]=='(')putchar(')'); if(q[i]=='[')putchar(']'); if(q[i]=='{')putchar('}'); } }
G. Board Game
将颜色分治,每次对于两边的颜色分别求出凸包,然后双指针求闵可夫斯基和的凸包。
时间复杂度$O(n\log n)$。
H. Scouts
$f[i][j]$表示仅考虑$[i,j]$区间的答案,则
$f[i][j]=\min(\max(f[i][k-1],f[k+1][j])+a[k]),i\leq k\leq j$
维护出$\max$的分界点后用一堆线段树维护即可。
时间复杂度$O(n^2\log n)$。
#include<cstdio> typedef long long ll; const int N=2005; const ll inf=1LL<<60; int M,n,i,j,a[N],g; ll f[N][N]; inline void up(ll&a,ll b){if(a>b)a=b;} struct ZKW{ ll v[4100]; inline void ask(int x,int y,ll&t){ if(x>y)return; for(x+=M-1,y+=M+1;x^y^1;x>>=1,y>>=1){ if(~x&1)up(t,v[x^1]); if(y&1)up(t,v[y^1]); } } inline void ins(int x,ll y){for(x+=M;x;x>>=1)up(v[x],y);} inline void build(){for(int i=1;i<=n+M;i++)v[i]=inf;} }T1[N],T2[N]; inline void add(int x,int y){ if(x>1)T1[y].ins(x-1,f[x][y]+a[x-1]); if(y<n)T2[x].ins(y+1,f[x][y]+a[y+1]); } int main(){ scanf("%d",&n); for(M=1;M<n+2;M<<=1); for(i=1;i<=n;i++)scanf("%d",&a[i]),T1[i].build(),T2[i].build(); for(i=1;i<=n;i++)add(i,i-1); for(i=1;i<=n;i++)f[i][i]=a[i],add(i,i); for(i=n;i;i--)for(g=i,j=i+1;j<=n;j++){ while(g<=j&&f[i][g-1]<f[g+1][j])g++; f[i][j]=inf; T1[j].ask(i,g-1,f[i][j]); T2[i].ask(g,j,f[i][j]); add(i,j); } printf("%lld",f[1][n]); }
I. Insects
最大权闭合子图。
#include<cstdio> const int N=2010,inf=1e9; struct E{int t,f;E*nxt,*pair;}*g[N],*d[N],pool[500000],*cur=pool; int n,m,i,j,k,x,y,S,T,h[N],gap[N],ans; inline void add(int s,int t,int f){ E*p=cur++;p->t=t;p->f=f;p->nxt=g[s];g[s]=p; p=cur++;p->t=s;p->f=0;p->nxt=g[t];g[t]=p; g[s]->pair=g[t];g[t]->pair=g[s]; } inline int min(int a,int b){return a<b?a:b;} int sap(int v,int flow){ if(v==T)return flow; int rec=0; for(E*p=d[v];p;p=p->nxt)if(h[v]==h[p->t]+1&&p->f){ int ret=sap(p->t,min(p->f,flow-rec)); p->f-=ret;p->pair->f+=ret;d[v]=p; if((rec+=ret)==flow)return flow; } if(!(--gap[h[v]]))h[S]=T; gap[++h[v]]++; d[v]=g[v]; return rec; } int main(){ int _p,_ca,_ck,_ct; scanf("%d%d%d%d%d",&n,&_p,&_ca,&_ck,&_ct); S=256*3+n+1; T=S+1; ans=_p*n; for(i=1;i<=n;i++){ add(S,i,_p); int x,y,z; scanf("%d%d%d",&x,&y,&z);x++,y++,z++; add(i,x+n,inf); add(i,y+n+256,inf); add(i,z+n+512,inf); } for(i=1;i<=256;i++){ add(i+n,T,_ca); add(i+n+256,T,_ck); add(i+n+512,T,_ct); } for(gap[0]=T,i=1;i<=T;i++)d[i]=g[i]; while(h[S]<T)ans-=sap(S,inf); printf("%d",ans); }
J. Caves
设$f[S][i]$表示走过了$S$集合,目前位于$i$的最优期望花费,转移带环,但是可以将不带环的部分的DP值排序后计算环的代价。
时间复杂度$O(2^nn^2)$。
K. Blocks
从$n$到$1$填数,那么若放在两侧则能被看到,否则看不到。
设$f[i][j]$表示$i$个数从两侧总计能看到$j$个的方案数,可以预处理,则
$ans=f[n][l+p-1]C(l+p-2,l-1)$
时间复杂度$O(n(l+p)+m)$。
#include<cstdio> const int N=50010,K=210,P=1000000007; int Case,n,A,B,i,j,f[N][K+5],ans,C[K][K]; inline void up(int&a,int b){a=a+b<P?a+b:a+b-P;} int main(){ f[1][1]=1; for(i=1;i<N;i++)for(j=1;j<K;j++)if(f[i][j]){ //inside up(f[i+1][j],1LL*(i-1)*f[i][j]%P); //outside up(f[i+1][j+1],f[i][j]); } for(C[0][0]=i=1;i<K;i++)for(C[i][0]=j=1;j<=i;j++)C[i][j]=(C[i-1][j-1]+C[i-1][j])%P; scanf("%d",&Case); while(Case--){ scanf("%d%d%d",&n,&A,&B); ans=1LL*f[n][A+B-1]*C[A+B-2][A-1]%P; printf("%d\n",ans); } }
L. Postman
固定起点之后按DFS序走最优,两遍树形DP求出每个点作为起点的答案即可。
时间复杂度$O(n)$。
#include<cstdio> typedef long long ll; const int N=1000010; int n,i,x,y,g[N],v[N<<1],nxt[N<<1],ed,size[N]; ll f[N],h[N],s[N],ans; inline void add(int x,int y){v[++ed]=y;nxt[ed]=g[x];g[x]=ed;} void dfs(int x,int y){ for(int i=g[x];i;i=nxt[i]){ int u=v[i]; if(u==y)continue; dfs(u,x); f[x]+=f[u]+1LL*size[u]*(size[x]*2+1); size[x]+=size[u]; } size[x]++; } void dfs2(int x,int y){ if(y){ h[x]=s[y]-f[x]-1LL*size[x]*((n-size[x]-1)*2+1); h[x]+=n-size[x]; } s[x]=f[x]+h[x]+1LL*(n-size[x])*(size[x]-1)*2; if(s[x]<ans)ans=s[x]; for(int i=g[x];i;i=nxt[i]){ int u=v[i]; if(u==y)continue; dfs2(u,x); } } int main(){ scanf("%d",&n); for(i=1;i<n;i++)scanf("%d%d",&x,&y),add(x,y),add(y,x); dfs(1,0); ans=f[1]; dfs2(1,0); printf("%lld",ans); }