2021CCPC上海省赛题解ABCDEGHIJK

A. 小 A 的点面论

题意

给定两相异的非零向量(x1,y1,z1),(x2,y2,z2)(0≤xi,yi,zi≤10)(x_1,y_1,z_1),(x_2,y_2,z_2)\ \ (0\leq x_i,y_i,z_i\leq 10)(x1​,y1​,z1​),(x2​,y2​,z2​)  (0≤xi​,yi​,zi​≤10),求一个向量(x,y,z)(−200≤x,y,z≤200)(x,y,z)\ \ (-200\leq x,y,z\leq 200)(x,y,z)  (−200≤x,y,z≤200)垂直于这两个向量.上述xi,yi,zi,x,y,z∈Zx_i,y_i,z_i,x,y,z\in\mathbb{Z}xi​,yi​,zi​,x,y,z∈Z.

思路I

输出(x1,y1,z1)×(x2,y2,z2)=(∣y1z1y2z2∣,−∣x1z1x2z2∣,∣x1y1x2y2∣)(x_1,y_1,z_1)\times (x_2,y_2,z_2)=\left(\begin{vmatrix}y_1 & z_1 \\ y_2&z_2\end{vmatrix},-\begin{vmatrix}x_1 & z_1 \\ x_2&z_2\end{vmatrix},\begin{vmatrix}x_1 & y_1 \\ x_2&y_2\end{vmatrix}\right)(x1​,y1​,z1​)×(x2​,y2​,z2​)=(∣∣​y1​y2​​z1​z2​​∣∣​,−∣∣​x1​x2​​z1​z2​​∣∣​,∣∣​x1​x2​​y1​y2​​∣∣​)即可.

代码I -> 2021CCPC上海省赛-A(计算几何+叉积)

void solve() {int x1, y1, z1, x2, y2, z2; cin >> x1 >> y1 >> z1 >> x2 >> y2 >> z2;int ans1 = y1 * z2 - y2 * z1;int ans2 = x2 * z1 - x1 * z2;int ans3 = x1 * y2 - x2 * y1;cout << ans1 << ' ' << ans2 << ' ' << ans3;
}int main() {solve();
}

思路II

因欲求向量坐标范围不超过200200200,可O(n3)O(n^3)O(n3)暴力枚举(x,y,z)(x,y,z)(x,y,z),检查它与(xi,yi,zi)(i=1,2)(x_i,y_i,z_i)\ \ (i=1,2)(xi​,yi​,zi​)  (i=1,2)的点积是否为000即可.

代码II -> 2021CCPC上海省赛-A(计算几何+暴力+点积)

void solve() {int x1, y1, z1, x2, y2, z2; cin >> x1 >> y1 >> z1 >> x2 >> y2 >> z2;for (int x = -200; x <= 200; x++) {for (int y = -200; y <= 200; y++) {for (int z = -200; z <= 200; z++) {if (x1 * x + y1 * y + z1 * z == 0 && x2 * x + y2 * y + z2 * z == 0) {cout << x << ' ' << y << ' ' << z;return;}}}}
}int main() {solve();
}


C. 小 A 的期末考试

题意

给定n(1≤n≤100)n\ \ (1\leq n\leq 100)n  (1≤n≤100)个同学的学号s(1≤s≤n)s\ \ (1\leq s\leq n)s  (1≤s≤n)和分数a(0≤a≤100)a\ \ (0\leq a\leq 100)a  (0≤a≤100)和小A的学号m(1≤m≤n)m\ \ (1\leq m\leq n)m  (1≤m≤n).设所有同学初始平均分为avgavgavg.现有操作:①若小A成绩低于606060分,将其改为606060分;②若小A外的其他同学分数≥avg\geq avg≥avg,将其分数−=2-=2−=2,但分数不低于000分.按学号升序输出每个同学最后的分数.

代码 -> 2021CCPC上海省赛-C(模拟)

void solve() {int n, m; cin >> n >> m;vii stus;double avg = 0;  // 平均分for (int i = 0; i < n; i++) {int s, a; cin >> s >> a;stus.push_back({ s,a });avg += a;}avg /= n;sort(all(stus));for (auto& [s, a] : stus) {if (s == m) a = max(a, 60);else a = max(0, a - (cmp(a, avg) >= 0 ? 2 : 0));}for (auto& [s, a] : stus) cout << a << ' ';
}int main() {solve();
}


E. Zztrans 的庄园

题意

有555种等级的鱼,分别用符号D,C,B,A,SD,C,B,A,SD,C,B,A,S表示,售价分别为16,24,54,80,1000016,24,54,80,1000016,24,54,80,10000元.每次钓鱼需要花232323元购买鱼饵.现给定n(1<n≤100)n\ \ (1<n\leq 100)n  (1<n≤100)种鱼的等级和调到的概率,求钓k(1≤k≤100)k\ \ (1\leq k\leq 100)k  (1≤k≤100)次收益的期望,误差不超过1e−41\mathrm{e}-41e−4.

思路

先求钓一次的净收益的期望,再乘kkk.

代码 -> 2021CCPC上海省赛-E(期望)

map<char, int> prices = { {'D',16},{'C',24},{'B',54},{'A',80},{'S',10000} };void solve() {int n, k; cin >> n >> k;double ans = 0;while (n--) {char t; double p; cin >> t >> p;ans += (prices[t] - 23) * p;}cout << fixed << setprecision(8) << ans * k;
}int main() {solve();
}


G. 鸡哥的雕像

题意

