[题解][Codeforces 1111A~1111E]Codeforces Round #537 (Div. 2) 简要题解

题目

洛谷 RemoteJudge

Codeforces

A

题意

  • 给定两个字符串 S S S T T T ,仅包含小写英文字母
  • 你可以找到 S S S 中的一个元音字母,把它变成另一个元音字母
  • 或者找到 S S S 中的一个辅音字母,把它变成另一个辅音字母
  • 求能不能把 S S S 变成 T T T
  • 1 ≤ ∣ S ∣ , ∣ T ∣ ≤ 1000 1\le|S|,|T|\le1000 1S,T1000

题解

  • 直接模拟

代码

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

const int N = 1005;

char s[N], t[N];

bool check(char c)
{
	return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
}

int main()
{
	scanf("%s%s", s + 1, t + 1);
	if (strlen(s + 1) != strlen(t + 1))
		return puts("No"), 0;
	int n = strlen(s + 1);
	for (int i = 1; i <= n; i++)
		if (check(s[i]) ^ check(t[i]))
			return puts("No"), 0;
	puts("Yes");
	return 0;
}

B

题意

  • n n n 个数
  • 你需要进行不超过 m m m 次操作,每次操作可以
  • (1)将一个数加一
  • (2)把这个数扔掉
  • 每个数最多有 k k k 个操作发生在这个数上
  • 最大化剩下的数的平均值
  • 注:不能把所有的数都扔掉
  • 1 ≤ n , k ≤ 1 0 5 , 1 ≤ m ≤ 1 0 7 1\le n,k\le10^5,1\le m\le10^7 1n,k105,1m107

题解

  • 显然,有下面几个性质
  • (1)一个数不可能加一之后又被扔掉
  • (2)被扔掉的总是较小的数
  • (3)对于剩下的数,具体是哪些数加上了 1 1 1 不重要
  • 先把这 n n n 个数从小到大排序
  • 枚举扔掉了几个数(记作 i i i
  • 这样我们还能进行 min ⁡ ( k × ( n − i ) , m − i ) \min(k\times(n-i),m-i) min(k×(ni),mi) 次加一操作
  • 直接利用前 (hou) 缀和计算平均值

代码

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

inline int read()
{
	int res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	return bo ? ~res + 1 : res;
}

typedef long long ll;

const int N = 1e5 + 5;

int n, k, m, a[N];
double ans;
ll sum[N];

int main()
{
	n = read(); k = read(); m = read();
	for (int i = 1; i <= n; i++) a[i] = read();
	std::sort(a + 1, a + n + 1);
	for (int i = n; i >= 1; i--) sum[i] = sum[i + 1] + a[i];
	for (int i = 1; i <= n; i++)
	{
		int cnt = m - (i - 1);
		if (cnt < 0) continue;
		int x = n - i + 1, delta = std::min(1ll * cnt, 1ll * x * k);
		ans = std::max(ans, 1.0 * (sum[i] + delta) / x);
	}
	printf("%.12lf\n", ans);
	return 0;
}

C

题意

  • 你有一条长度为 2 n 2^n 2n 的纸带
  • 纸带上 k k k 个点有标记,这些标记的位置坐标给定
  • 对于一条纸带,你把它解决掉有两种方法
  • (1)切成两条等长的纸带,对这两条纸带分别递归处理(纸带长度为 1 1 1 时不能进行此操作)
  • (2)直接把这条纸带解决掉。如果这条纸带上没有标记,则需要 A A A 的代价,否则需要 B × x × l B\times x\times l B×x×l 的代价( x x x 为纸带内标记的个数, l l l 为纸带长度)
  • 求把这条长度为 2 n 2^n 2n 的纸带解决掉的最小代价
  • 1 ≤ n ≤ 30 , 1 ≤ k ≤ 1 0 5 , 1 ≤ A , B ≤ 1 0 4 1\le n\le30,1\le k\le10^5,1\le A,B\le10^4 1n30,1k105,1A,B104

算法:线段树

  • 考虑建一棵动态开点线段树,每个点储存该区间内的标记个数,以及解决掉的最小代价
  • 显然有
  • m i n c o s t ( p ) = min ⁡ ( m i n c o s t ( l c ) + m i n c o s t ( r c ) , B × s u m ( p ) × l e n ( p ) ) mincost(p)=\min(mincost(lc)+mincost(rc),B\times sum(p)\times len(p)) mincost(p)=min(mincost(lc)+mincost(rc),B×sum(p)×len(p))
  • m i n c o s t ( p ) mincost(p) mincost(p) 为解决掉区间 p p p 的最小代价, l c lc lc r c rc rc 分别为线段树上 p p p 的左右儿子, s u m ( p ) sum(p) sum(p) 为区间 p p p 内的标记个数, l e n ( p ) len(p) len(p) 为区间 p p p 的长度
  • 注:如果 p p p 为空节点则 m i n c o s t ( p ) = A mincost(p)=A mincost(p)=A
  • 复杂度 O ( k n ) O(kn) O(kn)

代码

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

inline int read()
{
	int res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	return bo ? ~res + 1 : res;
}

template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}

