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);
}

  

posted @ 2018-04-17 23:26 Claris 阅读(...) 评论(...) 编辑 收藏