给定一个长度为n(2≤n≤1e5)n\ \ (2\leq n\leq 1\mathrm{e}5)n  (2≤n≤1e5)的序列a1,⋯,an(1≤ai≤1e9)a_1,\cdots,a_n\ \ (1\leq a_i\leq 1\mathrm{e}9)a1​,⋯,an​  (1≤ai​≤1e9).对每个i∈[1,n]i\in[1,n]i∈[1,n],输出除了aia_iai​外的其他元素之积,答案对998244353998244353998244353取模.

思路

注意到998244353<1e9998244353<1\mathrm{e}9998244353<1e9,而998244353998244353998244353在模998244353998244353998244353下不存在逆元,故不能用求逆元的方式解决.

维护前缀积pre[]pre[]pre[]和后缀积suf[]suf[]suf[],则对每个i,ans=pre[i−1]∗suf[i+1]i,ans=pre[i-1]*suf[i+1]i,ans=pre[i−1]∗suf[i+1].

代码 -> 2021CCPC上海省赛-G(前缀积+后缀积)

const int MAXN = 1e5 + 5;
const int MOD = 998244353;
int n;
int a[MAXN];
int pre[MAXN], suf[MAXN];  // 前缀积、后缀积void solve() {cin >> n;pre[0] = 1;for (int i = 1; i <= n; i++) {cin >> a[i];pre[i] = (ll)pre[i - 1] * a[i] % MOD;}suf[n + 1] = 1;for (int i = n; i >= 1; i--) suf[i] = (ll)suf[i + 1] * a[i] % MOD;for (int i = 1; i <= n; i++) cout << (ll)pre[i - 1] * suf[i + 1] % MOD << ' ';
}int main() {solve();
}


J. Alice and Bob-1

题意

有n(1≤n≤5000)n\ \ (1\leq n\leq 5000)n  (1≤n≤5000)个元素a1,⋯,an(−1e9≤ai≤1e9)a_1,\cdots,a_n\ \ (-1\mathrm{e}9\leq a_i\leq 1\mathrm{e}9)a1​,⋯,an​  (−1e9≤ai​≤1e9),Alice和Bob轮流取走一个元素,Alice先手.取完所有元素后,两人拥有的价值定义为各自取的元素之和的绝对值.设Alice和Bob拥有的价值分别为AAA和BBB,Alice希望A−BA-BA−B尽量大,Bob希望A−BA-BA−B尽量小,两人都采取最优策略,求A−BA-BA−B.

思路

贪心策略:两人轮流取当前的最大元素.

[] Alice希望A−BA-BA−B尽量大,Bob希望A−BA-BA−B尽量小,都等价于两人希望自己拥有的价值尽量大.

设Alice和Bob拥有的价值分别为∣A∣|A|∣A∣和∣B∣|B|∣B∣.

因价值有绝对值,故所有数取反不影响答案,不妨设S=∑i=1nai≥0\displaystyle S=\sum_{i=1}^n a_i\geq 0S=i=1∑n​ai​≥0…