typedef long long ll;

const int N = 4e6 + 5;

int n, k, a, b, ToT, Root;

struct node
{
	int lc, rc, sum;
	ll val;
} T[N];

void upt(int l, int r, int p)
{
	ll ls = T[p].lc ? T[T[p].lc].val : a,
		rs = T[p].rc ? T[T[p].rc].val : a;
	T[p].sum = T[T[p].lc].sum + T[T[p].rc].sum;
	T[p].val = Min(1ll * b * T[p].sum * (r - l + 1), ls + rs);
}

void change(int l, int r, int pos, int v, int &p)
{
	if (!p) p = ++ToT;
	if (l == r)
	{
		T[p].sum += v;
		T[p].val = 1ll * b * T[p].sum;
		return;
	}
	int mid = l + r >> 1;
	if (pos <= mid) change(l, mid, pos, v, T[p].lc);
	else change(mid + 1, r, pos, v, T[p].rc);
	upt(l, r, p);
}

int main()
{
	n = read(); k = read(); a = read(); b = read();
	while (k--) change(1, 1 << n, read(), 1, Root);
	std::cout << T[1].val << std::endl;
	return 0;
}

D

题意

  • 定义一个长度为偶数的字符串 s s s 合法,当且仅当对于每个字符 c c c ,都满足所有的字符 c c c 全部出现在 s s s 的左半部分或全部出现在 s s s 的右半部分
  • 读入一个长度为字符串 s s s ,包含小写和大写英文字符
  • q q q 组询问,每次询问给定两个字符 c 1 c_1 c1 c 2 c_2 c2
  • 求有多少个长度为 ∣ s ∣ |s| s 的合法字符串 t t t 满足
  • (1)对于任意字符 c c c c c c t t t 中的出现次数和在 s s s 中的出现次数相等
  • (2)字符 c 1 c_1 c1 c 2 c_2 c2 必须同时出现在 t t t 的左半边或右半边
  • 2 ≤ ∣ s ∣ ≤ 1 0 5 , 1 ≤ q ≤ 1 0 5 2\le|s|\le10^5,1\le q\le10^5 2s105,1q105

