【DFS序】【数学+前缀和】【DP+容斥原理】Day 8.26

第一次用LaTeX

T1

判断树上两节点间的父子关系

这道题恰好是我以前给自己出的题,没想到这次居然真的碰上了

大部分人都用LCA来做,但是我有更好的方法$O(n)$

DFS的时候标记每个节点的DFS序$dfn[i]$,再标记该节点的子树中所有点的DFS序最大值$sub[i]$

如果$dfn[y]<dfn[x]\leq sub[y]$,那么说明x在y的子树中

 1 #include <cstdio>
 2 struct E{
 3     int to,next;
 4 }e[2*40001];
 5 int x,y,n,m,sz=0,head[40001],dfn[40001],sub[40001],root,dnow=1;
 6 void insert(int x,int y)
 7 {
 8     sz++;
 9     e[sz].next=head[x];
10     head[x]=sz;
11     e[sz].to=y;
12 }
13 int dfs(int x,int f)
14 {
15     dfn[x]=dnow;
16     for (int i=head[x];i;i=e[i].next)
17         if (e[i].to!=f) dnow++,dfs(e[i].to,x);
18     return sub[x]=dnow;
19 }
20 int main()
21 {
22     scanf("%d",&n);
23     for (int i=1;i<=n;i++)
24     {
25         scanf("%d%d",&x,&y);
26         if (y==-1)
27         {
28             root=x;
29             continue;
30         }
31         insert(x,y);
32         insert(y,x);
33     }
34     dfs(root,root);
35     scanf("%d",&m);
36     for (int i=1;i<=m;i++)
37     {
38         scanf("%d%d",&x,&y);
39         if (dfn[x]<dfn[y]&&dfn[y]<=sub[x])
40         {
41             printf("1\n");
42             continue;
43         }
44         if (dfn[y]<dfn[x]&&dfn[x]<=sub[y])
45         {
46             printf("2\n");
47             continue;
48         }
49         printf("0\n");
50     }
51     return 0;
52 }

 

T2

A,B两组各有n个人,他们的实力值分别为$a_i$、$b_j$,两组之间随机比赛,每次比赛实力值大的一方得$(a_i-b_j)^2$,求a组得分减b组得分的数学期望,保留一位小数

根据题意,答案的表达式大概长这个样子:

$$\frac{\sum_{i=1}^{n}\sum_{j=1}^{n}\begin{cases}(a_i-b_j)^2&{a_i\geq b_j}\\-(a_i-b_j)^2&{a_i<b_j}\end{cases}}{n}$$

只观察分子,变形得到

$$\sum_{i=1}^{n}\sum_{j=1}^{n}\begin{cases}(a_i^2-2a_ib_j+b_j^2)&{a_i\geq b_j}\\-(a_i^2-2a_ib_j+b_j^2)&{a_i<b_j}\end{cases}$$

注意公式里面的$a_i$,$b_j$能被提出来,三项可以分开来求,里面的循环可以直接预处理

由于比分的正负性,我们需要快速把A中第i个人的和B组的所有比赛分成$a_i<b_j$和$a_i\geq b_j$两部分,我们可以在B中按实力值$b_i$维护$b_i$和$b_i^2$的前缀和,这样就可以在$O(1)$的时间内完成这两部分比赛的得分统计了

遍历A中元素时只需要把$a_i\geq b_j$比赛的部分取正(用前缀和求),$a_i<b_j$比赛的部分取负(用总和减前缀和),再把结果求和再除以n就行了

最后记得分子要先*10再/n放在long long里,再在/10和%10的部分中间添上小数点

但是我忘记四舍五入了,丢了50分

 1 #include <cstdio>
 2 #include <iostream>
 3 #include <algorithm>
 4 #define ll long long
 5 using namespace std;
 6 ll n,a[50001],b[50001],preb[50001],preb2[50001],precnt[50001],a2,bsum,b2sum,ans;
 7 int main()
 8 {
 9     ios::sync_with_stdio(false);
10     cin>>n;
11     for (int i=1;i<=n;i++) cin>>a[i];
12     for (int i=1;i<=n;i++) cin>>b[i];
13     sort(a+1,a+n+1);
14     sort(b+1,b+n+1);
15     for (int i=1;i<=n;i++)
16     {
17         precnt[b[i]]++;
18         preb[b[i]]+=b[i];
19         preb2[b[i]]+=b[i]*b[i];
20     }
21     for (int i=1;i<=50000;i++)
22     {
23         precnt[i]+=precnt[i-1];
24         preb[i]+=preb[i-1];
25         preb2[i]+=preb2[i-1];
26     }
27     bsum=preb[50000];
28     b2sum=preb2[50000];
29     for (int i=1;i<=n;i++)
30     {
31         ans+=(a[i]*a[i]*(2*precnt[a[i]]-n)-(ll)2*a[i]*(2*preb[a[i]]-bsum)+2*preb2[a[i]]-b2sum);
32     }
33     ans=ans*10/n;
34     cout<<ans/10<<"."<<ans%10;
35 }

 

T3

    1. 它有2*n个数位,n是正整数(允许有前导0)

    2. 构成它的每个数字都在给定的数字集合S中。

    3. 它前n位之和与后n位之和相等或者它奇数位之和与偶数位之和相等

求合法的方案数mod 999983

 

好像可以先用DP求出来每种数字之和所对应的方案数,再用容斥原理求两种情况的并集,不过没时间做了

今天看了一下题解,和上面我写的的思路大概一样,不过求两种情况交集的地方还是需要认真考虑

先用DP求出$f[i][j]$表示i个数的和为j的方案数,因为每种情况都可以把长度为2*n的序列拆成两个长度为n的子序列分开统计方案数,再利用乘法原理求出组合成长度为2*n的序列的总方案数为$\sum_{i=0}^{maxs*n}f[n][i]^2$

为了求出两种情况的交集,我们需要具体分析一下满足什么条件的时候会重复统计,为了同时展开两种情况,我们可以先考虑2*a=8的情况

$$\begin{cases}a1+a2+a3+a4=a5+a6+a7+a8\\a1+a3+a5+a7=a2+a4+a6+a8\end{cases}$$

$$\begin{cases}a1+a3=a6+a8\\a2+a4=a5+a7\end{cases}$$

但是还要考虑奇数的情况

$$\begin{cases}a1+a2+a3=a4+a5+a6\\a1+a3+a5=a2+a4+a6\end{cases}$$

$$\begin{cases}a1+a3=a4+a6\\a2=a5\end{cases}$$

所以a1,a3,a4,a6和a2,a5又分别构成了两个第一种情况所述的序列,方案数为$\sum_{j=0}^{maxs*n/2}f[n/2][j]^2$和$\sum_{k=0}^{maxs*(n+1)/2}f[(n+1)/2][k]^2$,又因为两个序列相互独立,所以用乘法组合起来,得到交集的方案数是$\sum_{j=0}^{maxs*n/2}f[n/2][j]^2*\sum_{k=0}^{maxs*(n+1)/2}f[(n+1)/2][k]^2$

所以答案为两种方案的方案数和-两种方案的并集=$2*\sum_{i=0}^{maxs*n}f[n][i]^2-\sum_{j=0}^{maxs*n/2}f[n/2][j]^2*\sum_{k=0}^{maxs*(n+1)/2}f[(n+1)/2][k]^2$

转载于:https://www.cnblogs.com/algonote/p/7436010.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值