Loading

6.28 模拟赛解题报告

写在前面

这是一套 2014 年就编辑完成的 NOIP 2021 模拟赛题。

这波思维直接领先一个世纪。

预期:100 + 100 + 20 = 220

实际:100 + 0 + 20 = 120

T2 想出正解来但是出了点小错误,不能说是挂分吧,是我这里本来就想错了。
想对了但没有完全对
而且因为想错了,结果造出来的样例也是错的(

T3 部分分和没给一样,暴力没分。

\[\]

T1

给定一棵 \(n\) 个节点树以及 \(m\) 次询问,每次询问 \(x,y\),输出 \(x\)\(y\) 之间的祖孙关系。
\(n,m\le 40,000\)

先对这棵树 dfs 一下,求出他们的 \(dfn\)

节点 \(a\) 是节点 \(b\) 的祖先,当且仅当 \(dfn_a\le dfn_b < dfn_a+siz_a\)

然后就做完了。

void dfs(int u,int fat){
  siz[u]=1;fa[u]=fat;dfn[u]=++cnt;
  for(int i=head[u];i;i=e[i].nxt){
    int to=e[i].to;
    if(to==fat) continue;
    dfs(to,u);siz[u]+=siz[to];
  }
}

int main(){
  n=read();
  for(int i=1,fr,to;i<=n;i++){
    fr=read();to=read();
    if(to==-1) rt=fr;
    else add(fr,to),add(to,fr);
  }
  dfs(rt,0);m=read();
  for(int i=1,fr,to;i<=m;i++){
    fr=read();to=read();
    if(dfn[to]>=dfn[fr]&&dfn[to]<=dfn[fr]+siz[fr]-1) printf("1\n");
    else if(dfn[fr]>=dfn[to]&&dfn[fr]<=dfn[to]+siz[to]-1) printf("2\n");
    else printf("0\n");
  }
  return 0;
}

\[\]

T2

\(A,B\) 两支队伍比赛,每支队伍有 \(n\) 个人,每个人有各自的权值。
当权值为 \(x\) 的人与权值为 \(y\) 的人比赛时,权值更高的一方的队伍分数增加 \((x-y)^2\)
\(A\) 队期望比 \(B\) 队高多少分。
\(n,A_i,B_i\le 50,000\)

朴素的想法是 \(O(n^2)\) 暴力,只能拿 30 分。考虑如何优化。

要求期望,先算总权值。

我们发现他的权值的计算方式 \((x-y)^2=x^2+y^2-2xy\)

对于一个权值为 \(y\)\(A\) 队的人,若权值比他高的人权值分别为 \(x_1,x_2,x_3\cdots x_k\)。则 \(A\) 队可以获得 \(ky^2+x_1^2+x_2^2+\cdots+x_k^2-2y(x_1+x_2+\cdots+x_k)\) 的权值。

如果有若干个权值比他小的人就将上述式子取个相反数即可。

然后我们发现其中的和与平方和在数列排完序后都可以做前缀和维护,然后按上述式子计算权值即可。

(以上都没有问题,我写的挺对的)

我们都知道期望等于权值除以概率。

对于任意的两个人,他们匹配到的概率是 \(\Large{\frac{1}{n}}\),因为一共有 \(n!\) 种情况,其中任意两个人有 \((n-1)!\) 种情况时匹配到一起的,所以概率为 \(\Large{\frac{n!}{(n-1)!}}\)\(=\)\(\Large{\frac{1}{n}}\)

结果我只算出来 \(n!\) 情况没有考虑重复,就把权值除以了 \(n!\)

另外,最好先用 long long 类型统计权值,最后强制类型转换一下,否则会出精度问题。

如果有大样例的话,上面两个错误就都能避免了。

要怪就怪 \(2!=2\)

signed main(){
  n=read();
  for(int i=1;i<=n;i++) scanf("%lld",&A[i]);
  for(int i=1;i<=n;i++) scanf("%lld",&B[i]);
  sort(A+1,A+n+1);sort(B+1,B+n+1);
  for(int i=1;i<=n;i++)sumA[i]=sumA[i-1]+A[i]*A[i],SUMA[i]=SUMA[i-1]+A[i];
  for(int i=1;i<=n;i++)sumB[i]=sumB[i-1]+B[i]*B[i],SUMB[i]=SUMB[i-1]+B[i];
  for(int i=1;i<=n;i++){
    int id=lower_bound(B+1,B+n+1,A[i])-B;
    Ans-=2*A[i]*(SUMB[id-1]);
    Ans+=2*A[i]*(SUMB[n]-SUMB[id-1]);
    Ans+=A[i]*A[i]*(id-1)+(sumB[id-1]);
    Ans-=A[i]*A[i]*(n-id+1)+(sumB[n]-sumB[id-1]);
  }
  printf("%.1lf",1.0*(double)Ans/n);
  return 0;
}

\[\]

T3

一个数字被称为好数字当他满足下列条件:

  1. 它有 \(2\times n\) 个数位,\(n\) 是正整数(允许有前导 \(0\));
  2. 构成它的每个数字都在给定的数字集合 \(S\) 中;
  3. 它前 \(n\) 位之和与后 \(n\) 位之和相等或者它奇数位之和与偶数位之和相等。
    给定 \(n,S\),求好数字的个数 \(\bmod\) \(999983\)
    \(n\le 1000\)

前二十分 \(n\le 7\),也就是求 \([0,10^{2\times n + 1})\) 之内的好数字,最高能到 \(10^{15}\)

我暴力循环其中的每个数字然后判断。估计可想而知会 TLE。但是数据太水过了。

我回去直接吹我 \(O(nk)\) 过一百万亿(

下面说正解:

首先,我们可以只填前 \(n\) 位,然后统计每种总和的出现次数,再取平方和,我们设其为 \(x\)

根据乘法原理,此时满足前 \(n\) 位和等于后 \(n\) 位和这一条件。

满足奇数位和等于偶数位和这一条件的数量与上面是一样的,因为我们可以把奇数位看作前 \(n\) 位,偶数位看作后 \(n\) 位。

但是答案并不是 \(2x\),因为有的数字同时满足这两个条件,被统计了两遍,所以要减去重复的。

接下来就计算重复的。

\(f_{i,j}\) 表示已经填了 \(i\) 位,此时数位总和为 \(j\) 的方案数。

\(k=\Large{\frac{n}{2}}\) 为前 \(n\) 位中的偶数位数,\(x\) 为前 \(n\) 位中的偶数位和,\(s\) 为前 \(n\) 位总和。

所以在前 \(n\) 位中,只算偶数位的方案数为 \(f_{k,x}\),只算奇数位的方案数为 \(f_{n-k,s-x}\)

根据条件 3 我们可以发现,前 \(n\) 位奇数和等于后 \(n\) 位偶数和,前 \(n\) 位偶数和等于后 \(n\) 位奇数和。

所以在后 \(n\) 位中,只算偶数位的方案数为 \(f_{n-k,s-x}\),只算奇数位的方案数为 \(f_{k,x}\)

所以重复的答案为 \(\sum\limits_{s=0}^M\sum\limits_{x=0}^s f_{k,x}^2\times f_{n-k,s-x}^2\),减去即可。其中 \(M\) 是数位总和的上限。

然后我们发现,前 \(n\) 个数奇数位和只与后 \(n\) 个数偶数位和有关,与另外两个无关。另外两个也与这两个无关。

所以考虑先单独算这两部分再乘起来。

此时可算出重复的答案为 \(\sum\limits_{x=0}^{M_{1}} f_{k,x}\times \sum\limits_{y=0}^{M_2} f_{n-k,y}\),其中 \(M_1\) 为前 \(n\) 位中偶数位和,\(M_2\) 为前 \(n\) 位中奇数位和。时间花费是前面的一半。

signed main(){
  n=read();f[0][0]=1;
  scanf("%s",s+1);int len=strlen(s+1);
  for(int i=1;i<=len;i++) 
    M=max(M,(int)(s[i]-'0')),f[1][s[i]-'0']=vis[s[i]-'0']=1;
  for(int i=1;i<n;i++)
    for(int j=0;j<=M*i;j++)
      for(int k=0;k<=M;k++){
        if(!vis[k]) continue;
        f[i+1][j+k]=(f[i+1][j+k]+f[i][j])%Mod;
      }
  for(int i=0;i<=M*n;i++)
    Ans=(Ans+2*f[n][i]*f[n][i]%Mod)%Mod;
  int os=n>>1,js=n-os,A=0,B=0;
  for(int i=0;i<=M*os;i++)
    A=(A+f[os][i]*f[os][i]%Mod)%Mod;
  for(int i=0;i<=M*js;i++)
    B=(B+f[js][i]*f[js][i]%Mod)%Mod;
  printf("%lld\n",(Ans-A*B%Mod+Mod)%Mod);
  return 0;
}

posted @ 2021-06-28 17:52  KnightL  阅读(89)  评论(0编辑  收藏  举报