8月4日模拟赛题解

前言

总体评价:

\(\rm T1\):签到题,用桶乱♂搞♂

然而机房一堆人爆零

\(\color{Red}{评分:10}\)

\(\rm T2\):思维题,思路清奇

为什么我的 \(50\) 分暴力爆零了???

\(\color{Red}{评分:60}\)

\(\rm T3\):缩点乱搞

\(\color{Red}{评分:30}\)

\(\rm T4\):我\(**\)你个大\(**\)

\(\color{Red}{评分:-114514}\)

成绩:

\(100+0+100+0,rk2\)

\(\rm T2\) 暴力怎么炸了???!!!

\(\rm T4\) 树剖一分都没骗到。


\(\rm Solution\)

T1

UVA1368 DNA序列 DNA Consensus String

输入 \(n\) 个长度均为 \(m\) 的字符串,求一个字符串,该序列能满足到 \(n\) 个序列的总 \(\rm Harmming\) 距离最小,若相等,输出字典序最小的。两个等长字符串的 \(\rm Harmming\) 距离等于字符不同的的位置个数。

例如,\(\rm ACGT\)\(\rm GCGA\)\(\rm Harmming\) 距离为 \(2\)(位置 \(1\)\(4\) 的字符不相同)。

思路:

对于本题,遍历 \(i=1\to m\),开桶记录 \(26\) 个字母在这 \(n\) 个字符串的第 \(i\) 位出现的次数,统计出现最多的字母,那么答案的第 \(i\) 位一定是它,剩下的加入 \(\rm Harmming\) 距离。

时间复杂度 \(\operatorname{O}(nmt)\)

\(\rm Code\)

char s[55][1005];
int a[30];

int main()
{
	int t, n, m;
	scanf("%d", &t);
	while (t--)
	{
		scanf("%d%d", &n, &m);
		for (re int i = 1; i <= n; i++)
		{
			scanf("%s", s[i]);
		}
		int ans = 0;
		for (re int i = 0; i < m; i++)
		{
			memset(a, 0, sizeof(a));
			for (re int j = 1; j <= n; j++)
			{
				a[s[j][i] - 'A' + 1]++;
			}
			int res = 0;
			char c;
			for (re int j = 1; j <= 26; j++)
			{
				if (res < a[j]) //记录出现次数最多的
				{
					res = a[j];
					c = j + 'A' - 1;
				}
			}
			ans += n - res; //剩下的计入Harmming距离中
			printf("%c", c);
		}
		printf("\n%d\n", ans);
	}
	return 0;
}

T2

有一个 \(n\)\(m\) 列的矩阵。矩阵的第一行数字为 \(1,2,3,\cdots,m\),第二行数字为 \(m+1,m+2,\cdots,2m\),...第 \(n\) 行数字为 \((n-1)m+1,(n-1)m+2,\cdots,nm\)

例如,对于 \(n=3,m=4\)

1  2  3  4
5  6  7  8
9  10 11 12

需要支持操作:将其中一行或一列的所有数乘以非负数,最后输出矩阵中所有值的总和\(\mod 10^9+7\)

思路:

因为同一列上相邻两个数相差 \(m\),所以通过第 \(1\) 行可以求出第 \(2\) 行 ~ 第 \(n\) 行的和;

根据乘法结合律,若同一个数被乘了多次,则改变顺序后结果仍然相等,所以我们可以先把列的算了,再乘上行的。

\(l(x)\) 为第 \(x\) 列一共应该乘上的数,\(h(x)\) 为第 \(x\) 行一共应该乘上的数;

则第 \(i\) 行乘 \(l(i)\) 后,
\(1\) 行的和为 \(b=\sum\limits_{i=1}^ml(i)\cdot i\),第 \(i\) 行比第 \(i-1\) 行的和多 \(a=\sum\limits_{i=1}^ml(i)\cdot m\),故此时第 \(i\) 行的和为 \(b+a(i-1)\);再乘上 \(h(i)\),最终第 \(i\) 行的和就是 \([b+a(i-1)]\cdot h(i)\)

时间复杂度 \(\operatorname{O}(n)\)

Caution:此题数据十分毒瘤!!!不开 \(\rm long long\) 见祖宗!!!每操作一次就要取模!!!

\(\rm Code\)

#define int long long

const int MAXN = 1e6 + 5;
const int MOD = 1e9 + 7;

int l[MAXN], h[MAXN];