算法:背包 DP + 组合数学

  • 由于看错题目,考场上一直看成了毒瘤讨论
  • 先考虑不限制咋做
  • 这是一个经典的背包问题
  • 如果只考虑每种字符出现在左半边还是右半边而不考虑顺序
  • 则设计 DP : f [ i ] [ j ] f[i][j] f[i][j] 表示前 i i i 个字符,占用左半边 j j j 个位置的方案数
  • 显然
  • f [ 0 ] [ 0 ] = 1 f[0][0]=1 f[0][0]=1
  • f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i − 1 ] [ j − c n t [ i ] ] f[i][j]=f[i-1][j]+f[i-1][j-cnt[i]] f[i][j]=f[i1][j]+f[i1][jcnt[i]]
  • c n t [ i ] cnt[i] cnt[i] 为字符 i i i 的出现次数
  • 如果考虑字符间的相对顺序
  • 根据多重集的排列公式,可以考虑把状态定义改一改
  • f [ i ] [ j ] f[i][j] f[i][j] 表示前 i i i 个字符,占用左半边 j j j 个位置的方案数除以 j ! j! j! 再除以 ( s u m [ i ] − j ) ! (sum[i]-j)! (sum[i]j)! 之后的结果
  • s u m [ i ] sum[i] sum[i] 为前 i i i 个字符的出现次数之和
  • f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i − 1 ] [ j − c n t [ i ] ] c n t [ i ] ! f[i][j]=\frac{f[i-1][j]+f[i-1][j-cnt[i]]}{cnt[i]!} f[i][j]=cnt[i]!f[i1][j]+f[i1][jcnt[i]]
  • (注意细节:如果 c n t [ i ] = 0 cnt[i]=0 cnt[i]=0 则字符 i i i 放左侧和放右侧没有区别,所以这时候直接 f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j]=f[i-1][j] f[i][j]=f[i1][j]
  • 那么不限制两个字符在同侧时,方案数就是 f [ 最 大 字 符 ] [ m ] × ( m ! ) 2 f[最大字符][m]\times(m!)^2 f[][m]×(m!)2 ,其中 m = n 2 m=\frac n2 m=2n
  • 然后考虑如果要限制两个字符 c 1 c_1 c1 c 2 c_2 c2 在同侧,我们先假设这两个字符都在左侧
  • c 1 c_1 c1 c 2 c_2 c2 这两个字符 ban 掉,那么我们要求的就是左侧有 m − c n t [ c 1 ] − c n t [ c 2 ] m-cnt[c_1]-cnt[c_2] mcnt[c1]cnt[c2] 个字符,右侧 m m m 个字符(且不能使用字符 c 1 c_1 c1 c 2 c_2 c2 )的方案数,除以 ( m ! ) × ( ( m − c n t [ c 1 ] − c n t [ c 2 ] ) ! ) (m!)\times((m-cnt[c_1]-cnt[c_2])!) (m!)×((mcnt[c1]cnt[c2])!) 得到的值
  • 回顾这个转移方程,发现我们最终的 f [ 最 大 字 符 ] [ ] f[最大字符][] f[][] 数组可以看作一个多项式,它等于
  • ∏ c 1 + x c n t [ c ] c n t [ c ] ! \prod_c\frac{1+x^{cnt[c]}}{cnt[c]!} ccnt[c]!1+xcnt[c]
  • 即不超过 52 52 52 (字符集大小)个,只有 2 2 2 个非零项的多项式之积
  • 我们现在想知道对于任意两个字符 c 1 c_1 c1 c 2 c_2 c2 ,这不超过 52 52 52 个多项式中去掉 c 1 c_1 c1 和第 c 2 c_2 c2 个多项式之后的乘积
  • 枚举字符 c 1 c_1 c1 c 2 c_2 c2 之后对多项式 ∏ c 1 + x c n t [ c ] c n t [ c ] ! \prod_c\frac{1+x^{cnt[c]}}{cnt[c]!} ccnt[c]!1+xcnt[c] 做除法即可。由于只有 2 2 2 个非零项,所以多项式除法可以做到 O ( n ) O(n) O(n) ,复杂度 O ( 5 2 2 × n ) O(52^2\times n) O(522×n) ,卡常可过
  • 当然,我们可以发现对一个 1 + x k 1+x^k 1+xk 形式的多项式做除法时,次数在模 k k k 意义下不同的项互不影响。而我们只想知道第 m − c n t [ c 1 ] − c n t [ c 2 ] m-cnt[c_1]-cnt[c_2] mcnt[c1]cnt[c2] 次项
  • 所以枚举 c 1 c_1 c1 ,做一遍除法之后再枚举 c 2 c_2 c2 时就只需要对与 m − c n t [ c 1 ] − c n t [ c 2 ] m-cnt[c_1]-cnt[c_2] mcnt[c1]cnt[c2] 在模 c n t [ c 2 ] cnt[c_2] cnt[c2] 同余的项进行处理,复杂度可以做到 O ( 52 n log ⁡ n ) O(52n\log n) O(52nlogn) ,但是为了保证复杂度,需要把 c n t [ c 2 ] cnt[c_2] cnt[c2] 相同的一起处理,不是很好写博主就没去写
  • 回到询问,设删掉 c 1 c_1 c1 c 2 c_2 c2 后多项式乘积的第 m − c n t [ c 1 ] − c n t [ c 2 ] m-cnt[c_1]-cnt[c_2] mcnt[c1]cnt[c2] 项系数为 h [ c 1 ] [ c 2 ] h[c_1][c_2] h[c1][c2] ,那么询问结果为
  • 2 × h [ c 1 ] [ c 2 ] × ( m ! ) 2 ( c n t [ c 1 ] ! ) × ( c n t [ c 2 ] ! ) \frac{2\times h[c_1][c_2]\times(m!)^2}{(cnt[c_1]!)\times(cnt[c_2]!)} (cnt[c1]!)×(cnt[c2]!)2×h[c1][c2]×(m!)2
  • 注意,这里的 2 2 2 是指 c 1 c_1 c1 c 2 c_2 c2 可以同时出现在左侧,也可以同时出现在右侧

