[Codeforces] combinatorics (R1600) Part.7
题单:https://codeforces.com/problemset?tags=combinatorics,1201-1600
1534C. Little Alawn’s Puzzle
原题指路:https://codeforces.com/problemset/problem/1534/C
题意 ( 2.5 s 2.5\ \mathrm{s} 2.5 s)
称一个 2 × n 2\times n 2×n的整型矩阵是好的,如果其两行分别都是 1 ∼ n 1\sim n 1∼n的排列,且同一列的两个数不同.给定一个好的矩阵.现有操作:交换同一列的两个数.问若干次操作后使得矩阵仍是好的的方案数,答案对 1 e 9 + 7 1\mathrm{e}9+7 1e9+7取模.
有 t ( 1 ≤ t ≤ 1 e 4 ) t\ \ (1\leq t\leq 1\mathrm{e}4) t (1≤t≤1e4)组测试数据.每组测试数据第一行输入一个整数 n ( 2 ≤ n ≤ 4 e 5 ) n\ \ (2\leq n\leq 4\mathrm{e}5) n (2≤n≤4e5).接下来输入一个 2 × n 2\times n 2×n的好的矩阵.数据保证所有测试数据的 n n n之和不超过 4 e 5 4\mathrm{e}5 4e5.
思路
注意到交换一列的两个数后,为使得同一行的数不同,还需交换若干列的两个数,显然这样的关系构成一个循环置换.用并查集统计置换环的个数 c n t cnt cnt,则 a n s = 2 c n t ans=2^{cnt} ans=2cnt.
代码
const int MAXN = 4e5 + 5;
const int MOD = 1e9 + 7;
namespace DSU { // 求置换环
int n; // 元素个数
int fa[MAXN]; // 下标从1开始
void init() {
for (int i = 1; i <= n; i++) fa[i] = i;
}
int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void merge(int x, int y) {
if ((x = find(x)) != (y = find(y))) fa[x] = y;
}
int countComponent() {
int res = 0;
for (int i = 1; i <= n; i++) res += fa[i] == i;
return res;
}
}
using namespace DSU;
void solve() {
cin >> n;
vi a(n);
for (auto& ai : a) cin >> ai;
init();
for (int i = 0; i < n; i++) {
int b; cin >> b;
merge(a[i], b);
}
cout << qpow(2, countComponent(), MOD) << endl;
}
int main() {
CaseT // 单测时注释掉该行
solve();
}
1552B. Running for Gold
原题指路:https://codeforces.com/problemset/problem/1552/B
题意
编号 1 ∼ n 1\sim n 1∼n的 n n n个运动员参加了编号 1 ∼ 5 1\sim 5 1∼5的 5 5 5场比赛,其中 i ( 1 ≤ i ≤ n ) i\ (1\leq i\leq n) i (1≤i≤n)号运动员在第 j ( 1 ≤ j ≤ 5 ) j\ \ (1\leq j\leq 5) j (1≤j≤5)比赛的排名为 r i j r_{ij} rij.称 x x x号运动员优于 y y y号运动员,如果至少有 3 3 3场比赛中 x x x号运动员的排名在 y y y号运动员之前.若一个运动员优于其他所有运动员,则他可以夺金.求最终夺金的运动员的编号,无解输出 − 1 -1 −1.
有 t ( 1 ≤ t ≤ 1000 ) t\ \ (1\leq t\leq 1000) t (1≤t≤1000)组测试数据.每组测试数据第一行输入一个整数 n ( 1 ≤ n ≤ 5 e 4 ) n\ \ (1\leq n\leq 5\mathrm{e}4) n (1≤n≤5e4).接下来输入一个 n × 5 n\times 5 n×5的整型矩阵,其中第 i ( 1 ≤ i ≤ n ) i\ \ (1\leq i\leq n) i (1≤i≤n)行的元素分别为 r i 1 , r i 2 , r i 3 , r i 4 , r i 5 ( 1 ≤ r i j ≤ n ) r_{i1},r_{i2},r_{i3},r_{i4},r_{i5}\ \ (1\leq r_{ij}\leq n) ri1,ri2,ri3,ri4,ri5 (1≤rij≤n).数据保证所有测试数据的 n n n之和不超过 5 e 4 5\mathrm{e}4 5e4.
思路I
至多只会有一个运动员夺金.
[证] 若不然,则至少有两个运动员 x x x和 y y y夺金,此时 x x x不优于 y y y,与 x x x夺金的条件矛盾.
1 1 1号运动员与其他运动员两两比较,找到一个可能夺金的运动员,即排名在比较过程中始终领先的运动员,再检查该运动员是否优于其他运动员即可.
代码I
struct Rank {
int rank[5];
bool operator<(const Rank& B) {
int cnt = 0;
for (int i = 0; i < 5; i++) cnt += rank[i] < B.rank[i];
return cnt >= 3;
}
};
void solve() {
int n; cin >> n;
vector<Rank> a(n);
for (int i = 0; i < n; i++)
for (int j = 0; j < 5; j++) cin >> a[i].rank[j];
int gold = 0; // 夺金候选人
for (int i = 1; i < n; i++)
if (a[i] < a[gold]) gold = i;
// 检查gold号运动员是否夺金
for (int i = 0; i < n; i++) {
if (gold != i && a[i] < a[gold]) {
cout << -1 << endl;
return;
}
}
cout << gold + 1 << endl;
}
int main() {
CaseT // 单测时注释掉该行
solve();
}
思路II
对每个运动员 i i i,随机遍历其他运动员,直至找到一个优于 i i i的运动员.下证这样的时间复杂度为 O ( n log n ) O(n\log n) O(nlogn).
[引理] 对 ∀ k ≥ 0 \forall k\geq 0 ∀k≥0,至多有 ( 2 k + 1 ) (2k+1) (2k+1)个运动员优于至少 ( n − k − 1 ) (n-k-1) (n−k−1)个其他运动员.
[引.证] 设有 m m m个运动员优于至少 ( n − k − 1 ) (n-k-1) (n−k−1)个其他运动员.
考察一个包含编号 1 ∼ m 1\sim m 1∼m的 m m m个节点的有向边图,其中存在边 i → j i\rightarrow j i→j当且仅当运动员 i i i优于运动员 j j j.
每个节点的出度 ≥ m − k − 1 \geq m-k-1 ≥m−k−1,则总边数 ≥ m ( m − k − 1 ) \geq m(m-k-1) ≥m(m−k−1).
注意到包含 m m m个节点的有向图至多 C m 2 C_m^2 Cm2条边,则 m ( m − k − 1 ) ≤ m ( m − 1 ) 2 m(m-k-1)\leq \dfrac{m(m-1)}{2} m(m−k−1)≤2m(m−1),即 m ≤ 2 k + 1 m\leq 2k+1 m≤2k+1.
[证] 考察一个恰优于其他 ( n − k − 1 ) (n-k-1) (n−k−1)个运动员的运动员 i i i,计算确定 i i i是否夺金所需的期望比较次数.
① k = 0 k=0 k=0时, i i i号运动员需与其他 ( n − 1 ) (n-1) (n−1)个运动员比较,期望比较次数为 O ( n ) O(n) O(n).
② k ≥ 1 k\geq 1 k≥1时,从 1 ∼ n 1\sim n 1∼n中随机选 k k k个数,抽到排名最靠前的运动员的期望步数为 O ( n k ) O\left(\dfrac{n}{k}\right) O(kn).
时间复杂度 O ( q 0 ⋅ n + ∑ k = 1 n − 1 q k ⋅ n k ) = O ( n ( q 0 + ∑ k = 1 n − 1 q k k ) ) \displaystyle O\left(q_0\cdot n+\sum_{k=1}^{n-1} q_k\cdot \dfrac{n}{k}\right)=O\left(n\left(q_0+\sum_{k=1}^{n-1} \dfrac{q_k}{k}\right)\right) O(q0⋅n+k=1∑n−1qk⋅kn)=O(n(q0+k=1∑n−1kqk)),
其中 q k q_k qk为恰优于其他 ( n − k − 1 ) (n-k-1) (n−k−1)个运动员的运动员的个数.
设 s k = q 0 + ⋯ + a k s_k=q_0+\cdots+a_k sk=q0+⋯+ak,则时间复杂度 O ( n ( s 0 + ∑ k = 1 n − 1 s k − s k − 1 k ) ) = O ( n ( ∑ k = 1 n − 2 s k k ( k + 1 ) + s n − 1 n − 1 ) ) \displaystyle O\left(n\left(s_0+\sum_{k=1}^{n-1} \dfrac{s_k-s_{k-1}}{k}\right)\right)=O\left(n\left(\sum_{k=1}^{n-2} \dfrac{s_k}{k(k+1)}+\dfrac{s_{n-1}}{n-1}\right)\right) O(n(s0+k=1∑n−1ksk−sk−1))=O(n(k=1∑n−2k(k+1)sk+n−1sn−1)).
由引理知: s k ≤ 2 k + 1 s_k\leq 2k+1 sk≤2k+1,故时间复杂度 ≤ O ( n ( ∑ k = 1 n − 2 2 k + 1 k ( k + 1 ) + 2 n − 1 n − 1 ) ) = O ( 2 n ∑ k = 1 n − 2 1 k ) = O ( n log n ) \displaystyle \leq O\left(n\left(\sum_{k=1}^{n-2} \dfrac{2k+1}{k(k+1)}+\dfrac{2n-1}{n-1}\right)\right)=O\left(2n\sum_{k=1}^{n-2}\dfrac{1}{k}\right)=O(n\log n) ≤O(n(k=1∑n−2k(k+1)2k+1+n−12n−1))=O(2nk=1∑n−2k1)=O(nlogn).
代码II
struct Rank {
int rank[5];
bool operator<(const Rank& B) {
int cnt = 0;
for (int i = 0; i < 5; i++) cnt += rank[i] < B.rank[i];
return cnt >= 3;
}
};
void solve() {
srand(time(0));
int n; cin >> n;
vector<Rank> a(n);
for (int i = 0; i < n; i++)
for (int j = 0; j < 5; j++) cin >> a[i].rank[j];
vi p(n); // 置换
for (int i = 0; i < n; i++) p[i] = i;
random_shuffle(all(p));
for (int i = 0; i < n; i++) {
bool gold = true; // 记录i号运动员能否夺金
for (int j = 0; j < n && gold; j++)
if (i != j) gold &= a[p[i]] < a[p[j]];
if (gold) {
cout << p[i] + 1 << endl;
return;
}
}
cout << -1 << endl;
}
int main() {
CaseT // 单测时注释掉该行
solve();
}
1567C. Carrying Conundrum
原题指路:https://codeforces.com/problemset/problem/1567/C
题意 ( 2 s 2\ \mathrm{s} 2 s)
考虑如上图所示的加法进位模式,即进位到当前列左边的第二列.给定一个正整数 n n n,求用上述方式会求得 a + b = n a+b=n a+b=n的有序正整数对 ( a , b ) (a,b) (a,b)的个数,其中 ( a , b ) (a,b) (a,b)与 ( b , a ) (b,a) (b,a)不同当且仅当 a ≠ b a\neq b a=b.
有 t ( 1 ≤ t ≤ 1000 ) t\ \ (1\leq t\leq 1000) t (1≤t≤1000)组测试数据.每组测试数据输入一个整数 n ( 2 ≤ n ≤ 1 e 9 ) n\ \ (2\leq n\leq 1\mathrm{e}9) n (2≤n≤1e9).
思路I
注意到上述加法进位模式事实上独立了奇数位和偶数位的运算,设 n n n的奇数位表示的数为 x x x,偶数位表示的数为 y y y.注意到将一个正整数 m m m拆分为两个非负整数之和有 ( m + 1 ) (m+1) (m+1)种方案,则 a n s = ( x + 1 ) ( y + 1 ) − 2 ans=(x+1)(y+1)-2 ans=(x+1)(y+1)−2,其中 − 2 -2 −2是因为两个数对 ( a , b ) (a,b) (a,b)的第一项同为 0 0 0或第二项同为 0 0 0时不合法.
代码I
void solve() {
string s; cin >> s;
int n = s.length();
int x = 0, y = 0;
for (int i = 0; i < n; i += 2) x = x * 10 + s[i] - '0';
for (int i = 1; i < n; i += 2) y = y * 10 + s[i] - '0';
cout << (x + 1) * (y + 1) - 2 << endl;
}
int main() {
CaseT // 单测时注释掉该行
solve();
}
1569C. Jury Meeting
原题指路:https://codeforces.com/problemset/problem/1569/C
题意 ( 2 s 2\ \mathrm{s} 2 s)
编号 1 ∼ n 1\sim n 1∼n的 n n n个人开会,其中 i ( 1 ≤ i ≤ n ) i\ \ (1\leq i\leq n) i (1≤i≤n)号人有 a i a_i ai个问题需要提出.现用一个 1 ∼ n 1\sim n 1∼n的排列 p p p来确定他们的发言顺序,轮到对应的人时,若他还有问题需要提出,则提出一个问题;否则跳过.重复该过程直至所有人的问题都提出完.称一个排列 p p p是好的,如果以 p p p为发言顺序时不存在一个人连续提出问题.求好的排列的个数,答案对 998244353 998244353 998244353取模.
有 t ( 1 ≤ t ≤ 1 e 4 ) t\ \ (1\leq t\leq 1\mathrm{e}4) t (1≤t≤1e4)组测试数据.每组测试数据第一行输入一个整数 n ( 2 ≤ n ≤ 2 e 5 ) n\ \ (2\leq n\leq 2\mathrm{e}5) n (2≤n≤2e5).第二行输入 n n n个整数 a 1 , ⋯ , a n ( 1 ≤ a i ≤ 1 e 9 ) a_1,\cdots,a_n\ \ (1\leq a_i\leq 1\mathrm{e}9) a1,⋯,an (1≤ai≤1e9).数据保证所有测试数据的 n n n之和不超过 2 e 5 2\mathrm{e}5 2e5.
思路
①若序列中的最大值的出现次数 ≥ 2 \geq 2 ≥2,显然任一排列都是好的, a n s = n ! ans=n! ans=n!.
②若序列中的最大值只出现一次,设它在下标 x x x处出现.
在第 a x a_x ax轮时只有一个人提出问题,在第 ( a x − 1 ) (a_x-1) (ax−1)轮时至少有一个人在第 x x x个人之后提出问题.
设序列中值为 ( a x − 1 ) (a_x-1) (ax−1)的元素有 k k k个,显然序列中值不为 a x a_x ax或 ( a x − 1 ) (a_x-1) (ax−1)的元素可任意排列,有 A n n − k − 1 = n ! ( k + 1 ) ! A_n^{n-k-1}=\dfrac{n!}{(k+1)!} Ann−k−1=(k+1)!n!种情况.
若 k k k个值为 ( a x − 1 ) (a_x-1) (ax−1)的人中至少有一个在第 x x x个人之后提出问题,则排列是好的.
下面计算不好的排列的个数.显然排列是不好的当且仅当值为 a x a_x ax的人排在最后,有 k ! k! k!种情况.
故不好的排列有 n ! ( k + 1 ) ! ⋅ k ! = n ! k + 1 \dfrac{n!}{(k+1)!}\cdot k!=\dfrac{n!}{k+1} (k+1)!n!⋅k!=k+1n!个,则 a n s = n ! − n ! k + 1 ans=n!-\dfrac{n!}{k+1} ans=n!−k+1n!.
代码
const int MAXN = 2e5 + 5;
const int MOD = 998244353;
int fac[MAXN];
void init() {
fac[0] = 1;
for (int i = 1; i < MAXN; i++) fac[i] = (ll)fac[i - 1] * i % MOD;
}
void solve() {
int n; cin >> n;
vi a(n);
for (auto& ai : a) cin >> ai;
int maxnum = *max_element(all(a));
if (count(all(a), maxnum) >= 2) {
cout << fac[n] << endl;
return;
}
int k = count(all(a), maxnum - 1);
cout << (((ll)fac[n] - (ll)fac[n] * qpow(k + 1, MOD - 2, MOD)) % MOD + MOD) % MOD << endl;
}
int main() {
init();
CaseT // 单测时注释掉该行
solve();
}
1594E1. Rubik’s Cube Coloring (easy version)
原题指路:https://codeforces.com/problemset/problem/1594/E1
题意 ( 2 s 2\ \mathrm{s} 2 s)
用如上图所示的魔方的六种颜色给一棵包含 ( 2 k − 1 ) (2^k-1) (2k−1)个节点的满二叉树的节点染色,要求任意两相邻的节点染的颜色在魔方上也相邻,求染色方案数,答案对 1 e 9 + 7 1\mathrm{e}9+7 1e9+7取模.
第一行输入一个整数 k ( 1 ≤ k ≤ 60 ) k\ \ (1\leq k\leq 60) k (1≤k≤60).
思路I
根节点有 6 6 6种选择,根节点颜色确定后从上往下每个节点有 4 4 4种选择,故 a n s = 6 ⋅ 4 2 k − 2 ans=6\cdot 4^{2^k-2} ans=6⋅42k−2.
代码I
const int MOD = 1e9 + 7;
void solve() {
int k; cin >> k;
cout << (ll)6 * qpow(4, ((ll)1 << k) - 2, MOD) % MOD;
}
int main() {
solve();
}
思路II
通过颜色是否不同、颜色之和是否不为 7 7 7判断颜色能否相邻:①白色为 1 1 1,黄色为 6 6 6;②绿色为 2 2 2,蓝色为 5 5 5;③红色为 3 3 3,橙色为 4 4 4.
d p [ i ] [ j ] dp[i][j] dp[i][j]表示深度为 i i i的满二叉树根节点染 j ∈ [ 1 , 6 ] j\in [1,6] j∈[1,6]色的方案数,则 a n s = ∑ j = 1 6 d p [ k ] [ j ] \displaystyle ans=\sum_{j=1}^6 dp[k][j] ans=j=1∑6dp[k][j].
状态转移即枚举深度、根节点颜色、左儿子节点颜色、右儿子节点颜色,初始条件 d p [ 1 ] [ j ] = 1 ( j = 1 , ⋯ , 6 ) dp[1][j]=1\ \ (j=1,\cdots,6) dp[1][j]=1 (j=1,⋯,6).
代码II
const int MAXN = 65;
const int MOD = 1e9 + 7;
int dp[MAXN][7]; // dp[i][j]表示深度为i的满二叉树根节点染j色的方案数
void solve() {
int n; cin >> n;
for (int i = 1; i <= 6; i++) dp[1][i] = 1; // 初始条件
for (int i = 2; i <= n; i++) { // 枚举深度
for (int j = 1; j <= 6; j++) { // 枚举根节点颜色
for (int k = 1; k <= 6; k++) { // 枚举左儿子节点的颜色
if (j != k && j + k != 7) {
for (int l = 1; l <= 6; l++) { // 枚举右儿子节点的颜色
if (j != l && j + l != 7)
dp[i][j] = ((ll)dp[i][j] + (ll)dp[i - 1][k] * dp[i - 1][l] % MOD) % MOD;
}
}
}
}
}
int ans = 0;
for (int i = 1; i <= 6; i++) ans = (ans + dp[n][i]) % MOD;
cout << ans;
}
int main() {
solve();
}
1614C. Divan and bitwise operations
原题指路:https://codeforces.com/problemset/problem/1614/C
题意
对一个长度为 n n n的序列 a 1 , ⋯ , a n a_1,\cdots,a_n a1,⋯,an,定义其权值为其所有非空子列的元素异或和之和.,给定其中 m m m个区间的元素或值,每个元素至少包含在一个区间中.求 a [ ] a[] a[]的权值,答案对 1 e 9 + 7 1\mathrm{e}9+7 1e9+7取模.若有多组解,输出任一组解.
有 t ( 1 ≤ t ≤ 1000 ) t\ \ (1\leq t\leq 1000) t (1≤t≤1000)组测试数据.每组测试数据第一行输入两个整数 n , m ( 1 ≤ n , m ≤ 2 e 5 ) n,m\ \ (1\leq n,m\leq 2\mathrm{e}5) n,m (1≤n,m≤2e5).接下来 m m m行每行输入三个整数 l , r , x ( 1 ≤ l ≤ r ≤ n , 0 ≤ x ≤ 2 30 − 1 ) l,r,x\ \ (1\leq l\leq r\leq n,0\leq x\leq 2^{30}-1) l,r,x (1≤l≤r≤n,0≤x≤230−1).数据保证序列 a 1 , ⋯ , a n a_1,\cdots,a_n a1,⋯,an的每个元素至少包含在一个区间中,且至少存在一个序列满足要求,所有测试数据的 n n n之和、 m m m之和都不超过 2 e 5 2\mathrm{e}5 2e5.
思路
考察所有元素的二进制表示的每一位对答案的贡献.
对二进制第 i i i位,若没有元素在该位为 1 1 1,则该位对答案无贡献;否则该位包含奇数个 1 1 1的子区间会对答案产生贡献.
设 x x x为序列所有元素的或,亦即所有子区间的或.
下面证明二进制第 i i i位包含奇数个 1 1 1的子列有 2 n − 1 2^{n-1} 2n−1个,则 a n s = x ⋅ 2 n − 1 ans=x\cdot 2^{n-1} ans=x⋅2n−1.
[证] 将所有元素分为两个集合 A A A和 B B B,其中 A A A中的元素的二进制表示的第 i i i位都为 0 0 0, B B B中的元素的二进制表示的第 i i i位都为 1 1 1.
①取 A A A的任一子集都不影响二进制第 i i i位 1 1 1的出现次数的奇偶性,故不影响答案,有 2 ∣ A ∣ 2^{|A|} 2∣A∣种取法.
②令 k = [ ∣ B ∣ 是偶数 ] k=[|B|是偶数] k=[∣B∣是偶数].为使得二进制第 i i i位 1 1 1的出现奇数次,应从 B B B中取奇数个元素,
有 C ∣ B ∣ 1 + C ∣ B ∣ 3 + ⋯ + C ∣ B ∣ ∣ B ∣ − k = 2 ∣ B ∣ − 1 C_{|B|}^1+C_{|B|}^3+\cdots+C_{|B|}^{|B|-k}=2^{|B|-1} C∣B∣1+C∣B∣3+⋯+C∣B∣∣B∣−k=2∣B∣−1种取法.
综上,二进制第 i i i位包含奇数个 1 1 1的子列有 2 ∣ A ∣ ⋅ 2 ∣ B ∣ − 1 = 2 n − 1 2^{|A|}\cdot 2^{|B|-1}=2^{n-1} 2∣A∣⋅2∣B∣−1=2n−1个.
代码
const int MOD = 1e9 + 7;
void solve() {
int n; cin >> n;
int tmp = 0; // 序列所有元素的或
CaseT{
int l, r, x; cin >> l >> r >> x;
tmp |= x;
}
cout << (ll)tmp * qpow(2, n - 1, MOD) % MOD << endl;
}
int main() {
CaseT // 单测时注释掉该行
solve();
}
1648A. Weird Sum
原题指路:https://codeforces.com/problemset/problem/1648/A
题意 ( 2 s 2\ \mathrm{s} 2 s)
给定一个 n × m n\times m n×m的表格,每个格子有一种颜色,每种颜色用一个 1 ∼ 1 e 5 1\sim 1\mathrm{e}5 1∼1e5的整数表示.对所有颜色,求任意两相同颜色的格子间的Manhattan距离之和.
第一行输入两个整数 n , m ( 1 ≤ n ≤ m , 1 ≤ n m ≤ 1 e 5 ) n,m\ \ (1\leq n\leq m,1\leq nm\leq 1\mathrm{e}5) n,m (1≤n≤m,1≤nm≤1e5).接下来输入一个 n × m n\times m n×m的元素范围为 [ 1 , 1 e 5 ] [1,1\mathrm{e}5] [1,1e5]的整型矩阵,表示表格的染色情况.
思路I
设某种颜色有 ( r 0 , c 0 ) , ⋯ , ( r k − 1 , c k − 1 ) (r_0,c_0),\cdots,(r_{k-1},c_{k-1}) (r0,c0),⋯,(rk−1,ck−1)的格子,则该颜色对答案的贡献为 ∑ i = 0 k − 1 ∑ j = i + 1 k − 1 ( ∣ r i − r j ∣ + ∣ c i − c j ∣ ) = ( ∑ i = 0 k − 1 ∑ j = i + 1 k − 1 ∣ r i − r j ∣ ) + ( ∑ i = 0 k − 1 ∑ j = i + 1 k − 1 ∣ c i − c j ∣ ) \displaystyle \sum_{i=0}^{k-1}\sum_{j=i+1}^{k-1}(|r_i-r_j|+|c_i-c_j|)=\left(\sum_{i=0}^{k-1} \sum_{j=i+1}^{k-1} |r_i-r_j|\right)+\left(\sum_{i=0}^{k-1} \sum_{j=i+1}^{k-1} |c_i-c_j|\right) i=0∑k−1j=i+1∑k−1(∣ri−rj∣+∣ci−cj∣)=(i=0∑k−1j=i+1∑k−1∣ri−rj∣)+(i=0∑k−1j=i+1∑k−1∣ci−cj∣).
下面讨论如何计算第一个和式,第二个同理.设 s [ ] s[] s[]是 r [ ] r[] r[]非降序排列的后的结果,
则 ∑ i = 0 k − 1 ∑ j = i + 1 k − 1 ∣ r i − r j ∣ = ∑ i = 0 k − 1 ∑ j = i + 1 k − 1 ( s j − s i ) = ( ∑ i = 0 k − 1 ∑ j = i + 1 k − 1 s j ) + ( ∑ i = 0 k − 1 ∑ j = i + 1 k − 1 − s i ) \displaystyle \sum_{i=0}^{k-1}\sum_{j=i+1}^{k-1} |r_i-r_j|=\sum_{i=0}^{k-1}\sum_{j=i+1}^{k-1}(s_j-s_i)=\left(\sum_{i=0}^{k-1}\sum_{j=i+1}^{k-1} s_j\right)+\left(\sum_{i=0}^{k-1}\sum_{j=i+1}^{k-1} -s_i\right) i=0∑k−1j=i+1∑k−1∣ri−rj∣=i=0∑k−1j=i+1∑k−1(sj−si)=(i=0∑k−1j=i+1∑k−1sj)+(i=0∑k−1j=i+1∑k−1−si).
注意到第一个和式中 s j s_j sj出现 j j j次,第二个和式中 − s i -s_i −si出现 ( k − i − 1 ) (k-i-1) (k−i−1)次,
则上式 = ∑ j = 0 k − 1 j s j + ∑ i = 0 k − 1 − ( k − i − 1 ) s i = ∑ i = 0 k − 1 ( 2 i − k + 1 ) s i \displaystyle =\sum_{j=0}^{k-1} js_j+\sum_{i=0}^{k-1} -(k-i-1)s_i=\sum_{i=0}^{k-1} (2i-k+1)s_i =j=0∑k−1jsj+i=0∑k−1−(k−i−1)si=i=0∑k−1(2i−k+1)si.
代码I
const int MAXN = 1e5 + 5;
int n, m;
vi row[MAXN], col[MAXN]; // 每种颜色所在的行、列
void solve() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int c; cin >> c;
row[c].push_back(i), col[c].push_back(j);
}
}
for (int c = 1; c < MAXN; c++) sort(all(row[c])), sort(all(col[c]));
ll ans = 0;
for (int c = 1; c < MAXN; c++) { // 枚举颜色
int k = row[c].size();
for (int i = 0; i <= k - 1; i++) ans += (ll)(2 * i - k + 1) * row[c][i];
for (int i = 0; i <= k - 1; i++) ans += (ll)(2 * i - k + 1) * col[c][i];
}
cout << ans;
}
int main() {
solve();
}
思路II
注意到每个 r i r_i ri对答案的贡献为 r i − r 1 + r i − r 2 + ⋯ + r i − r i − 1 = ( i − 1 ) r i − ( r 1 + ⋯ + r i − 1 ) r_i-r_1+r_i-r_2+\cdots+r_i-r_{i-1}=(i-1)r_i-(r_1+\cdots+r_{i-1}) ri−r1+ri−r2+⋯+ri−ri−1=(i−1)ri−(r1+⋯+ri−1),用前缀和优化即可.
代码II
const int MAXN = 1e5 + 5;
int n, m;
vl row[MAXN], col[MAXN]; // 每种颜色所在的行、列
vl pre(MAXN); // 前缀和
void solve() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int c; cin >> c;
row[c].push_back(i), col[c].push_back(j);
}
}
ll ans = 0;
for (int c = 1; c < MAXN; c++) { // 枚举颜色
sort(all(row[c]));
partial_sum(all(row[c]), pre.begin()); // 注意用此函数时原数组也要开ll
for (int i = 1; i < row[c].size(); i++) {
ans += (ll)row[c][i] * i; // 下标从0开始,故此处无需-1
ans -= pre[i - 1];
}
sort(all(col[c]));
partial_sum(all(col[c]), pre.begin());
for (int i = 1; i < col[c].size(); i++) {
ans += (ll)col[c][i] * i; // 下标从0开始,故此处无需-1
ans -= pre[i - 1];
}
}
cout << ans;
}
int main() {
solve();
}
1739C. Card Game
原题指路:https://codeforces.com/problemset/problem/1739/C
题意 ( 2 s 2\ \mathrm{s} 2 s)
有 n n n( n n n为偶数)张牌,每张牌上写着一个 1 ∼ n 1\sim n 1∼n的整数,每张牌上的数字相异.称写着数字 x x x的牌大于写着数字 y y y的牌,如果 x > y x>y x>y.A和B玩游戏,A先手.初始时两人各自拿到 n 2 \dfrac{n}{2} 2n张牌.每轮每人打出一张牌,此时若对手能打出一张更大的牌,则游戏继续;否则对手失败.双方都能打完手中的牌时游戏平局.给定偶数 n n n,两人都采取最优策略,分别求使得A胜、B胜、平均的初始时的分牌方式,答案对 998244353 998244353 998244353取模.
有 t ( 1 ≤ t ≤ 30 ) t\ \ (1\leq t\leq 30) t (1≤t≤30)组测试数据.每组测试数据输入一个偶数 n ( 2 ≤ n ≤ 60 ) n\ \ (2\leq n\leq 60) n (2≤n≤60).
思路I
平局只有一种情况:
(1)若A有值为 n n n的牌,则他初始时打出即获胜.故平局时值为 n n n的牌只能属于B.
(2)若B有值为 ( n − 1 ) (n-1) (n−1)的牌,则打第一次打出值为 n n n的牌,第二次打出值为 ( n − 1 ) (n-1) (n−1)的牌即获胜.
故平局时值为 ( n − 1 ) (n-1) (n−1)的牌只能属于A.
(3)若B有值为 ( n − 2 ) (n-2) (n−2)的牌,
①若A第一次打值为 ( n − 1 ) (n-1) (n−1)的牌,则B先打值为 n n n的牌,再打值为 ( n − 2 ) (n-2) (n−2)的牌即获胜.
②若A第一次打其他牌,则B先打值为 ( n − 2 ) (n-2) (n−2)的牌,再打值为 n n n的牌即获胜.
故要平局时值为 ( n − 2 ) (n-2) (n−2)的牌只能属于A.
用一个长度为 n n n的字符串 s s s表示值为 n ∼ 1 n\sim 1 n∼1的牌的分配方式则平局当且仅当 s = B A A B B A A B B ⋯ s=BAABBAABB\cdots s=BAABBAABB⋯.
设先手胜的方案数为 a n s ans ans,则后手胜的方案数为 C n n 2 − a n s − 1 C_n^{\frac{n}{2}}-ans-1 Cn2n−ans−1.
下面求先手胜的方案数.由平局的分析过程知:应从大到小、每两个一组地考虑 n ∼ 1 n\sim 1 n∼1的整数.
①若先手有当前最大的牌,则先手必胜,先手获胜的方案数增加 C n 2 − 1 − n − i 2 i − 1 C_{\frac{n}{2}-1-\frac{n-i}{2}}^{i-1} C2n−1−2n−ii−1,
即从剩下的牌中任选 ( i − 1 ) (i-1) (i−1)张作为先手的其他牌.
②若后手同时有当前最大的牌和次大的牌,则后手必胜,后手获胜方案数增加 C n 2 − n − i 2 i − 2 C_{\frac{n}{2}-\frac{n-i}{2}}^{i-2} C2n−2n−ii−2,
即从剩下的牌中任选 ( i − 2 ) (i-2) (i−2)张作为后手的其他牌.
③若先手有当前的次大牌,后手有当前的最大牌,最优策略是先手先打次大的牌,后手再打最大的牌,只有一种情况.
从大到小每两个一组的枚举 n ∼ 1 n\sim 1 n∼1,切换先手计算答案即可.
代码I
const int MAXN = 65;
const int MOD = 998244353;
int C[MAXN][MAXN]; // 组合数C(n,m)
void init() {
for (int i = 0; i < MAXN; i++) {
for (int j = 0; j <= i; j++) {
if (!j) C[i][j] = 1;
else C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % MOD;
}
}
}
void solve() {
int n; cin >> n;
int ans1 = 0; // 先手胜的方案数
for (int i = n, cur = 0; i >= 1; i -= 2, cur ^= 1) { // cur表示当前轮到的玩家,0为先手,1为后手
if (cur) ans1 = (ans1 + C[i - 2][n / 2 - (n - i) / 2]) % MOD;
else ans1 = (ans1 + C[i - 1][n / 2 - 1 - (n - i) / 2]) % MOD;
}
int ans2 = (C[n][n / 2] - ans1 - 1 + MOD) % MOD; // 后手胜的方案数
cout << ans1 << ' ' << ans2 << ' ' << 1 << endl;
}
int main() {
init();
CaseT // 单测时注释掉该行
solve();
}
思路II
同思路I,用一个字符串 s s s表示分配情况,平局当且仅当 s ′ = B A A B B A A B B ⋯ s'=BAABBAABB\cdots s′=BAABBAABB⋯.
d p [ x ] [ y ] [ t ] dp[x][y][t] dp[x][y][t]表示 s s s中有 x x x个A、 y y y个B,若当前 s s s的字符与 s ′ s' s′的对应字符相同,则 t = 0 t=0 t=0;若当前 s s s的字符与 s ′ s' s′的对应字符不同,且这会导致A胜,则 t = 1 t=1 t=1;否则导致B胜,则 t = 2 t=2 t=2.最终答案分别为 d p [ n 2 ] [ n 2 ] [ t ] ( t = 0 , 1 , 2 ) dp\left[\dfrac{n}{2}\right]\left[\dfrac{n}{2}\right][t]\ \ (t=0,1,2) dp[2n][2n][t] (t=0,1,2),初始条件 d p [ 0 ] [ 0 ] [ 2 ] = 1 dp[0][0][2]=1 dp[0][0][2]=1,方便加入第一张牌后确定A是否必胜.
代码II
const int MAXN = 65;
const int MOD = 998244353;
int dp[MAXN][MAXN][3]; // dp[x][y][t]表示s中有x个A、y个B,
// 若当前s的字符与s'的对应字符相同,则t=0
// 若当前s的字符与s'的对应字符不同,且这会导致A胜,则t=1
// 若当前s的字符与s'的对应字符不同,且这会导致B胜,则t=2
void solve() {
memset(dp, 0, so(dp));
int n; cin >> n;
dp[0][0][2] = 1; // 初始条件B必胜
for (int i = 0; i <= n / 2; i++) {
for (int j = 0; j <= n / 2; j++) {
for (int t = 0; t < 3; t++) {
int cur = (i + j) % 4; // 当前轮到的玩家
if (cur == 0 || cur == 3) {
if (i < n / 2)
dp[i + 1][j][t == 2 ? 0 : t] = (dp[i + 1][j][t == 2 ? 0 : t] + dp[i][j][t]) % MOD; // 当前字符为A
if (j < n / 2)
dp[i][j + 1][t] = (dp[i][j + 1][t] + dp[i][j][t]) % MOD; // 当前字符为B
}
else {
if (i < n / 2)
dp[i + 1][j][t] = (dp[i + 1][j][t] + dp[i][j][t]) % MOD; // 当前字符为A
if (j < n / 2)
dp[i][j + 1][t == 2 ? 1 : t] = (dp[i][j + 1][t == 2 ? 1 : t] + dp[i][j][t]) % MOD; // 当前字符为B
}
}
}
}
for (int i = 0; i < 3; i++) cout << dp[n / 2][n / 2][i] << " \n"[i == 2];
}
int main() {
CaseT // 单测时注释掉该行
solve();
}
截至2022.10.18,无新题目.