signed main()
{
	int n = read(), m = read(), k = read();
	for (re int i = 1; i <= m; i++)
	{
		l[i] = 1;
	}
	for (re int i = 1; i <= n; i++)
	{
		h[i] = 1;
	}
	while (k--)
	{
		char op;
		scanf("%s", &op);
		int x = read(), y = read();
		if (op == 'R')
		{
			h[x] = h[x] * y % MOD;
		}
		else
		{
			l[x] = l[x] * y % MOD;
		}
	}
	int a = 0, b = 0, ans = 0;
	for (re int i = 1; i <= m; i++)
	{
		a = (a + l[i] * m % MOD) % MOD;
		b = (b + l[i] * i % MOD) % MOD;
	}
	for (re int i = 1; i <= n; i++)
	{
		ans = (ans + (b + a * (i - 1) % MOD) * h[i] % MOD) % MOD; //最后将每一行的和累加
	}
	write(ans);
	return 0;
}

T3

P2341 [USACO03FALL][HAOI2006]受欢迎的牛 G

给定一张有向图,求有多少个节点,所有节点都可到达它。

思路:

首先,对于一个强连通子图内,若其中一个节点满足要求,则该 \(\rm SCC\) 内所有节点都满足要求,这是根据强连通子图的定义。当然,强连通子图满足,则 \(\rm SCC\) 也一定满足。

所以直接搞缩点,建新图的同时记录出度,那么答案一定就是出度为 \(0\) 的点所对应的 \(\rm SCC\) 中的所有节点,证明如下:

假设有一个点 \(u\) 出度 \(\ge1\) 且所有点都可到达它,则以 \(u\) 为起点的某一条有向边 \(<u,v>\)\(v\) 也要能到达 \(u\),则 \(u\)\(v\) 能合并成一个更大的强连通子图,与 \(u\)\(\rm SCC\) 矛盾。

还有一个问题,就是当出度为 \(0\) 的点的个数超过 \(1\) 时,说明这 \(2\) 个点不能互相到达,此时答案为 \(0\)

时间复杂度 \(\operatorname{O}(n)\)


\(\rm Code\)

const int MAXN = 1e4 + 5;
const int MAXM = 5e4 + 5;

int cnt, Time, tot;
int head[MAXN], dfn[MAXN], low[MAXN], c[MAXN], sum[MAXN], out[MAXN];
bool ins[MAXN];
stack<int> sta;

struct edge
{
	int to, nxt;
}e[MAXM];

void add(int u, int v)
{
	e[++cnt] = edge{v, head[u]};
	head[u] = cnt;
}

void tarjan(int u)
{
	dfn[u] = low[u] = ++Time;
	sta.push(u);
	ins[u] = true;
	for (re int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (!dfn[v])
		{
			tarjan(v);
			low[u] = min2(low[u], low[v]);
		}
		else if (ins[u])
		{
			low[u] = min2(low[u], dfn[v]);
		}
	}
	if (dfn[u] == low[u])
	{
		tot++;
		int v = 0;
		while (u != v)
		{
			v = sta.top();
			sta.pop();
			ins[v] = false;
			c[v] = tot;
			sum[tot]++;
		}
	}
}

int main()
{
	int n = read(), m = read();
	for (re int i = 1; i <= m; i++)
	{
		int u = read(), v = read();
		add(u, v);
	}
	for (re int i = 1; i <= n; i++)
	{
		if (!dfn[i])
		{
			tarjan(i);
		}
	}
	for (re int u = 1; u <= n; u++) //缩点
	{
		for (re int i = head[u]; i; i = e[i].nxt)
		{
			int v = e[i].to;
			if (c[u] != c[v])
			{
				out[c[u]]++;
			}
		}
	}
	int cnt = 0, ans = 0;
	for (re int i = 1; i <= tot; i++)
	{
		if (!out[i]) //出度为0
		{
			if (cnt++) //超过1个
			{
				putchar('0');
				return 0;
			}
			ans = sum[i];
		}
	}
	write(ans);
	return 0;
}

T4

有一棵 \(n\) 个点的树,每条边有边权,定义 \(f(i,j)\) 为点 \(i\) 到点 \(j\) 的路径上所有边的边权的按位与(\(\rm and\) 值),\(g(i,j)\) 为点 \(i\) 到点 \(j\) 的路径上所有边的边权的按位或(\(\rm or\) 值),求 \(\sum\limits_{i=1}^n\sum\limits_{j=i+1}^n f(i,j)\times g(i,j)\) 的值。

