前言
总体评价:
T 1 \rm T1 T1:签到题,用桶乱♂搞♂
然而机房一堆人爆零
评分:10 \color{Red}\colorbox{White}{评分:10} 评分:10
T 2 \rm T2 T2:思维题,思路清奇
为什么我的
50
50
50 分暴力爆零了???
评分:60 \color{Red}\colorbox{White}{评分:60} 评分:60
T 3 \rm T3 T3:缩点乱搞
评分:30 \color{Red}\colorbox{White}{评分:30} 评分:30
T 4 \rm T4 T4:我 ∗ ∗ ** ∗∗你个大 ∗ ∗ ** ∗∗
评分:-100 \color{Red}\colorbox{White}{评分:-100} 评分:-100
成绩:
100 + 0 + 100 + 0 , r k 2 100+0+100+0,rk2 100+0+100+0,rk2
T 2 \rm T2 T2 暴力怎么炸了???!!!
T 4 \rm T4 T4 树剖一分都没骗到。
S o l u t i o n \rm Solution Solution
T1
UVA1368 DNA序列 DNA Consensus String
输入 n n n 个长度均为 m m m 的字符串,求一个字符串,该序列能满足到 n n n 个序列的总 H a r m m i n g \rm Harmming Harmming 距离最小,若相等,输出字典序最小的。两个等长字符串的 H a r m m i n g \rm Harmming Harmming 距离等于字符不同的的位置个数。
例如, A C G T \rm ACGT ACGT 和 G C G A \rm GCGA GCGA 的 H a r m m i n g \rm Harmming Harmming 距离为 2 2 2(位置 1 1 1、 4 4 4 的字符不相同)。
思路:
对于本题,遍历 i = 1 → m i=1\to m i=1→m,开桶记录 26 26 26 个字母在这 n n n 个字符串的第 i i i 位出现的次数,统计出现最多的字母,那么答案的第 i i i 位一定是它,剩下的加入 H a r m m i n g \rm Harmming Harmming 距离。
时间复杂度 O ( n m t ) \operatorname{O}(nmt) O(nmt)。
C o d e \rm Code 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 n n 行 m m m 列的矩阵。矩阵的第一行数字为 1 , 2 , 3 , ⋯ , m 1,2,3,\cdots,m 1,2,3,⋯,m,第二行数字为 m + 1 , m + 2 , ⋯ , 2 m m+1,m+2,\cdots,2m m+1,m+2,⋯,2m,…第 n n n 行数字为 ( n − 1 ) m + 1 , ( n − 1 ) m + 2 , ⋯ , n m (n-1)m+1,(n-1)m+2,\cdots,nm (n−1)m+1,(n−1)m+2,⋯,nm。
例如,对于 n = 3 , m = 4 n=3,m=4 n=3,m=4:
1 2 3 4
5 6 7 8
9 10 11 12
需要支持操作:将其中一行或一列的所有数乘以非负数,最后输出矩阵中所有值的总和 m o d 1 0 9 + 7 \mod 10^9+7 mod109+7。
思路:
因为同一列上相邻两个数相差 m m m,所以通过第 1 1 1 行可以求出第 2 2 2 行 ~ 第 n n n 行的和;
根据乘法结合律,若同一个数被乘了多次,则改变顺序后结果仍然相等,所以我们可以先把列的算了,再乘上行的。
设 l ( x ) l(x) l(x) 为第 x x x 列一共应该乘上的数, h ( x ) h(x) h(x) 为第 x x x 行一共应该乘上的数;
则第
i
i
i 行乘
l
(
i
)
l(i)
l(i) 后,
第
1
1
1 行的和为
b
=
∑
i
=
1
m
l
(
i
)
⋅
i
b=\sum\limits_{i=1}^ml(i)\cdot i
b=i=1∑ml(i)⋅i,第
i
i
i 行比第
i
−
1
i-1
i−1 行的和多
a
=
∑
i
=
1
m
l
(
i
)
⋅
m
a=\sum\limits_{i=1}^ml(i)\cdot m
a=i=1∑ml(i)⋅m,故此时第
i
i
i 行的和为
b
+
a
(
i
−
1
)
b+a(i-1)
b+a(i−1);再乘上
h
(
i
)
h(i)
h(i),最终第
i
i
i 行的和就是
[
b
+
a
(
i
−
1
)
]
⋅
h
(
i
)
[b+a(i-1)]\cdot h(i)
[b+a(i−1)]⋅h(i)。
时间复杂度 O ( n ) \operatorname{O}(n) O(n)。
Caution:此题数据十分毒瘤!!!不开 l o n g l o n g \rm long long longlong 见祖宗!!!每操作一次就要取模!!!
C o d e \rm Code 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
给定一张有向图,求有多少个节点,所有节点都可到达它。
思路:
首先,对于一个强连通子图内,若其中一个节点满足要求,则该 S C C \rm SCC SCC 内所有节点都满足要求,这是根据强连通子图的定义。当然,强连通子图满足,则 S C C \rm SCC SCC 也一定满足。
所以直接搞缩点,建新图的同时记录出度,那么答案一定就是出度为 0 0 0 的点所对应的 S C C \rm SCC SCC 中的所有节点,证明如下:
假设有一个点 u u u 出度 ≥ 1 \ge1 ≥1 且所有点都可到达它,则以 u u u 为起点的某一条有向边 < u , v > <u,v> <u,v>, v v v 也要能到达 u u u,则 u u u 和 v v v 能合并成一个更大的强连通子图,与 u u u 是 S C C \rm SCC SCC 矛盾。
还有一个问题,就是当出度为 0 0 0 的点的个数超过 1 1 1 时,说明这 2 2 2 个点不能互相到达,此时答案为 0 0 0。
时间复杂度 O ( n ) \operatorname{O}(n) O(n)。
C o d e \rm Code 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 n n 个点的树,每条边有边权,定义 f ( i , j ) f(i,j) f(i,j) 为点 i i i 到点 j j j 的路径上所有边的边权的按位与( a n d \rm and and 值), g ( i , j ) g(i,j) g(i,j) 为点 i i i 到点 j j j 的路径上所有边的边权的按位或( o r \rm or or 值),求 ∑ i = 1 n ∑ j = i + 1 n f ( i , j ) × g ( i , j ) \sum\limits_{i=1}^n\sum\limits_{j=i+1}^n f(i,j)\times g(i,j) i=1∑nj=i+1∑nf(i,j)×g(i,j) 的值。
思路
对于 20 % 20\% 20% 的数据:
- 直接暴力即可。
对于 50 % 50\% 50% 的数据:
- 在 d f s \rm dfs dfs 时递推,假设点 u u u 与点 f a ( u ) fa(u) fa(u) 直接的边权为 w ( x ) w(x) w(x) 且 d e p ( u ) ≥ d e p ( v ) dep(u)\ge dep(v) dep(u)≥dep(v),则 f ( u , v ) = f ( f a ( u ) , v ) and w ( x ) , g ( u , v ) = g ( f a ( u ) , v ) or w ( x ) f(u,v)=f(fa(u),v)\,\text{and}\,w(x),g(u,v)=g(fa(u),v)\,\text{or}\,w(x) f(u,v)=f(fa(u),v)andw(x),g(u,v)=g(fa(u),v)orw(x),然后 O ( n 2 ) \operatorname{O}(n^2) O(n2) 枚举 u u u 和 v v v 即可。
对于 100 % 100\% 100% 的数据:
在二进制下:
1 0 1
x 0 1 1
------------------------------
1 0 1
1 0 1
0 0 0
------------------------------
0 1 1 1 1
根据乘法分配律可以发现,对于第 i + j i+j i+j 位,设 c n t f = cntf= cntf= 第 i i i 位为 1 1 1 的 f ( i , j ) f(i,j) f(i,j) 数量, c n t g cntg cntg 为第 j j j 位为 1 1 1 的 g ( i , j ) g(i,j) g(i,j) 数量,则这一位上的值应该是 c n t f × c n t g cntf\times cntg cntf×cntg;又因为是第 i + j i+j i+j 位,所以它的总贡献就是 c n t f × c n t g × 2 i + j cntf\times cntg\times 2^{i+j} cntf×cntg×2i+j
要让 f ( u , v ) f(u,v) f(u,v) 的第 i i i 位为 1 1 1,则这条路径上的所有边权的第 i i i 位都要为 1 1 1,所以我们只保留边权的第 i i i 位为 1 1 1 的边,那么只有在每个连通块内的任意两点 < u , v > <u,v> <u,v> 满足 f ( u , v ) f(u,v) f(u,v) 的第 i i i 位为 1 1 1。
对于第 j j j 位为 1 1 1 的 g ( u , v ) g(u,v) g(u,v), o r \rm or or 运算不好处理为 1 1 1 的情况,但是可以处理为 0 0 0 的情况,容斥原理得:第 i i i 位和第 j j j 为都为 1 1 1 的 = = = 第 i i i 位为 1 1 1 的 − - − 第 i i i 位为 1 1 1,第 j j j 位为 0 0 0 的。
那么我们只保留第 i i i 位为 1 1 1,第 j j j 位为 0 0 0 的就行了。
时间复杂度 O ( 400 n ) \operatorname{O}(400n) O(400n)。
Code \text{Code} 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;
}