ans=∣A∣−∣B∣=∣A∣−∣S−A∣={S,A≥S2A−S,0<A<S−S,A≤0ans=|A|-|B|=|A|-|S-A|=\begin{cases}S,A\geq S \\ 2A-S,0<A<S \\ -S,A\leq 0\end{cases}ans=∣A∣−∣B∣=∣A∣−∣S−A∣=⎩⎨⎧​S,A≥S2A−S,0<A<S−S,A≤0​.作图知:该分段函数单调增,故证.

先将a[]a[]a[]升序排列.因集合元素可能全正、全负、有正有负,但答案都能归结为两种情况:①最大值被Alice取走,使得ansansans去掉绝对值后正得更多;②最大值被Bob取走,使得ansansans去掉绝对值后负得更多,两种情况取max⁡\maxmax即可.

代码 -> 2021CCPC上海省赛-J(贪心)

void solve() {int n; cin >> n;vi a(n + 1);ll sum = 0;for (int i = 1; i <= n; i++) {cin >> a[i];sum += a[i];}sort(a.begin() + 1, a.end());ll sum1 = 0, sum2 = 0;for (int i = 1; i <= n; i += 2) sum1 += a[i];for (int i = n; i >= 1; i -= 2) sum2 += a[i];cout << max(abs(sum1) - abs(sum - sum1), abs(sum2) - abs(sum - sum2));
}int main() {solve();
}


D. Zztrans 的班级合照

题意 (3s3\ \mathrm{s}3 s)

nnn(偶数)个人按如下要求排队:排成人数相同的两排,每排从左往右身高不减,且第二排同学身高不低于第一排对应位置的同学的身高.现给定将同学按身高升序排列后每个同学的排名(身高相同的同学排名相同),求排队方案数,答案对998244353998244353998244353取模.

第一行输入一个偶数n(2≤n≤5000)n\ \ (2\leq n\leq 5000)n  (2≤n≤5000).第二行输入nnn个整数a1,⋯,an(1≤ai≤n)a_1,\cdots,a_n\ \ (1\leq a_i\leq n)a1​,⋯,an​  (1≤ai​≤n),分别表示每个同学的身高排名.

思路

记录每个身高iii的人数cnt[i]cnt[i]cnt[i]后将原数组去重,则相同的身高的人任意排,答案乘上人数的阶乘即可.

考虑所有身高都不同时的情况.注意到任意时刻第二排的人数不超过第一排的人数,dp[i][j]dp[i][j]dp[i][j]表示排完前iii个人,且第一排比第二排多jjj个人的方案数,则最终答案为dp[n][n/2]dp[n][n/2]dp[n][n/2].

用sumsumsum记录当前排完的人数.对每个身高的人数iii,枚举第一排比第二排多的人数jjj,显然它不超过min⁡{n2,sum}\min\left\{\dfrac{n}{2},sum\right\}min{2n​,sum}.因第二排的人数不超过第一排的人数,故还需满足j≥sum−jj\geq sum-jj≥sum−j.按上一个状态dp[sum−i][]dp[sum-i][]dp[sum−i][]中第一排比第二排多的人数分类,不妨设dp[sum][]dp[sum][]dp[sum][]中第一排还需补kkk个人,则可从dp[sum−i][j−k]dp[sum-i][j-k]dp[sum−i][j−k]转移到dp[sum][j]dp[sum][j]dp[sum][j],枚举k∈[0,min⁡{i,j}]k\in[0,\min\{i,j\}]k∈[0,min{i,j}]即可.

代码 -> 2021CCPC上海省赛-D(DP+组合计数)

const int MAXN = 5005;
const int MOD = 998244353;
int n;
int cnt[MAXN];  // cnt[i]表示身高为i的人数
int dp[MAXN][MAXN];  // dp[i][j]表示排完前i个人,且第一排比第二排多j个人的方案数
int fac[MAXN], ifac[MAXN];void init() {  // 预处理阶乘fac[0] = 1;for (int i = 1; i < MAXN; i++) fac[i] = (ll)fac[i - 1] * i % MOD;
}void solve() {init();cin >> n;for (int i = 0; i < n; i++) {int x; cin >> x;cnt[x]++;}vi h;  // 去重后的身高int ans = 1;for (int i = 1; i <= n; i++) {if (cnt[i]) {h.push_back(cnt[i]);ans = (ll)ans * fac[cnt[i]] % MOD;  // 相同身高的人任意排}}int sum = 0;  // 当前排完的人数dp[0][0] = 1;  // i=0时只有j=0是合法方案for (auto i : h) {  // 枚举每个身高的人数sum += i;// 第一排的人数比第二排多的人数不超过min{当前排完的人数,总人数的一半}for (int j = min(n / 2, sum); j >= sum - j; j--) {for (int k = 0; k <= min(i, j); k++)  // 枚举上一个状态站第一排的人数dp[sum][j] = ((ll)dp[sum][j] + dp[sum - i][j - k]) % MOD;}}ans = (ll)ans * dp[n][n / 2] % MOD;cout << ans;
}int main() {solve();
}


H. 鸡哥的 AI 驾驶

题意 (4s4\ \mathrm{s}4 s)

数轴上有若干辆车,每辆车有三个参数:位置、速度、型号.型号相同的两车在同一位置时不会发生事故,不同型号的辆车在同一位置时会发生事故.求一个时间ts.t.[0,t]t\ s.t.\ [0,t]t s.t. [0,t]时间内未发生事故,(t,t+1](t,t+1](t,t+1]时间内发生了事故.

第一行输入两个整数n,k(1≤k≤n≤1e5)n,k\ \ (1\leq k\leq n\leq 1\mathrm{e}5)n,k  (1≤k≤n≤1e5),分别表示车数、型号数.接下来nnn行每行输入三个整数p,v,t(−1e9≤p,v≤1e9,1≤t≤k)p,v,t\ \ (-1\mathrm{e}9\leq p,v\leq 1\mathrm{e}9,1\leq t\leq k)p,v,t  (−1e9≤p,v≤1e9,1≤t≤k).数据保证初始时任意两车不在同一位置.

输出时间ttt,若不会发生事故,输出−1-1−1.

思路

若会发生事故,则时间越久越可能发生事故,故是否发生事故的性质具有二段性,可二分出其分界点.

考虑如何check.显然两不同型号的车发生事故的充要条件是它们的相互位置发生改变,即直观上它们互相穿过了对方.注意到每辆车不发生事故的移动范围是数轴上该型号的车最左边与最右边的位置之间的线段,则某型号的车离开该范围也会发生事故.

考察二分时间的范围.显然耗时最久的是从x=−1e9x=-1\mathrm{e}9x=−1e9以速度v=1v=1v=1走到x=1e9x=1\mathrm{e}9x=1e9,耗时t=2e9t=2\mathrm{e}9t=2e9,则边界可取[0,2e9+1][0,2\mathrm{e}9+1][0,2e9+1],其中+1+1+1是为了退出循环后断定l=2e9l=2\mathrm{e}9l=2e9是否有解.

代码 -> 2021CCPC上海省赛-H(二分)

const int MAXN = 1e5 + 5;
int n, k;
struct Car {int p, v, t;  // 位置、速度、型号bool operator<(const Car& B)const { return p < B.p; }
}cars[MAXN];
pii segs[MAXN];  // 每一段型号相同的车两端点的车的编号
pair<ll, int> pos[MAXN];  // 车移动后的位置、编号bool check(int ti) {for (int i = 1; i <= n; i++) pos[i] = { cars[i].p + (ll)cars[i].v * ti,i };  // 末位置sort(pos + 1, pos + n + 1);for (int i = 1; i <= n; i++) {if (i != 1 && pos[i].first == pos[i - 1].first && cars[pos[i].second].t != cars[pos[i - 1].second].t)return false;  // 型号不同的两车在同一位置if (i < segs[pos[i].second].first || i > segs[pos[i].second].second) return false;  // 超出最大移动范围}return true;
}void solve() {cin >> n >> k;for (int i = 1; i <= n; i++) cin >> cars[i].p >> cars[i].v >> cars[i].t;if (k == 1) {  // 只有一种型号不会发生事故cout << -1;return;}sort(cars + 1, cars + n + 1);  // 按位置升序排列// 预处理segs[]for (int i = 1; i <= n; i++) {  // 左端点if (cars[i].t == cars[i - 1].t) segs[i].first = segs[i - 1].first;else segs[i].first = i;}for (int i = n; i >= 1; i--) {  // 右端点if (cars[i].t == cars[i + 1].t) segs[i].second = segs[i + 1].second;else segs[i].second = i;}int l = 0, r = 2e9 + 1;  // 注意+1,否则l=2e9时无法判断是否有解while (l < r) {int mid = (ll)l + r + 1 >> 1;  // 注意这里会爆intif (check(mid)) l = mid;else r = mid - 1;}cout << (l == 2e9 + 1 ? -1 : l);
}int main() {solve();
}


B. 小 A 的卡牌游戏

题意 (2s2\ \mathrm{s}2 s)

一副nnn张卡的卡组恰包含aaa张A卡、bbb张B卡、ccc张C卡.现给出nnn次三选一的机会,三张卡分别来自三个种类,玩家需从三张卡中选一张加入自己的卡组,使得卡组强度尽量大.每张卡有一个强度值,卡组的强度是所有卡的强度之和.求卡组强度的最大值.

第一行输入四个整数n,a,b,c(1≤a,b,c≤n≤5000,a+b+c=n)n,a,b,c\ \ (1\leq a,b,c\leq n\leq 5000,a+b+c=n)n,a,b,c  (1≤a,b,c≤n≤5000,a+b+c=n).接下来nnn行每行输入三个整数描述一个三选一的机会,其中第iii行输入三个整数ai,bi,ci(1≤ai,bi,ci≤1e9)a_i,b_i,c_i\ \ (1\leq a_i,b_i,c_i\leq 1\mathrm{e}9)ai​,bi​,ci​  (1≤ai​,bi​,ci​≤1e9),分别表示该次选择中A卡、B卡、C卡的强度.

思路I

先考虑只有A卡和B卡的情况.注意不能贪心地选择强度前aaa大的A卡和强度前bbb大的B卡,因为选择间不独立.考虑先确定B卡的选择,剩下的选A卡.对两次三选一的机会(ai,bi)(a_i,b_i)(ai​,bi​)和(aj,bj)(a_j,b_j)(aj​,bj​),若选bib_ibi​,则只能再选aja_jaj​,同理选bjb_jbj​只能再选aia_iai​,则选前者更优的充要条件是:bi+aj>bj+aib_i+a_j>b_j+a_ibi​+aj​>bj​+ai​,即bi−ai>bj−ajb_i-a_i>b_j-a_jbi​−ai​>bj​−aj​.故将三选一的机会按bi−aib_i-a_ibi​−ai​降序排列后贪心地选前几个即可.

考虑有A、B、C卡的情况.dp[i][j]dp[i][j]dp[i][j]表示表示前iii次选择中有jjj次选择C卡的最大强度,其中的(i−j)(i-j)(i−j)次选A卡或B卡按照上述贪心策略选即可.总时间复杂度O(n2)O(n^2)O(n2).

代码I -> 2021CCPC上海省赛-B(贪心+DP)

const int MAXN = 5005;
int n, A, B, C;
ll dp[MAXN][MAXN];  // dp[i][j]表示前i次选择中有j次选择C卡的最大强度struct Card {int a, b, c;bool operator<(const Card& B)const {if (b - a != B.b - B.a) return b - a > B.b - B.a;else return c > B.c;}
}cards[MAXN];void solve() {cin >> n >> A >> B >> C;for (int i = 1; i <= n; i++) cin >> cards[i].a >> cards[i].b >> cards[i].c;sort(cards + 1, cards + n + 1);for (int i = 0; i <= n; i++)for (int j = i + 1; j <= C; j++) dp[i][j] = -INFF;  // 初始化非法状态for (int i = 1; i <= n; i++) {for (int j = 0; j <= min(i, C); j++) {  // 枚举选C卡的次数if (j) dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + cards[i].c);  // 选C卡if (i - j <= B) dp[i][j] = max(dp[i][j], dp[i - 1][j] + cards[i].b);  // 选B卡else dp[i][j] = max(dp[i][j], dp[i - 1][j] + cards[i].a);  // 选A卡}}cout << dp[n][C];
}int main() {solve();
}


