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;
}