代码

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

inline int read()
{
	int res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	return bo ? ~res + 1 : res;
}

const int N = 1e5 + 5, M = 52, ZZQ = 1e9 + 7;

int n, m, q, fac[N], inv[N], f[N], g[N], tmp[N], pmt[N], cnt[N], h[M][M];
char s[N];

int qpow(int a, int b)
{
	int res = 1;
	while (b)
	{
		if (b & 1) res = 1ll * res * a % ZZQ;
		a = 1ll * a * a % ZZQ;
		b >>= 1;
	}
	return res;
}

int o(char c)
{
	if (c >= 'a' && c <= 'z') return c - 'a';
	return c - 'A' + 26;
}

void divi(int x, int dis1)
{
	for (int i = 0; i <= n; i++)
	{
		g[i] = f[i];
		if (i > n - dis1) tmp[i] = 0;
	}	
	for (int i = n; i >= dis1; i--)
	{
		tmp[i - dis1] = 1ll * x * g[i] % ZZQ;
		g[i - dis1] -= g[i];
		if (g[i - dis1] < 0) g[i - dis1] += ZZQ;
	}
	for (int i = 0; i <= n; i++) pmt[i] = tmp[i];
}

void ivid(int y, int dis2)
{
	for (int i = 0; i <= n; i++)
	{
		tmp[i] = pmt[i];
		if (i > n - dis2) g[i] = 0;
	}
	for (int i = n; i >= dis2; i--)
	{
		g[i - dis2] = 1ll * y * tmp[i] % ZZQ;
		tmp[i - dis2] -= tmp[i];
		if (tmp[i - dis2] < 0) tmp[i - dis2] += ZZQ;
	}
}

int main()
{
	int x, y;
	scanf("%s", s + 1);
	n = strlen(s + 1);
	for (int i = 1; i <= n; i++) cnt[o(s[i])]++;
	fac[0] = inv[0] = inv[1] = 1;
	for (int i = 1; i <= n; i++) fac[i] = 1ll * fac[i - 1] * i % ZZQ;
	for (int i = 2; i <= n; i++)
		inv[i] = 1ll * (ZZQ - ZZQ / i) * inv[ZZQ % i] % ZZQ;
	for (int i = 2; i <= n; i++)
		inv[i] = 1ll * inv[i] * inv[i - 1] % ZZQ;
	m = n >> 1;
	q = read();
	f[0] = 1;
	for (int i = 0; i < 52; i++) if (cnt[i])
	{
		for (int j = cnt[i]; j <= n; j++)
			tmp[j] = 1ll * f[j - cnt[i]] * inv[cnt[i]] % ZZQ;
		for (int j = 0; j <= n; j++) f[j] = 1ll * f[j] * inv[cnt[i]] % ZZQ;
		for (int j = cnt[i]; j <= n; j++)
			f[j] = (f[j] + tmp[j]) % ZZQ;
	}
	for (int i = 0; i < 52; i++) if (cnt[i])
	{
		divi(fac[cnt[i]], cnt[i]);
		for (int j = i + 1; j < 52; j++) if (cnt[j])
		{
			ivid(fac[cnt[j]], cnt[j]);
			h[i][j] = h[j][i] = m - cnt[i] - cnt[j] < 0 ? 0 :
				g[m - cnt[i] - cnt[j]];
		}
	}
	while (q--)
	{
		x = o(s[read()]); y = o(s[read()]);
		if (x == y)
		{
			printf("%d\n", 1ll * f[m] * fac[m] % ZZQ * fac[m] % ZZQ);
			continue;
		}
		else printf("%d\n", 2ll * h[x][y] * inv[cnt[x]] % ZZQ
			* inv[cnt[y]] % ZZQ * fac[m] % ZZQ * fac[m] % ZZQ);
	}
	return 0;
}