思路II

各边容量都为111,费用为卡牌强度的负值,转化为求最小费用流,最终答案为最小费用的负值.

代码II -> 2021CCPC上海省赛-B(费用流)

namespace SPFA_Cost_Flow {static const int MAXN = 5005, MAXM = 1e5 + 10;  // 边开两倍int n, m, s, t;  // 点数、边数、源点、汇点int head[MAXN], edge[MAXM], capa[MAXM], cost[MAXM], nxt[MAXM], idx;  // capa[i]表示边i的容量,cost[i]表示边i的费用int min_capa[MAXN];  // min_capa[i]表示到节点i的所有边的容量的最小值int dis[MAXN];  // dis[i]表示源点到节点i的最短路int pre[MAXN];  // pre[i]表示节点i的前驱边的编号bool state[MAXN];  // SPFA中记录每个节点是否在队列中void add(int a, int b, int c, int d) {  // 建边a->b,容量为c,费用为dedge[idx] = b, capa[idx] = c, cost[idx] = d, nxt[idx] = head[a], head[a] = idx++;  // 正向边edge[idx] = a, capa[idx] = 0, cost[idx] = -d, nxt[idx] = head[b], head[b] = idx++;  // 反向边,流量初始为0,费用为正向边的相反数}bool spfa() {  // 返回是否找到增广路memset(dis, INF, so(dis));memset(min_capa, 0, so(min_capa));qi que;que.push(s);dis[s] = 0, min_capa[s] = INF;  // 源点处的流量无限制while (que.size()) {int u = que.front(); que.pop();state[u] = false;for (int i = head[u]; ~i; i = nxt[i]) {int v = edge[i];if (capa[i] && dis[v] > dis[u] + cost[i]) {  // 边还有容量dis[v] = dis[u] + cost[i];pre[v] = i;  // 记录前驱边min_capa[v] = min(min_capa[u], capa[i]);if (!state[v]) {que.push(v);state[v] = true;}}}}return min_capa[t];  // 汇点的流量非零即可以到达汇点,亦即存在增广路}pll EK() {  // first为最大流、second为最小费用pll res(0, 0);while (spfa()) {  // 当前还有增广路int tmp = min_capa[t];res.first += tmp, res.second += (ll)tmp * dis[t];for (int i = t; i != s; i = edge[pre[i] ^ 1])capa[pre[i]] -= tmp, capa[pre[i] ^ 1] += tmp;  // 正向边减,反向边加}return res;}
}
using namespace SPFA_Cost_Flow;int A, B, C;void solve() {memset(head, -1, so(head));s = 0, t = MAXN - 1;  // 超级源点、超级汇点cin >> n >> A >> B >> C;// 所有汇点向超级汇点连边,容量为每种卡的数量,费用为0add(n + 1, t, A, 0), add(n + 2, t, B, 0), add(n + 3, t, C, 0);for (int i = 1; i <= n; i++) {int a, b, c; cin >> a >> b >> c;add(s, i, 1, 0);  // 超级源点向源点连边,容量为1,费用为0// 各源点向对应的汇点连边,容量为1,费用为强度的负值add(i, n + 1, 1, -a), add(i, n + 2, 1, -b), add(i, n + 3, 1, -c);}cout << -EK().second;
}int main() {solve();
}