思路

对于 \(20\%\) 的数据:

  • 直接暴力即可。

对于 \(50\%\) 的数据:

  • \(\rm dfs\) 时递推,假设点 \(u\) 与点 \(fa(u)\) 直接的边权为 \(w(x)\)\(dep(u)\ge dep(v)\),则 \(f(u,v)=f(fa(u),v)\,\text{and}\,w(x),g(u,v)=g(fa(u),v)\,\text{or}\,w(x)\),然后 \(\operatorname{O}(n^2)\) 枚举 \(u\)\(v\) 即可。

对于 \(100\%\) 的数据:

在二进制下:

           	1   0   1
	x      	0   1   1
------------------------------
  			1   0   1
  		1	0   1
	0	0	0
------------------------------
	0	1	1	1	1

根据乘法分配律可以发现,对于第 \(i+j\) 位,设 \(cntf=\)\(i\) 位为 \(1\)\(f(i,j)\) 数量,\(cntg\) 为第 \(j\) 位为 \(1\)\(g(i,j)\) 数量,则这一位上的值应该是 \(cntf\times cntg\);又因为是第 \(i+j\) 位,所以它的总贡献就是 \(cntf\times cntg\times 2^{i+j}\)

要让 \(f(u,v)\) 的第 \(i\) 位为 \(1\),则这条路径上的所有边权的第 \(i\) 位都要为 \(1\),所以我们只保留边权的第 \(i\) 位为 \(1\) 的边,那么只有在每个连通块内的任意两点 \(<u,v>\) 满足 \(f(u,v)\) 的第 \(i\) 位为 \(1\)

对于第 \(j\) 位为 \(1\)\(g(u,v)\)\(\rm or\) 运算不好处理为 \(1\) 的情况,但是可以处理为 \(0\) 的情况,容斥原理得:第 \(i\) 位和第 \(j\) 为都为 \(1\)\(=\)\(i\) 位为 \(1\)\(-\)\(i\) 位为 \(1\),第 \(j\) 位为 \(0\) 的。

那么我们只保留第 \(i\) 位为 \(1\),第 \(j\) 位为 \(0\) 的就行了。

时间复杂度 \(\operatorname{O}(400n)\)


\(\text{Code}\)

const int MAXN = 5e4 + 5;
const int MOD = 998244353;

int n;
int fa[MAXN], siz[MAXN], u[MAXN], v[MAXN], w[MAXN];

void init()
{
	for (re int i = 1; i <= n; i++)
	{
		fa[i] = i;
		siz[i] = 1;
	}
}

int find(int x)
{
	return x == fa[x] ? x : fa[x] = find(fa[x]);
}

void merge(int x, int y)
{
	x = find(x), y = find(y);
	if (x != y)
	{
		fa[y] = x;
		siz[x] += siz[y];
	}
}

signed main()
{
	n = read();
	for (re int i = 1; i < n; i++)
	{
		u[i] = read(), v[i] = read(), w[i] = read();
	}
	int ans = 0;
	for (re int i = 0; i <= 20; i++)
	{
		for (re int j = 0; j <= 20; j++)
		{
			int cnt1 = 0, cnt2 = 0;
			init();
			for (re int k = 1; k < n; k++)
			{
				if (w[k] & (1 << i))
				{
					merge(u[k], v[k]);
				}
			}
			for (re int k = 1; k <= n; k++)
			{
				if (k == find(k))
				{
					cnt1 = (cnt1 + (siz[k] - 1) * siz[k] / 2 % MOD) % MOD;
				}
			}
			init();
			for (re int k = 1; k < n; k++)
			{
				if (w[k] & (1 << i))
				{
					if (!(w[k] & (1 << j))) //第i位为1,第j位为0
					{
						merge(u[k], v[k]);
					}
				}
			}
			for (re int k = 1; k <= n; k++)
			{
				if (k == find(k))
				{
					cnt2 = (cnt2 + (siz[k] - 1) * siz[k] / 2 % MOD) % MOD;
				}
			}
			ans = (ans + (1ll /*写成1会WA*/ << (i + j)) % MOD * (cnt1 - cnt2 + MOD) % MOD) % MOD;
		}
	}
	write(ans);
	return 0;
}
posted @ 2021-08-07 18:11  mango09  阅读(39)  评论(0编辑  收藏  举报
-->