E

题意

  • 给定一棵 n n n 节点的树, q q q 组询问
  • 每次询问三个参数 k , m , r k,m,r k,m,r 以及一个长度为 k k k 的数组 a a a
  • 询问在以 r r r 为根时,把数组 a a a 内的节点分成 m m m 组,使得每组内任意两点没有祖先后代关系的方案数
  • 1 ≤ n , q ≤ 1 0 5 1\le n,q\le10^5 1n,q105
  • 每组询问内 1 ≤ k , r ≤ n , 1 ≤ m ≤ min ⁡ ( 300 , k ) 1\le k,r\le n,1\le m\le\min(300,k) 1k,rn,1mmin(300,k)
  • 所有询问的 k k k 之和不超过 1 0 5 10^5 105

算法: DFS 序 + 大讨论 + DP

  • 丧心病狂的换根操作啊!!!!!
  • 先解决两个问题
  • (1)以 r r r 为根时点 u u u 的子树大小
  • (2)以 r r r 为根时点 u u u 的 DFS 序
  • 下面 d f n [ u ] dfn[u] dfn[u] s i z e [ u ] size[u] size[u] d e p [ u ] dep[u] dep[u] 分别为以 1 1 1 为根时 u u u 的 DFS 序、子树大小、深度
  • 问题(1)较好处理,有三种情况
  • u = r u=r u=r u u u 的子树大小为 n n n
  • ② 在以 1 1 1 为根的树上 u u u 不是 r r r 的祖先: u u u 的子树大小为 s i z e [ u ] size[u] size[u]
  • ③否则 u u u 的子树大小为 n − s i z e [ x ] n-size[x] nsize[x] ,其中 x x x 为在树上从 u u u 走到 r r r 的过程中经过的第二个点(由于在以 1 1 1 为根的树上 u u u r r r 的祖先,可以用倍增求出 x x x
  • 问题(2)就是真正的大讨论了
  • 为了方便讨论,我们假设对于任意 1 1 1 r r r 的路径上的点 x x x 1 1 1 除外),把 f a [ x ] fa[x] fa[x] 设定为以 r r r 为根时 x x x 的第一个子节点( f a [ x ] fa[x] fa[x] 为以 1 1 1 为根时 x x x 的父亲节点)
  • u = r u=r u=r u u u 的 DFS 序为 1 1 1
  • ②以 1 1 1 为根时 u u u r r r 的子树(不包括 r r r )内: u u u 的 DFS 序为 n − s i z e [ r ] + d f n [ u ] − d f n [ r ] + 1 n-size[r]+dfn[u]-dfn[r]+1 nsize[r]+dfn[u]dfn[r]+1
  • ③以 1 1 1 为根时 u u u r r r 的祖先(不包括 r r r ): u u u 的 DFS 序为 d e p [ r ] − d e p [ u ] + 1 dep[r]-dep[u]+1 dep[r]dep[u]+1
  • ④否则设 x x x u u u r r r 的 LCA , y y y 为在树上从 x x x 走到 r r r 的过程中经过的第二个点(同样也可用倍增求),这时 u u u 的 DFS 序为
  • d e p [ r ] − d e p [ x ] + n − s i z e [ x ] + w h i c h ( x , y , u ) dep[r]-dep[x]+n-size[x]+which(x,y,u) dep[r]dep[x]+nsize[x]+which(x,y,u)
  • 其中 w h i c h ( x , y , u ) which(x,y,u) which(x,y,u) 表示在集合 [ d f n [ x ] , d f n [ x ] + s z e [ x ] ) − [ d f n [ y ] , d f n [ y ] + s z e [ y ] ) [dfn[x],dfn[x]+sze[x])-[dfn[y],dfn[y]+sze[y]) [dfn[x],dfn[x]+sze[x])[dfn[y],dfn[y]+sze[y]) 中, d f n [ u ] dfn[u] dfn[u] 的排名
  • d f n [ u ] &lt; d f n [ y ] dfn[u]&lt;dfn[y] dfn[u]<dfn[y] d f n [ u ] ≥ d f n [ y ] + s z e [ y ] dfn[u]\ge dfn[y]+sze[y] dfn[u]dfn[y]+sze[y] 两类进行讨论
  • 大讨论结束之后我们就可以考虑通过 DP 算方案数辣
  • 虚树 + 树形背包 DP 的做法很容易想到,但是不好写 + 常数大
  • 我们 DP 的策略:先把所有点按照以 r r r 为根的 DFS 序进行排序
  • 然后 f [ i ] [ j ] f[i][j] f[i][j] 表示所有 DFS 序前 i i i 小的关键点分成 j j j 个合法集合的方案数
  • 我们知道, DFS 序有一个性质:对于任意点 u u u u u u 的所有严格祖先的 DFS 序严格小于 u u u 的 DFS 序
  • 初始状态 f [ 0 ] [ 0 ] = 1 f[0][0]=1 f[0][0]=1
  • 有两种转移
  • (1) i + 1 i+1 i+1 独自分到一个集合
  • f [ i + 1 ] [ j + 1 ] + = f [ i ] [ j ] f[i+1][j+1]+=f[i][j] f[i+1][j+1]+=f[i][j]
  • (2) i + 1 i+1 i+1 加入前面已经生成过的集合内
  • 根据 DFS 序的性质,与 i + 1 i+1 i+1 互斥的所有点(严格祖先)一定全部出现在 [ 1 , i ] [1,i] [1,i] 中,而 i + 1 i+1 i+1 的所有严格祖先又必然地两两存在祖先关系,故 i + 1 i+1 i+1 的所有严格祖先中,任意两个点都不可能在一个集合内。所以当前的 j j j 个集合中,必然会有恰好 c n t [ i + 1 ] cnt[i+1] cnt[i+1] 个集合由于包含了 i + 1 i+1 i+1 的祖先而不能和 i + 1 i+1 i+1 并到一个集合内,还剩下 j − c n t [ i + 1 ] j-cnt[i+1] jcnt[i+1] 个集合可以让点 i + 1 i+1 i+1 加入( c n t [ i ] cnt[i] cnt[i] 表示以 r r r 为根的树上, DFS 序排名为 i i i 的关键点的所有严格祖先中有多少个关键点,可以使用树上差分 + 树状数组等手段求出),于是这个转移也出来了
  • f [ i + 1 ] [ j ] + = f [ i ] [ j ] × ( j − c n t [ i + 1 ] ) f[i+1][j]+=f[i][j]\times(j-cnt[i+1]) f[i+1][j]+=f[i][j]×(jcnt[i+1])
  • 复杂度 O ( n log ⁡ n + ( ∑ k ) ( log ⁡ n + m ) ) O(n\log n+(\sum k)(\log n+m)) O(nlogn+(k)(logn+m))