K. Alice and Bob-2

题意 (15s15\ \mathrm{s}15 s)

给定一些只包含小写字母的字符串,Alice和Bob两人轮流取字符,Alice先手,不能操作者败.每次有两种操作:①选择一个非空的字符串,取走其中任一个字符;②选择一个非空的字符串,取走其中任两个相异的字符.

有t(1≤t≤10)t\ \ (1\leq t\leq 10)t  (1≤t≤10)组测试数据.每组测试数据第一行输入一个整数n(1≤n≤10)n\ \ (1\leq n\leq 10)n  (1≤n≤10),表示字符串个数.接下来nnn行每行输入一个长度不超过404040且只包含小写字母的字符串sss.

对每组测试数据,输出最后的胜利者.

思路

显然SG函数.注意到字符串"aabb"和字符串"ccdd"对答案的贡献是相同的,即本问题中两字符串本质不同当且仅当它们所含的字符种类数不同或所含的字符种类数相同且相同字符的数目不同.考虑记搜,将本质相同的字符串用哈希值表示.

事实上,长度为lenlenlen的本质不同的字符串的个数为P(len)P(len)P(len),其中P(i)P(i)P(i)表示iii的无序分拆数,故要求的SG函数只有∑i=1nP(i)=215308\displaystyle\sum_{i=1}^n P(i)=215308i=1∑n​P(i)=215308个.

代码 -> 2021CCPC上海省赛-K(SG函数+记搜+哈希)

namespace Hash {const int Base = 131, MOD = 1e9 + 7;umap<int, int> mp;  // 记录哈希值对应的下标int idx = 0;int get_hash(vi& a) {int res = 0;for (auto i : a) {if (i) res = ((ll)res * Base + i) % MOD;else break;}return res;}int get_idx(int a) {if (mp.count(a)) return mp[a];else return mp[a] = idx++;}
};
using namespace Hash;int cnt[30];  // 每个字母出现的次数
umap<int, int> SG;int get_mex(set<int>& s) {int mex = 0;for (auto i : s) {if (i == mex) mex++;else break;}return mex;
}int get_SG(vi a) {sort(all(a), greater<int>());  // 注意排序int ha = get_hash(a);if (SG.count(ha)) return SG[ha];  // 搜过set<int> tmpSG;  // 存已求出的SG函数值// 删除一个字符for (int i = 0; i < a.size(); i++) {  // 枚举要删除的字符if (a[i]) {  // 还有这种字符a[i]--;  // 删除一个字符tmpSG.insert(get_SG(a));a[i]++;  // 恢复现场}else break;  // 没有这种字符}// 删除两个相异的字符for (int i = 0; i < a.size(); i++) {  // 枚举第一个要删除的字符if (!a[i]) break;  // 没有这种字符for (int j = i + 1; j < a.size(); j++) {  // 枚举第二个要删除的字符if (!a[j]) break;a[i]--, a[j]--;  // 删除两个字符tmpSG.insert(get_SG(a));a[i]++, a[j]++;  // 恢复现场}}return SG[ha] = get_mex(tmpSG);
}void solve() {int ans = 0;  // 各SG函数的异或和CaseT{for (int i = 0; i < 26; i++) cnt[i] = 0;  // 清空string s; cin >> s;for (auto ch : s) cnt[ch - 'a']++;vi tmp;for (int i = 0; i < 26; i++)if (cnt[i]) tmp.push_back(cnt[i]);ans ^= get_SG(tmp);}cout << (ans ? "Alice" : "Bob") << endl;
}int main() {solve();
}



I. 对线

