\(\cal T_1\) 基础概率练习题 / probability

Description

对于一个长度为 \(n\) 的非负整数序列 \(a_{1}, a_{2}, \ldots, a_{n}\),已知 \(a_{1} \geqslant k, \sum_{i=1}^{n} a_{i}=m\),询问 \(a_{1}\) 是最大值的概率,如果序列中有多个最大值,随机任意一个作为最大值。对 \(998244353\) 取模。

\[\begin{array}{|c|c|c|c|c|} \hline 子任务编号 & n & m & k & 分数 \\ \hline 1 & & & =0 & 5 \\ \hline 2 & \leqslant 10 & \leqslant 10 & & 5 \\ \hline 3 & \leqslant 100 & \leqslant 5000 & & 20 \\ \hline 4 & \leqslant 5000 & \leqslant 5000 & & 20 \\ \hline 5 & \leqslant 5000 & & & 20 \\ \hline 6 & & & & 30 \\ \hline \end{array} \]

对于所有数据,\(1 \leqslant n, m, k \leqslant 10^{7}, k \leqslant m\).

Solution

考场上想了一个 \(n,m\leqslant 5000\) 的做法,在这里讲一下吧 🥺。

首先想到枚举 \(a_1\) 的值为 \(c\),然后计算最大值为 \(c\),最大值有 \(i+1\) 个的排列数量。那么就需要将 \(m-(i+1)c\) 分给 \(n-i-1\) 个数,求每个数小于 \(c\) 的方案数,这个可以用二项式反演解决,设 \(f(j)\) 为将 \(m-(i+1)c\) 分给 \(n-i-1\) 个数,至少\(j\) 个数大于等于 \(c\) 的方案数,\(g(j)\) 就是恰好。

调了好久,最后发现是 long long 乘爆了。🥺😢😭😅。


正解做法出奇的简单。考虑条件概率 \(P(A | B) = P(AB)/P(B)\),设第一个人拿到 rk1 为 A,设第一个人得分 \(\geqslant k\) 为 B,于是只需要求 \(P(AB)\)\(P(B)\).

\(P(B)\) 非常好计算,对于 \(P(AB)\),也就是第一个人 rk1 且得分 \(\geqslant k\) 的概率。事实上可以发现,对于每个人这个概率是相同的,并且只要有人得分 \(\geqslant k\) 就意味着 rk1 得分一定 \(\geqslant k\)

于是只用算出至少一个人的得分 \(\geqslant k\) 的方案数就可以得到 \(nP(B)\)(把这个问题反一下,就可以用二项式反演 \(\mathcal O(n)\) 做),从而得到答案。

Code

懒得写了。

\(\cal T_2\) 基础树剖练习题 / chain

Description

给定一棵 \(n\) 个点以 \(1\) 为根的有根树,边有颜色,初始时全为白色。接下来有三种操作:

  • 1 u:表示修改 \(u\) 到根路径上的边的颜色,具体修改方式在下文说明;

  • 2 u:表示查询 \(u\) 到根路径上的黑边的个数;

  • 3 u:表示查询 \(u\) 子树内黑边的个数。

修改方式:首先,将从 \(u\) 到根的链拉出来,即为 \(l i s_{1}, l i s_{2}, \ldots, l i s_{m}\),其中 \(l i s_{1}=u, l i s_{m}= 1\)。然后,除去 \(u\) 到根路径上的边,将其余与 \(lis _{1} , lis _{3} , lis _{5}, \ldots\) 相连的边染成黑色,将与 \({lis}_{2} , lis _{4} , lis _{6}, \ldots\) 相连的边染成白色。最后,对于在 \(u\) 到根路径上的边,将 \(\left(\right. lis_1, \left.l i s_{2}\right),\left(l i s_{3}, l i s_{4}\right),\left(l i s_{5}, l i s_{6}\right), \ldots\) 染成黑色,将 \(\left(\right. lis _{2} , \left.lis_{3}\right),\left(\right. lis _{4} , \left.lis_{5}\right),\left(\right. lis_{6} , \left.lis_{7}\right), \ldots\) 染成白色。

\(1\leqslant n,q\leqslant 10^5\).

Solution