代码

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

inline int read()
{
	int res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	return bo ? ~res + 1 : res;
}

template <class T>
inline void Swap(T &a, T &b) {T t = a; a = b; b = t;}

const int N = 1e5 + 5, M = N << 1, LogN = 20, E = 305, ZZQ = 1e9 + 7;

int n, q, ecnt, nxt[M], adj[N], go[M], T, dfn[N], sze[N], fa[N][LogN], dep[N],
xdfn[N], xsze[N], k, m, r, a[N], A[N], cnt[N], f[N][E];

void change(int x, int v)
{
	for (; x <= n; x += x & -x)
		A[x] += v;
}

int ask(int x)
{
	int res = 0;
	for (; x; x -= x & -x)
		res += A[x];
	return res;
}

void change(int l, int r, int v)
{
	change(l, v); change(r + 1, -v);
}

void add_edge(int u, int v)
{
	nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
	nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u;
}

void dfs(int u, int fu)
{
	dfn[u] = ++T; sze[u] = 1;
	dep[u] = dep[fa[u][0] = fu] + 1;
	for (int i = 0; i < 16; i++)
		fa[u][i + 1] = fa[fa[u][i]][i];
	for (int e = adj[u], v; e; e = nxt[e])
	{
		if ((v = go[e]) == fu) continue;
		dfs(v, u);
		sze[u] += sze[v];
	}
}