题意 (12s12\ \mathrm{s}12 s)

有三排长度为nnn的兵线,每排兵线从左往右编号1∼n1\sim n1∼n,同排同编号位置对齐.现有如下四个操作:

①0xlr(x∈{1,2,3},1≤l≤r≤n)0\ x\ l\ r\ \ (x\in\{1,2,3\},1\leq l\leq r\leq n)0 x l r  (x∈{1,2,3},1≤l≤r≤n),表示询问第xxx排从lll位置到rrr位置间的士兵数,答案对998244353998244353998244353取模.

②1xlry(x∈{1,2,3},1≤l≤r≤n,1≤y≤1e9)1\ x\ l\ r\ y\ \ (x\in\{1,2,3\},1\leq l\leq r\leq n,1\leq y\leq 1\mathrm{e}9)1 x l r y  (x∈{1,2,3},1≤l≤r≤n,1≤y≤1e9),表示第xxx排从lll位置到rrr位置都增加yyy个士兵.

③2xylr(x,y∈{1,2,3},1≤l≤r≤n)2\ x\ y\ l\ r\ \ (x,y\in\{1,2,3\},1\leq l\leq r\leq n)2 x y l r  (x,y∈{1,2,3},1≤l≤r≤n),表示交换第xxx排和第yyy排区间[l,r][l,r][l,r]上的士兵.

④3xylr(x,y∈{1,2,3},1≤l≤r≤n)3\ x\ y\ l\ r\ \ (x,y\in\{1,2,3\},1\leq l\leq r\leq n)3 x y l r  (x,y∈{1,2,3},1≤l≤r≤n),表示将第xxx排区间[l,r][l,r][l,r]上的士兵复制一份加到第yyy排对应位置.

思路

线段树节点维护:①1×41\times 41×4的矩阵sum[][]sum[][]sum[][],其中sum[1][x]sum[1][x]sum[1][x]表示第xxx排当前区间的区间和;②4×44\times 44×4的矩阵lazy[][]lazy[][]lazy[][],表示矩阵乘法的懒标记,初始化为单位矩阵.整体思路:每个4×44\times 44×4的矩阵左上角的3×33\times 33×3矩阵的每一列维护兵线的每一排,4×44\times 44×4的矩阵的第444行维护操作.

①操作,答案为[l,r][l,r][l,r]的区间和的a[1][x]a[1][x]a[1][x]元素.

②操作,根据Gauss消元解线性方程组中的"一行的若干倍加到另一行上",不妨取矩阵的第444行都为111,则该操作等价于将第444行的yyy倍加到第xxx行.如x=1x=1x=1时,转移矩阵[100001000010y001]\begin{bmatrix}1&0&0&0 \\ 0&1&0&0 \\ 0&0&1&0 \\ y&0&0&1\end{bmatrix}⎣⎡​100y​0100​0010​0001​⎦⎤​;x=2x=2x=2时,转移矩阵[1000010000100y01]\begin{bmatrix}1&0&0&0 \\ 0&1&0&0 \\ 0&0&1&0 \\ 0&y&0&1\end{bmatrix}⎣⎡​1000​010y​0010​0001​⎦⎤​;x=3x=3x=3时,转移矩阵.[10000100001000y1]\begin{bmatrix}1&0&0&0 \\ 0&1&0&0 \\ 0&0&1&0 \\ 0&0&y&1\end{bmatrix}⎣⎡​1000​0100​001y​0001​⎦⎤​.

③操作,根据Gauss消元解线性方程组中的"交换两行",易知转移矩阵即单位矩阵交换第xxx行和第yyy行的结果.如x=1,y=3x=1,y=3x=1,y=3时,转移矩阵[0010010010000001]\begin{bmatrix}0&0&1&0 \\ 0&1&0&0 \\ 1&0&0&0 \\ 0&0&0&1\end{bmatrix}⎣⎡​0010​0100​1000​0001​⎦⎤​.

④操作,根据Gauss消元解线性方程组中的"一行的若干倍加到另一行上",易知转移矩阵即单位矩阵的a[x][y]a[x][y]a[x][y]元素+1+1+1.如x=1,y=3x=1,y=3x=1,y=3时,转移矩阵[1010010000100001]\begin{bmatrix}1&0&1&0 \\ 0&1&0&0 \\ 0&0&1&0 \\ 0&0&0&1\end{bmatrix}⎣⎡​1000​0100​1010​0001​⎦⎤​.

注意输入输出量大.

代码 -> 2021CCPC上海省赛-I(线段树维护矩阵)