一些闲话:考试的时候写了基础暴力和链的部分分,结果最后所有暴力都跑过了一个理论跑不过的测试点,链的测试点却压根没有,真的亏麻了(。

以下文本需要在 此网站 中进行解密,密码是 恒电平逻辑电路 的英文名后面接上其中文名的拼音全称:

U2FsdGVkX19OSAbz1MTNf6P3p4zDyhJwzTRB3R+B8KQdKTF+lZccKzEy7MOdSCbJ
6zaqr8iy8D4UCxjglc1hcdKSFRC4nR8xC4N/F5bpgULg1l4waOmV8vPeHNUBSaIq
QXXCp1ezsIq6J0NP+qB/F917ZcrYIRU+/dtesWHKtqHrW/F7Ozjr6JhJCLv9BC4Q
eTRVX9KB4adZb1/nadcIGe4DyLfsd0GIs3wNt7o6nYZEc2k1on4feSgr14j/qBMv
fwYXWdYE8lKD/jAIMF6bgP5qms5LO5CkIC7xedPALM5YL9wFluQTNa1GCBbrjM34
cgZowmyOXmjuEDE+4N8tOhJNYEBrGxcwvAeZJie3yEwZbrXsPxtYZpy6GN9vVbPY
kLed6+f6EBWJUYV3IBBF4nFExcet3k470nM7X7SViBeTTuDBhmIKmXpInlGBGhca
01WtSVDo325BCWfuj65CiXgK4J87b+U4umWjHJY+3uJgmKoJJddFFhALSCv4owHG
fUG/d4QjsAnue47YL6HkqrQFUgZQVTBtQyv4PgTLr2vpDA9j2EJXFBMlr1Ve6puv
0SnAssUv8Xu9QcQEgWpKwjWVoUgG/xXhy1i0D5qJ6U8FB055F6bTczS+jkdf356q
dHnMZ3KKDG8Z5mAbO3hqcvJJ/sRQyMNgUT/XiuSoJdhGYAOxQvQbXEwlXc61lzys
cQ19ksRwtoJ+jy8kLFrQIOOILM/dv48WVBUgcfFz4T8ko/irLl+NKGLL7v8/Gg+v
2yF/kRR/YUVBvlZoE8OJFbutgWiqu8Wa+uIEq+q05ODT0ylDKDX5DzET9JmkZ9sR
a/sqMGLDhGl36HzIRZJ+qZvr6dQgnR5Q8mRBi9qhXBgfpcQbdQuTu1iA0omhut4Y
iNeQkJ6a6/HBYN+KHWh+hrKfFoq4x1iFp2R1ARmZX43zb0Z5DcENDIKWARg/t0Dn
OkDvDn4AAKCpeUisLPzbhy8GzbxUEGoValiKRMk4qJkO7AjJtt2pkiWxIAs2kaU7
JAwVdJUklKtlcQ/fKcSUl8ZG5BqDGwcV2BHRA1Yr7sSHrKEs18kKsS8BqDOsvbS4
es1rYHHht/o7+22rQJG03J5ck5jj+8rS2DZdbrS0TPsy9KiZPTO/sYxxkDsgSLJW
+3BKtZb05m0mH5pFFrkv319/8AFfsTMxaP696j4kKOn6wk31edvuMpSnllED0Xhk
l93gZbaDUQh6QGUXu5CTpvnR989VMEqtuUy8DF7qeD7/GZOSy3SNpS04hvBrcWCj
Sa5ZL8asj0hjgKzqtzFbZsKkexcHWWWb+eEzeY/rjXMWRD9CEUVgp3jbp//6+tqH
cENA4F1UU9RN1NK0ZwlgCgVdoxoK+Gi2OGso8ByD1+i0dy/Wki6cSCJjv1pOBd0k
kwbvSmrD1HQ9zKiqIky4Cq+b5QMZuKHcSQJtus3rIjQYrorS/oRlhKgFhPH6k4Ef
f2UGu5jexB/Oc0gp7cEIJYPd0lHq+NeW0Oow4ifbsaSEShMV2CIbs6x3cXNmQX4U
Up7dweO8dFrkmI8dRDPPlSoGMYNyBkX23YK6pu/wLVaekAa3796ywFkZzYNg8Q6q
DX5wsH2Gk/xRSVfMXrGwr14U/U8IxK7TpJvh3hqpEwSHtUDdCeXluZ/U4ZDlGn0c
weDAir1vs5EULafW/oCzP2NyeDeGUSJ/MV+de1JweAtJJa7ploB1GolI/jgNBvsu
GJeeg1I2/eYdW+ifPhufR2alaZFSIIbPyXUK0dEyZIUU4l0kbhZUer8ji+GfCu1L
IrunDvO+Ik3mOiI9cfG+FWwWfDiIyE1Vdx10SFMnCd8ErT0Zg6PFH5gmFkfTBenE
PMrCk3UIWXBBk0LV2jIvwA==

回到本题,用点 \(u\) 表示 \(u\) 到父亲的那条边。直接根据询问修改是不易的,但是你会发现一条边的黑白,只和被修改节点深度的奇偶性有关,所以可以直接预处理某个奇偶性下黑边条数,修改的时候就只需要改 \(\rm tag\),具体可以看代码实现。复杂度 \(\mathcal O(n\log^2 n)\).

Code

这里放一个链部分分的代码,已经和正解对拍过了:

# include <cstdio>
# include <cctype>
# define print(x,y) write(x), putchar(y)

template <class T>
inline T read(const T sample) {
    T x=0; char s; bool f=0;
    while(!isdigit(s=getchar())) f|=(s=='-');
    for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
    return f? -x: x;
}
template <class T>
inline void write(T x) {
    static int writ[50], w_tp=0;
    if(x<0) putchar('-'), x=-x;
    do writ[++w_tp]=x-x/10*10, x/=10; while(x);
    while(putchar(writ[w_tp--]^48), w_tp);
}

# include <vector>
using namespace std;

const int maxn = 1e5+5;

vector <int> e[maxn];
int n, f[maxn], val[maxn];

bool isChain() {
	for(int i=2;i<=n;++i)
		if(f[i]!=i-1) return false;
	return true;
}

namespace Jinx {
	int add[maxn];
	int delta, stk[maxn], tp, del[maxn];
	int query(int u) {
		if(!tp) return 0;
		if(u>stk[1]) return (stk[1]>>1)+delta+1;
		int l=1, r=tp, mid, ans=-1;
		while(l<=r) {
			mid = l+r+1>>1;
			if(stk[mid]>=u) ans=mid, l=mid+1;
			else r=mid-1;
		}
		if((u&1)==(stk[ans]&1)) mid=u>>1;
		else mid = (u-1>>1);
		return delta-del[ans]+mid;
	}
	void Rux() {
		for(int q=read(9); q; --q) {
			int opt=read(9), u=read(9);
			if(opt==1) {
				while(tp && stk[tp]<=u) delta -= add[tp--];
				if(!tp) { stk[++tp]=u, delta=0; continue; }
				stk[++tp]=u;
				if((u&1)==(stk[tp-1]&1)) add[tp]=1;
				else add[tp] = ((u&1)?0:1);
				delta+=add[tp], del[tp] = delta;
			} else if(opt==2) print(query(u),'\n');
			else print(query(n)-query(u),'\n');
		}
	}
}

int query(int u) {
	int ret=0;
	for(const auto& v:e[u]) 
		ret += query(v)+val[v];
	return ret;
}

int main() {
	n=read(9);
	for(int i=2;i<=n;++i) 
		e[f[i]=read(9)].emplace_back(i);
	if(isChain()) return Jinx::Rux(), (0-0);
	for(int q=read(9); q; --q) {
		int opt=read(9), u=read(9);
		if(opt==1) {
			int pre1=0;
			for(int x=u; x; ) {
				for(const auto& i:e[x])
					if(i^pre1) val[i]=1;
				if(x^u) val[pre1]=0;
				int v = f[x];
				if(!v) break;
				for(const auto& i:e[v])
					if(i^x) val[i]=0;
				val[x]=1, x=f[v], pre1=v; 
			}
		} else if(opt==2) {
			int ans=0;
			for(int x=u; x^1; x=f[x])
				ans += val[x];
			print(ans,'\n');
		} else print(query(u),'\n');
	}
	return 0;
}

正解代码:

# include <cstdio>
# include <cctype>
# define print(x,y) write(x), putchar(y)

template <class T>
inline T read(const T sample) {
    T x=0; char s; bool f=0;
    while(!isdigit(s=getchar())) f|=(s=='-');
    for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
    return f? -x: x;
}
template <class T>
inline void write(T x) {
    static int writ[50], w_tp=0;
    if(x<0) putchar('-'), x=-x;
    do writ[++w_tp]=x-x/10*10, x/=10; while(x);
    while(putchar(writ[w_tp--]^48), w_tp);
}

# include <vector>
using namespace std;

const int maxn = 1e5+5;

vector <int> e[maxn];
int las[maxn];
int Ref[maxn], lst[maxn], rst[maxn], ls[maxn], rs[maxn];
int f[maxn], n, dep[maxn], son[maxn], tp[maxn], dfn[maxn];

namespace caterpillar {
    int sz[maxn], idx;
    void predfs(int u) {
        dep[u] = dep[f[u]]+(sz[u]=1);
        for(const auto& v:e[u]) {
            predfs(v), sz[u]+=sz[v];
            if(sz[son[u]]<sz[v]) son[u]=v;
        }
    }
    void handleSons(int u) {
        ls[u] = idx+1;
        for(const auto& v:e[u])
            if(v^son[u]) Ref[dfn[v]=++idx]=v;
        rs[u] = idx;
        if(son[u]) handleSons(son[u]);
        las[u] = idx;
    }
    void relabel(int u,int t) {
        tp[u]=t;
        if(!dfn[u]) Ref[dfn[u]=++idx]=u;
        if(u==t) handleSons(u);
        lst[u] = idx+1;
        if(son[u]) relabel(son[u],t);
        for(const auto& v:e[u])
            if(v^son[u]) relabel(v,v);
        rst[u] = idx;
    }
}

namespace SgT {
    struct node { int s,tag,dat[2]; } t[maxn<<2];
    void _pushUp(int o) {
        t[o].dat[0] = t[o<<1].dat[0]+t[o<<1|1].dat[0],
        t[o].dat[1] = t[o<<1].dat[1]+t[o<<1|1].dat[1];
    }
    void pushUp(int o) { t[o].s=t[o<<1].s+t[o<<1|1].s; }
    void pushDown(int o) {
        if(t[o].tag==-1) return;
        t[o<<1].tag = t[o<<1|1].tag = t[o].tag,
        t[o<<1].s = t[o<<1].dat[t[o].tag],
        t[o<<1|1].s = t[o<<1|1].dat[t[o].tag];
        t[o].tag = -1;
    }
    void build(int o,int l,int r) {
        t[o].tag = -1;
        if(l==r) return t[o].dat[dep[Ref[l]]&1]=1, void();
        int mid = l+r>>1; build(o<<1,l,mid);
        build(o<<1|1,mid+1,r), _pushUp(o);
    }
    void modify(int o,int l,int r,int L,int R,int k) {
        if(L>R) return;
        if(l>=L && r<=R) return t[o].s=t[o].dat[t[o].tag=k], void();
        int mid = l+r>>1; pushDown(o);
        if(L<=mid) modify(o<<1,l,mid,L,R,k);
        if(R>mid) modify(o<<1|1,mid+1,r,L,R,k); pushUp(o);
    }
    int query(int o,int l,int r,int L,int R) {
        if(L>R) return 0;
        if(l>=L && r<=R) return t[o].s;
        int mid = l+r>>1, ret=0; pushDown(o);
        if(L<=mid) ret=query(o<<1,l,mid,L,R);
        if(R>mid) ret=ret+query(o<<1|1,mid+1,r,L,R);
        return ret;
    }
}

void Modify(int u) {
    int k = (dep[u]&1), las=0;
    for(; u; u=f[las=tp[u]]) {
        SgT::modify(1,1,n,lst[tp[u]],dfn[u],k);
        SgT::modify(1,1,n,ls[tp[u]],rs[u],k^1);
        if(las)	SgT::modify(1,1,n,dfn[las],dfn[las],k);
        if(son[u]) SgT::modify(1,1,n,dfn[son[u]],dfn[son[u]],k^1);
        if(tp[u]^1) SgT::modify(1,1,n,dfn[tp[u]],dfn[tp[u]],k);
    }
}

int handleChain(int u,int ret=0) {
    for(; u; u=f[tp[u]]) ret += SgT::query(1,1,n,lst[tp[u]],dfn[u])+
        SgT::query(1,1,n,dfn[tp[u]],dfn[tp[u]]); return ret;
}

int handleSubtree(int u) {
    return SgT::query(1,1,n,ls[u],las[u])+SgT::query(1,1,n,lst[u],rst[u]);
}

int main() {
    freopen("chain.in","r",stdin);
    freopen("chain.out","w",stdout);
    n=read(9); 
    for(int i=2;i<=n;++i) e[f[i]=read(9)].emplace_back(i);
    caterpillar::predfs(1), caterpillar::relabel(1,1);
    SgT::build(1,1,n);
    for(int q=read(9); q; --q) {
        int opt=read(9), x=read(9);
        if(opt==1) Modify(x);
        else if(opt==2) print(handleChain(x),'\n');
        else print(handleSubtree(x),'\n');
    }
    return 0;
}

\(\cal T_3\) 基础树论练习题 / tree

Description

定义一棵有标号有根树合法当且仅当每一个点的儿子个数属于 \(S\),依次生成 $ k $ 个 \(n\) 个点有标号有根树森林。
\(\forall\ 0 \leqslant i<n\),求出有多少种不同的生成方案使得其中恰有 \(ik\) 棵合法的树。输出结果模 \(p\) 后的值。

\[\begin{array}{|c|c|c|c|c|}\hline 子任务编号 & p & k & 子任务依赖 & 分数 \\\hline 1 & & =1 & & 10 \\\hline 2 & & \leqslant 10^{4} & 1 & 10 \\\hline 3 & \leqslant 100 & & & 30 \\\hline 4 & \leqslant 10^{4} & & 3 & 20 \\\hline 5 & & & 2,4 & 30 \\\hline\end{array} \]

\(1\leqslant n\leqslant 20,n<p\leqslant 10^5+3,1\leqslant k\leqslant 10^{16}\),且 \(p\) 是质数。

Solution

一些闲话:这题在考场上是真的做不动啊……

\(k=1\)

实际上就是求 \(n\) 个点的森林,含 \(i,∀\ 0 \leqslant i < n\) 个合法的树的方案数。但是我这也不会大雾

\(f_i\) 为节点个数为 \(i\) 的合法树,\(F(x)\)\(f_i\)\(\mathtt{EGF}\)。事实上 "合法的树" 是很难直接描绘的,所以考虑递推地求解 —— 用 \(F(x)\) 来描绘 \(F(x)\).

具体地,我们可以钦定一个根,再枚举它的子树,就有

\[F(x)=x\cdot\left(\sum_{i\in S}\frac{F^i(x)}{i!}\right) \]

之所以选用 \(\mathtt{EGF}\) 也是看在其重标号的功能,那个 \(1/i!\) 是为了去除卷积时子树的互异。由于 \(n\leqslant 20\),所以这个柿子大概是可以硬算的 qwq.

那么对于 \(i\),所求即为

\[n![x^n]\frac{F^i(x)}{i!}\cdot \exp(G(x)-F(x)) \]

它的意义就是枚举 \(i\) 棵合法的树,其余部分则是 不合法的森林\(G(x)\) 是有标号有根树的 \(\mathtt{EGF}\).

\(k\leqslant 10^4\)

可以把上面的答案式子写成 \(\mathtt{OGF}\) 的形式(记其为 \(H(x)\)),那么实际上是求 \(H^k(x)\)(最多需要求到大约 \(x^{nk}\) 项),多项式求幂可以做到 \(\mathcal O(nk\log (nk))\).

后面的内容大约学不会了,学会了大约也用不来,所以就鸽了 🥺。

Code

啥也没有。
posted on 2022-07-04 23:14  Oxide  阅读(92)  评论(3编辑  收藏  举报