int lca(int u, int v)
{
	if (dep[u] < dep[v]) Swap(u, v);
	for (int i = 16; i >= 0; i--)
	{
		if (dep[fa[u][i]] >= dep[v]) u = fa[u][i];
		if (u == v) return u;
	}
	for (int i = 16; i >= 0; i--)
		if (fa[u][i] != fa[v][i])
			u = fa[u][i], v = fa[v][i];
	return fa[u][0];
}

int jm(int u, int dis)
{
	for (int i = 16; i >= 0; i--)
		if ((dis >> i) & 1) u = fa[u][i];
	return u;
}

int which(int l, int r, int inl, int inr, int id)
{
	if (id < inl) return id - l;
	return id - l - (inr - inl + 1);
}

int get_sze(int u, int rt)
{
	if (dfn[rt] < dfn[u] || dfn[rt] >= dfn[u] + sze[u])
		return sze[u];
	if (u == rt) return n;
	return n - sze[jm(rt, dep[rt] - dep[u] - 1)];
}

int get_dfn(int u, int rt)
{
	if (u == rt) return 1;
	if (dfn[rt] <= dfn[u] && dfn[u] < dfn[rt] + sze[rt])
		return n - sze[rt] + dfn[u] - dfn[rt] + 1;
	int x = lca(u, rt), y;
	if (u == x) return dep[rt] - dep[u] + 1;
	int delta = dep[rt] - dep[x] + 1 + n - sze[x];
	y = jm(rt, dep[rt] - dep[x] - 1);
	return delta + which(dfn[x], dfn[x] + sze[x] - 1,
		dfn[y], dfn[y] + sze[y] - 1, dfn[u]);
}

inline bool comp(int a, int b)
{
	return xdfn[a] < xdfn[b];
}

int main()
{
	int x, y;
	n = read(); q = read();
	for (int i = 1; i < n; i++) x = read(), y = read(),
		add_edge(x, y);
	dfs(1, 0);
	f[0][0] = 1;
	while (q--)
	{
		k = read(); m = read(); r = read();
		for (int i = 1; i <= k; i++)
		{
			a[i] = read();
			xdfn[a[i]] = get_dfn(a[i], r);
			xsze[a[i]] = get_sze(a[i], r);
			change(xdfn[a[i]] + 1, xdfn[a[i]] + xsze[a[i]] - 1, 1);
			for (int j = 0; j <= m; j++) f[i][j] = 0;
		}
		std::sort(a + 1, a + k + 1, comp);
		for (int i = 1; i <= k; i++) cnt[i] = ask(xdfn[a[i]]);
		for (int i = 1; i <= k; i++)
			change(xdfn[a[i]] + 1, xdfn[a[i]] + xsze[a[i]] - 1, -1);
		for (int i = 0; i < k; i++) for (int j = 0; j <= m; j++)
		{
			if (!f[i][j]) continue;
			if (j < m) f[i + 1][j + 1] = (f[i + 1][j + 1] + f[i][j]) % ZZQ;
			f[i + 1][j] = (1ll * f[i][j] * (j - cnt[i + 1]) + f[i + 1][j]) % ZZQ;
		}
		int ans = 0;
		for (int i = 0; i <= m; i++) ans = (ans + f[k][i]) % ZZQ;
		printf("%d\n", ans);
	}
	return 0;
}
抱歉,根据提供的引用内容,我无法理解你具体想要问什么问题。请提供更清晰明确的问题,我将竭诚为你解答。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Codeforces Round 860 (Div. 2)题解](https://blog.csdn.net/qq_60653991/article/details/129802687)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [【CodeforcesCodeforces Round 865 (Div. 2) (补赛)](https://blog.csdn.net/t_mod/article/details/130104033)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [Codeforces Round 872 (Div. 2)(前三道](https://blog.csdn.net/qq_68286180/article/details/130570952)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值