namespace FastIO {
#define gc() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)  // 重写getchar()
#define pc(ch) (p - buf2 == SIZE ? fwrite(buf2, 1, SIZE, stdout), p = buf2, *p++ = ch : *p++ = ch)  // 重写putchar()char buf[1 << 23], * p1 = buf, * p2 = buf;template<typename T>void read(T& x) {  // 数字快读x = 0;T sgn = 1;char ch = gc();while (ch < '0' || ch > '9') {if (ch == '-') sgn = -1;ch = gc();}while (ch >= '0' && ch <= '9') {x = (((x << 2) + x) << 1) + (ch & 15);ch = gc();}x *= sgn;}const int SIZE = 1 << 21;int stk[40], top;char buf1[SIZE], buf2[SIZE], * p = buf2, * s = buf1, * t = buf1;template<typename T>void print_number(T x) {p = buf2;  // 复位指针pif (!x) {pc('0');return;}top = 0;  // 栈顶指针if (x < 0) {pc('-');x = ~x + 1;  // 取相反数}do {stk[top++] = x % 10;x /= 10;} while (x);while (top) pc(stk[--top] + 48);}template<typename T>void write(T x) {  // 数字快写print_number(x);fwrite(buf2, 1, p - buf2, stdout);}
};
using namespace FastIO;const int MAXN = 3e5 + 5;
const int MOD = 998244353;template<typename T>
struct Matrix {static const int MAXSIZE = 5;int n, m;  // 行数、列数T a[MAXSIZE][MAXSIZE];  // 下标从1开始Matrix() :n(0), m(0) { memset(a, 0, so(a)); }Matrix(int _n, int _m) :n(_n), m(_m) { memset(a, 0, so(a)); }void init_identity() {  // 初始化为单位矩阵assert(n == m);memset(a, 0, so(a));for (int i = 1; i <= n; i++) a[i][i] = 1;}Matrix<T> operator+(const Matrix<T>& B)const {assert(n == B.n), assert(m == B.m);Matrix<T> res(n, n);for (int i = 1; i <= n; i++) {for (int j = 1; j <= m; j++)res.a[i][j] = ((ll)a[i][j] + B.a[i][j]) % MOD;}return res;}Matrix<T> operator-(const Matrix<T>& B)const {assert(n == B.n), assert(m == B.m);Matrix<T> res(n, n);for (int i = 1; i <= n; i++) {for (int j = 1; j <= m; j++)res.a[i][j] = ((a[i][j] - B.a[i][j]) % MOD + MOD) % MOD;}return res;}Matrix<T> operator*(const Matrix<T>& B)const {assert(m == B.n);Matrix<T> res(n, B.m);for (int i = 1; i <= n; i++) {for (int j = 1; j <= B.m; j++) {for (int k = 1; k <= m; k++)res.a[i][j] = ((ll)res.a[i][j] + (ll)a[i][k] * B.a[k][j]) % MOD;}}return res;}Matrix<T> operator^(int k)const {  // 快速幂assert(n == m);Matrix<T> res(n, n);res.init_identity();  // 单位矩阵Matrix<T> tmpa(n, n);  // 存放矩阵a[][]的乘方memcpy(tmpa.a, a, so(a));while (k) {if (k & 1) res = res * tmpa;k >>= 1;tmpa = tmpa * tmpa;}return res;}Matrix<T>& operator=(const Matrix<T>& B) {memset(a, 0, so(a));n = B.n, m = B.m;for (int i = 1; i <= n; i++)for (int j = 1; j <= m; j++) a[i][j] = B.a[i][j];return *this;}bool operator==(const Matrix<T>& B)const {if (n != B.n || m != B.m) return false;for (int i = 1; i <= n; i++) {for (int j = 1; j <= m; j++)if (a[i][j] != B.a[i][j]) return false;}return true;}void print() {for (int i = 1; i <= n; i++)for (int j = 1; j <= m; j++) cout << a[i][j] << " \n"[j == m];}
};Matrix<ll> Imatrix = Matrix<ll>(4, 4);  // 单位矩阵struct Node {int l, r;Matrix<ll> sum;  // 区间和Matrix<ll> lazy;  // 矩阵乘法标记
}SegT[MAXN << 2];void push_up(int u) {SegT[u].sum = SegT[u << 1].sum + SegT[u << 1 | 1].sum;
}void build(int u, int l, int r) {SegT[u].l = l, SegT[u].r = r;SegT[u].sum = Matrix<ll>(4, 4);SegT[u].lazy = Matrix<ll>(4, 4);SegT[u].lazy.init_identity();  // 初始化为单位矩阵,表示无修改if (l == r) {SegT[u].sum.a[1][4] = 1;return;}int mid = l + r >> 1;build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);push_up(u);
}void push_down(int u) {if (SegT[u].lazy == Imatrix) return;  // 无懒标记SegT[u << 1].sum = SegT[u << 1].sum * SegT[u].lazy;SegT[u << 1].lazy = SegT[u << 1].lazy * SegT[u].lazy;SegT[u << 1 | 1].sum = SegT[u << 1 | 1].sum * SegT[u].lazy;SegT[u << 1 | 1].lazy = SegT[u << 1 | 1].lazy * SegT[u].lazy;SegT[u].lazy.init_identity();  // 清空为单位矩阵
}void modify(int u, int l, int r, Matrix<ll>& mul) {if (l <= SegT[u].l && SegT[u].r <= r) {SegT[u].sum = SegT[u].sum * mul;SegT[u].lazy = SegT[u].lazy * mul;return;}push_down(u);int mid = SegT[u].l + SegT[u].r >> 1;if (l <= mid) modify(u << 1, l, r, mul);if (r > mid) modify(u << 1 | 1, l, r, mul);push_up(u);
}Matrix<ll> query(int u, int l, int r) {if (l <= SegT[u].l && SegT[u].r <= r) return SegT[u].sum;push_down(u);int mid = SegT[u].l + SegT[u].r >> 1;Matrix<ll> res(4, 4);if (l <= mid) res = res + query(u << 1, l, r);if (r > mid) res = res + query(u << 1 | 1, l, r);return res;
}void solve() {Imatrix.init_identity();int n, q; read(n), read(q);build(1, 1, n);while (q--) {int op; read(op);if (op == 0) {  // 询问第x排[l,r]的区间和int x, l, r; read(x), read(l), read(r);write(query(1, l, r).a[1][x]), putchar('\n');}else if (op == 1) {  // 第x排[l,r]+=yint x, l, r, y; read(x), read(l), read(r), read(y);Matrix<ll> mul(4, 4);mul.init_identity();mul.a[4][x] = y;modify(1, l, r, mul);}else if (op == 2) {  // 交换第x排和第y排的[l,r]int x, y, l, r; read(x), read(y), read(l), read(r);Matrix<ll> mul(4, 4);mul.init_identity();mul.a[x][x] = mul.a[y][y] = 0;mul.a[x][y] = mul.a[y][x] = 1;modify(1, l, r, mul);}else {  // 第y排[l,r]+=第x排[l,r]int x, y, l, r; read(x), read(y), read(l), read(r);Matrix<ll> mul(4, 4);mul.init_identity();mul.a[x][y]++;modify(1, l, r, mul);}}
}int main() {solve();
}



2021CCPC上海省赛题解ABCDEGHIJK相关推荐

  1. 2021ICPC上海区域赛DEGKI

    题目链接: https://codeforces.com/gym/103446 视频讲解: https://www.bilibili.com/video/bv1994y1f76o 代码:https:/ ...

  2. 2021CCPC新疆省赛题解BDEFGHIJK

    2021CCPC新疆省赛题解BDEFGHIJK K. chino with c language 题意 memcpy()memcpy()memcpy()不会检查源地址范围与目标地址范围是否重叠,它只从 ...

  3. 2021CCPC江西省赛题解ABGHIJKL

    2021CCPC江西省赛题解ABGHIJKL K. Many Littles Make a Mickle 题意 有 t ( 1 ≤ t ≤ 100 ) t\ \ (1\leq t\leq 100) t ...

  4. 2019 ACM - ICPC 上海网络赛 E. Counting Sequences II (指数型生成函数)

    繁凡出品的全新系列:解题报告系列 -- 超高质量算法题单,配套我写的超高质量题解和代码,题目难度不一定按照题号排序,我会在每道题后面加上题目难度指数(1∼51 \sim 51∼5),以模板题难度 11 ...

  5. 2018 ACM 国际大学生程序设计竞赛上海大都会赛重现赛 F Color it

    链接:https://www.nowcoder.com/acm/contest/163/F 来源:牛客网 2018 ACM 国际大学生程序设计竞赛上海大都会赛重现赛 F Color it 时间限制:C ...

  6. 校内训练赛题解第三篇

    校内训练赛题解 人气估值 解题思路 脑力训练计划 (模拟 + 字符串) 解题思路 大暑赛期(贪心 + 思维) 人气估值 题目描述 你是某动画制作公司的企划部长.如今动画制作公司制作的东西,已经不仅仅局 ...

  7. 21上海省赛 F-鸡哥的限币令

    21上海省赛 F-鸡哥的限币令 n个点m条单向边的图中,边上有边权,要求选择一个边的集合使得每一个点有至少一条连入的边和一条连出的边,且这个集合的边权和最小.如果不能找到,输出-1:如果找到了,输出边 ...

  8. 上海区域赛Unlock the Cell Phone

    /* 题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4026  题目来源:上海区域赛Unlock the Cell Phone 报告人:SpringWa ...

  9. “科林明伦杯”哈尔滨理工大学第十届程序设计竞赛(同步赛) 题解

    "科林明伦杯"哈尔滨理工大学第十届程序设计竞赛(同步赛) 题解 萌新又来写题解啦 原题链接 B 减成一 题意:存在n个数,每次操作可以任选一个区间使得区间内的所有数字减一.问最少多 ...

最新文章

  1. pytroch 数据集 datasets DataLoader示例
  2. java rgb 黑色_Java实现图片亮度自动调节(RGB格式)
  3. Hibernate HQL基础 限定查询条件(这里面有各种条件查询)
  4. python画图数据的平均值怎么算的_Python气象数据处理与绘图(2):常用数据计算方法...
  5. deeplearning.ai 改善深层神经网络 week2 优化算法
  6. c调用python keras模型_tensorflow中调用keras训练模型作为一个计算过程
  7. java容易搞混的技术_Java中比较容易混淆的知识点
  8. 移动Web—CSS为Retina屏幕替换更高质量的图片
  9. 英语单词常用前缀(21-40)
  10. Python基础(Day 2)(数值 字符串 布尔 列表)
  11. 虚拟服务器修改教程,【新挑战】十二职业虚拟机一键端图文架设修改教程
  12. 微信连wifi3.1总结
  13. PHP 对接微信公众号订阅消息详细教程
  14. 前后端交互流程,如何进行交互
  15. 以核心素养为导向的计算机教学方式,“雨花学术论坛”再开讲:让核心素养在课堂“落地生根”...
  16. download sources报错: Cannot connect to the Maven process. Try again later. If the problem persists
  17. 手机上怎么做电子档文件(实用技巧)
  18. Web基础——JavaScript之事件绑定与事件对象
  19. Second season twentieth episode,poor Phoebe
  20. 你知道云渲染和自己渲染有什么区别吗?

热门文章

  1. 华为瘦胖ap互转_华为V2R3 胖瘦AP 转换
  2. 智能密码锁语音提示芯片选型?
  3. 头胀头晕的原因(眩晕)
  4. 技术人员需要了解的手机验证码登录风险
  5. mui关闭页面plus.webview.currentWebview().close();使用后页面闪现一下的问题解决
  6. linux 磁盘参数优化: barrier
  7. VK11\VK12\VK13 价格间隔拆分问题
  8. iframe 嵌入页面后无法显示的问题
  9. 安卓开发之屏蔽按键抖动,连击,长按事件
  10. html期末作业代码网页设计——简洁日式料理餐饮(4页) HTML+CSS+JavaScript 父亲美食HTM5网页设计作业成品