工作时长
将输入时间换算成距离 2022-01-01 00:00:00
的秒数,排序后成对计算即可.
#include <iostream>
#include <cstdio>
#include <cassert>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
freopen("records.txt", "r", stdin);
int y, mo, d, h, mi, s;
vector<int> days = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
vector<int> v;
while (scanf("%d-%d-%d %d:%d:%d", &y, &mo, &d, &h, &mi, &s) == 6) {
// printf("%d-%d-%d %d:%d:%d\n", y, mo, d, h, mi, s);
assert(y == 2022);
int t = 0;
for (int i = 1; i < mo; ++i) {
t += days[i];
}
t += d;
t = t * 24 + h;
t = t * 60 + mi;
t = t * 60 + s;
v.push_back(t);
}
assert(v.size() % 2 == 0);
sort(v.begin(), v.end());
int ans = 0;
for (int i = 0; i < v.size(); i += 2) ans += v[i + 1] - v[i];
cout << ans; // 5101913
return 0;
}
#if 0
2022-01-01 12:00:05
2022-01-02 00:20:05
2022-01-01 07:58:02
2022-01-01 16:01:35
#endif
与或异或
10 10 10 个门电路,每个门电路 3 3 3 种状态,总共 3 10 = 59049 3^{10}=59049 310=59049 种状态. 枚举即可.
#include <iostream>
using namespace std;
int main()
{
const int n = 5;
int arr[n][n];
arr[0][0] = 1; arr[0][1] = 0; arr[0][2] = 1; arr[0][3] = 0; arr[0][4] = 1;
int tot = 59049;
int ans = 0;
for (int t = 0; t < tot; ++t) {
int s = t;
for (int i = 1; i < n; ++i) {
for (int j = 0; j < n - i; ++j) {
int op = s % 3;
s /= 3;
switch (op) {
case 0: arr[i][j] = arr[i - 1][j] & arr[i - 1][j + 1]; break;
case 1: arr[i][j] = arr[i - 1][j] | arr[i - 1][j + 1]; break;
case 2: arr[i][j] = arr[i - 1][j] ^ arr[i - 1][j + 1]; break;
}
}
}
ans += (arr[n - 1][0] == 1);
}
cout << ans; // 30528
return 0;
}
翻转
注意到:
- 最左和最右的棋子无法翻转.
- 一颗棋子在翻转后无法再翻转回来,因此每个位置最多翻转一次.
- 一颗棋子翻转后,与它相邻的棋子无法进行翻转.
- S S S 与 T T T 棋子不同的位置必须要翻转.
因此,如果 S S S 通过若干次翻转可以得到 T T T ,需要翻转的位置一定不在两端且互不相邻,且翻转的次序可以任意.
贪心地一次遍历即可.
#include <cstdio>
const int N = 1e6 + 2;
char S[N];
char T[N];
int main()
{
int D;
scanf("%d", &D);
while (D--) {
scanf("%s%s", T + 1, S + 1);
bool ok = true;
int cnt = 0;
for (int i = 1; S[i]; ++i) {
if (S[i] == T[i]) continue;
if (i > 1 && S[i + 1]) // 如果不在两端
if (S[i] != S[i - 1] && S[i] != S[i + 1]) {
S[i] = '1' - S[i] + '0';
++cnt;
continue;
}
ok = false;
break;
}
int ans = ok ? cnt : -1;
printf("%d\n", ans);
}
}
阶乘的和
将 A 1 , A 2 , ⋯ , A n A_1,A_2,\cdots,A_n A1,A2,⋯,An 排序得到 a 1 , a 2 , ⋯ , a n a_1,a_2,\cdots,a_n a1,a2,⋯,an .
注意到:
- ∑ i = 1 n ( A i ! ) = ∑ i = 1 n ( a i ! ) \sum_{i=1}^n(A_i!)=\sum_{i=1}^n(a_i!) ∑i=1n(Ai!)=∑i=1n(ai!) .
- ( a 1 ! ) ∣ ∑ i = 1 n ( a i ! ) (a_1!)|\sum_{i=1}^n(a_i!) (a1!)∣∑i=1n(ai!) , ( a p ! ) ∣ ∑ i = p n ( a i ! ) (a_p!)|\sum_{i=p}^n(a_i!) (ap!)∣∑i=pn(ai!) .
- 若 m ! ∣ ∑ i = 1 n ( a i ! ) m!|\sum_{i=1}^n(a_i!) m!∣∑i=1n(ai!) , 则 ( m − 1 ) ! ∣ ∑ i = 1 n ( a i ! ) (m-1)!|\sum_{i=1}^n(a_i!) (m−1)!∣∑i=1n(ai!).
因此,若 M M M 是满足条件的最大的 m m m ,即 M ! ∣ ∑ i = 1 n ( a i ! ) M!|\sum_{i=1}^n(a_i!) M!∣∑i=1n(ai!) ,则 ∑ i = 1 n ( a i ! ) m o d ( M + 1 ) ! = k × M ! \sum_{i=1}^n(a_i!) \mod (M+1)!=k\times M! ∑i=1n(ai!)mod(M+1)!=k×M! ,其中 k ∈ { 1 , 2 , ⋯ , M } k\in\{1,2,\cdots,M\} k∈{1,2,⋯,M} .
初始化 m = a 1 m=a_1 m=a1 不断假设答案为 m m m, 求解 ∑ i = 1 n ( a i ! ) \sum_{i=1}^n(a_i!) ∑i=1n(ai!) 取模 ( m + 1 ) ! (m+1)! (m+1)! 的余数 k × m ! k\times m! k×m! . 当 1 ≤ k ≤ m 1\le k \le m 1≤k≤m 时,算法结束,此时的 m m m 即为结果.
时间复杂度 O ( n ) O(n) O(n) .
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
int n;
scanf("%d", &n);
vector<int> a(n);
for (auto &p : a) scanf("%d", &p);
sort(a.begin(), a.end());
int k = 0, m = a[0];
for (int i = 0; i < n; ++i) {
if (m == a[i]) {
++k;
continue;
}
while (k > m && k % (m + 1) == 0) {
++m;
k /= m;
if (m == a[i]) {
++k;
break;
}
}
}
while (k > m && k % (m + 1) == 0) {
++m;
k /= m;
}
printf("%d", m);
return 0;
}
公因数匹配
A i , A j A_i,A_j Ai,Aj 存在大于 1 1 1 的公因数,等价于 A i , A j A_i,A_j Ai,Aj 均含有某个质因子.
记 p o s x pos_x posx 为质因子 x x x 出现的最早位置.
对于每一个 j j j 求出最小的 i i i 并更新答案即可.
时间复杂度 O ( n A m a x ) O(n\sqrt{A_{max}}) O(nAmax) . 预处理 1000 1000 1000 以内的素数表可将复杂度降为 ( n log A m a x ) (n\log{A_{max}}) (nlogAmax) .
本题存在没有答案的情况(如当 n = 1 n=1 n=1 时),但题目未作说明,不妨假设输入保证有解.
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 1e6 + 1;
int pos[M]; // pos[x] 代表质因数 x 最早出现的位置,初始化为 0
int main()
{
int n;
scanf("%d", &n);
int ansi = n + 1, ansj = -1;
for (int i = 1; i <= n; ++i) {
int x;
scanf("%d", &x);
int l = n + 1;
for (int t = 2; t <= x / t; ++t) {
if (x % t) continue;
do x /= t; while (x % t == 0);
if (pos[t]) l = min(l, pos[t]);
else pos[t] = i;
}
if (x > 1) {
if (pos[x]) l = min(l, pos[x]);
else pos[x] = i;
}
if (l < ansi) ansi = l, ansj = i;
}
printf("%d %d", ansi, ansj);
return 0;
}
#if 0
5
5 3 2 6 9
#endif
奇怪的数
题面没有说明数位是从左往右数还是从右往左数(二者在允许出现前导 0 0 0 的前提下没有区别),这份题解默认从左往右数.
令 f i , a , b , c , d f_{i,a,b,c,d} fi,a,b,c,d 代表长度为 i i i 且后四位为 a b c d abcd abcd 的奇怪的数的个数,其中 i ≥ 5 i\ge 5 i≥5. 则
f i + 1 , a , b , c , d = ∑ e ≤ m − a − b − c − d f i , e , a , b , c f_{i+1,a,b,c,d}=\sum_{e \le m-a-b-c-d}f_{i,e,a,b,c} fi+1,a,b,c,d=e≤m−a−b−c−d∑fi,e,a,b,c
初始化暴力算出 f 5 , a , b , c , d f_{5,a,b,c,d} f5,a,b,c,d 即可.
时间复杂度 O ( n ⋅ D 5 ) O(n\cdot D^5) O(n⋅D5) ,其中 D D D 代表每一位可能的取值个数. 用另一个数组 s s s 维护 ∑ e f i , e , a , b , c \sum_{e}f_{i,e,a,b,c} ∑efi,e,a,b,c 的值可将复杂度降为 O ( n ⋅ D 4 ) O(n\cdot D^4) O(n⋅D4) . 计算量在 1 0 8 10^8 108 级别,不过开启 O 2 O2 O2 优化之后可以轻松在 1 s 1s 1s 内计算出 n = 200000 , m = 50 n=200000,m=50 n=200000,m=50 的结果. 不过这个复杂度还是挺极限的,如果存在更好的做法,望告知!
由于 f i f_i fi 的状态仅取决于 f i − 1 f_{i - 1} fi−1 的状态,因此可采用滚动数组,空间复杂度 O ( D 4 ) O(D^4) O(D4) .
#include <cstdio>
#include <iostream>
using namespace std;
const int mod = 998244353;
const int M = 12;
int f[2][M][M][M][M];
int s[2][M][M][M][M];
int main()
{
int n, m;
scanf("%d%d", &n, &m);
int t = 0;
for (int a = 0; a < 10; a += 2) {
for (int b = 1; b < 10; b += 2) {
for (int c = 0; c < 10; c += 2) {
for (int d = 1; d < 10; d += 2) {
int &val = f[t][a][b][c][d];
for (int e = 1; e < 10 && e <= m - a - b - c - d; e += 2) ++val;
s[t][a + 2][b][c][d] = s[t][a][b][c][d] + val;
}
}
}
}
for (int i = 6; i <= n; ++i) {
t ^= 1;
for (int a = (i - 3) & 1; a < 10; a += 2) {
for (int b = (i - 2) & 1; b < 10; b += 2) {
for (int c = (i - 1) & 1; c < 10; c += 2) {
for (int d = i & 1; d < 10; d += 2) {
int &val = f[t][a][b][c][d];
val = 0;
if (a + b + c + d <= m) {
int e = min(9, m - a - b - c - d);
if ((i - 4) & 1) { if (!(e & 1)) --e; }
else { if (e & 1) --e; }
val = s[t ^ 1][e + 2][a][b][c];
}
s[t][a + 2][b][c][d] = (s[t][a][b][c][d] + val) % mod;
}
}
}
}
}
int ans = 0;
for (int a = (n - 3) & 1; a < 10; a += 2) {
for (int b = (n - 2) & 1; b < 10; b += 2) {
for (int c = (n - 1) & 1; c < 10; c += 2) {
for (int d = n & 1; d < 10; d += 2) {
ans += f[t][a][b][c][d];
if (ans >= mod) ans -= mod;
}
}
}
}
printf("%d", ans);
return 0;
}
#if 0
5 5 => 6
100000 50 => 976293084
200000 50 => 670295496
#endif
太阳
让一条来自太阳的射线从 x x x 轴负向开始逆时针旋转,扫描过程中记录被照到的线段即可.
时间复杂度 O ( n log n ) O(n\log n) O(nlogn) .
#include <iostream>
#include <vector>
#include <algorithm>
#include <set>
using namespace std;
struct node {
int x, y, i;
node(int x, int y, int i) : x(x), y(y), i(i) {}
bool operator< (const node &t) const
{
long long a = 1LL * t.y * x;
long long b = 1LL * y * t.x;
return a < b;
}
bool operator== (const node &t) const {
return 1LL * t.y * x == 1LL * y * t.x;
}
};
int main()
{
int n, X, Y;
scanf("%d%d%d", &n, &X, &Y);
vector<node> v;
vector<bool> vis(n + 1);
for (int i = 1; i <= n; ++i) {
int x, y, l;
scanf("%d%d%d", &x, &y, &l);
v.emplace_back(x - X, Y - y, i);
v.emplace_back(x + l - X, Y - y, -i);
}
sort(v.begin(), v.end());
typedef pair<int,int> pii;
set<pii> st;
auto last = node(-1, 1, 0);
for (auto &t : v) {
if (!(t == last)) {
if (!st.empty()) {
vis[st.begin()->second] = true;
}
last = t;
}
if (t.i > 0) st.emplace(t.y, t.i);
else st.erase(make_pair(t.y, -t.i));
}
int ans = 0;
for (bool t : vis) ans += t;
printf("%d", ans);
return 0;
}
子树的大小
第 i i i 层的结点个数是 m i m^{i} mi , i i i 从 0 0 0 开始.
因此第 h h h 层最左端结点的编号为 1 + ∑ i = 0 h − 1 m i 1+\sum_{i=0}^{h-1}m^i 1+∑i=0h−1mi .
求出 k k k 号结点和 n n n 号所在层数及其左边的结点个数后分类讨论即可.
若 k k k 与 n n n 在同一层,答案为 1 1 1 .
否则答案为 ∑ i = 0 h n − 1 − h k m i + max ( 0 , min ( m h n − h k , l n + 1 − l k × m h n − h k ) ) \sum_{i=0}^{h_n-1-h_k}m^i+\max(0,\min(m^{h_n-h_k},l_n + 1 - l_k \times m^{h_n-h_k})) ∑i=0hn−1−hkmi+max(0,min(mhn−hk,ln+1−lk×mhn−hk)) ,其中 h ⋅ h_\cdot h⋅ 代表结点所在层数, l ⋅ l_\cdot l⋅ 代表当前层该结点左边的结点个数.
时间复杂度 O ( T log n ) O(T\log n) O(Tlogn) .
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void get_info(int m, int k, int &h, int &l) {
long long cnt = 0;
long long num = 1;
h = 0;
while (cnt + num < k) {
cnt += num;
++h;
num *= m;
}
l = k - cnt - 1;
}
int main()
{
int T;
scanf("%d", &T);
while (T--) {
int n, m, k;
scanf("%d%d%d", &n, &m, &k);
int hk, lk;
get_info(m, k, hk, lk);
int hn, ln;
get_info(m, n, hn, ln);
if (hk == hn) {
puts("1");
continue;
}
int ans = 0;
long long t = 1;
for (int i = 0; i < hn - hk; ++i) {
ans += t;
t *= m;
}
ans += max(0LL, min(t, ln + 1 - lk * t));
printf("%d\n", ans);
}
return 0;
}
#if 0
3
1 2 1
11 3 4
74 5 3
#endif
高塔
小蓝在第 n n n 回合后结束游戏的方案数为
∑ c 1 + c 2 + ⋯ + c n ≤ m ∏ j = 1 n A j c j = ( ∑ c 1 + c 2 + ⋯ + c n ≤ m ∏ j = 1 n c j ) ⋅ ∏ j = 1 n A j = T n , m ⋅ ∏ j = 1 n A j \begin{aligned} \sum\limits_{c_1+c_2+\cdots +c_n\le m}\prod\limits_{j=1}^nA_jc_j &= \left(\sum\limits_{c_1+c_2+\cdots +c_n\le m}\prod\limits_{j=1}^nc_j\right)\cdot\prod\limits_{j=1}^nA_j \\ &= T_{n, m}\cdot \prod\limits_{j=1}^nA_j \end{aligned} c1+c2+⋯+cn≤m∑j=1∏nAjcj=(c1+c2+⋯+cn≤m∑j=1∏ncj)⋅j=1∏nAj=Tn,m⋅j=1∏nAj
小蓝在第 i ( 1 ≤ i < n ) i(1\le i\lt n) i(1≤i<n) 回合后耗尽能量的方案数为
∑ c 1 + c 2 + ⋯ + c i = m ∏ j = 1 i A j c j = ( ∑ c 1 + c 2 + ⋯ + c i = m ∏ j = 1 i c j ) ⋅ ∏ j = 1 i A j = ( T i , m − T i , m − 1 ) ⋅ ∏ j = 1 i A j \begin{aligned} \sum\limits_{c_1+c_2+\cdots +c_i=m}\prod\limits_{j=1}^iA_jc_j &= \left(\sum\limits_{c_1+c_2+\cdots +c_i=m}\prod\limits_{j=1}^ic_j\right)\cdot\prod\limits_{j=1}^iA_j\\ &= (T_{i,m}-T_{i, m-1})\cdot\prod\limits_{j=1}^iA_j \end{aligned} c1+c2+⋯+ci=m∑j=1∏iAjcj=(c1+c2+⋯+ci=m∑j=1∏icj)⋅j=1∏iAj=(Ti,m−Ti,m−1)⋅j=1∏iAj
其中 T i , j = ∑ c 1 + c 2 + ⋯ + c i ≤ j ∏ k = 1 i c k = C i + j 2 i T_{i,j}=\sum\limits_{c_1+c_2+\cdots +c_i\le j}\prod\limits_{k=1}^ic_k=\mathrm C_{i+j}^{2i} Ti,j=c1+c2+⋯+ci≤j∑k=1∏ick=Ci+j2i.
∑ c 1 + c 2 + ⋯ + c i ≤ j ∏ k = 1 i c k = C i + j 2 i \sum\limits_{c_1+c_2+\cdots +c_i\le j}\prod\limits_{k=1}^ic_k=\mathrm C_{i+j}^{2i} c1+c2+⋯+ci≤j∑k=1∏ick=Ci+j2i 的证明参见 https://math.stackexchange.com/questions/4678677/how-can-i-prove-this-formula-sum-a-1a-2-cdots-a-m-le-n-prod-i-1ma-i .
则总的方案数为
( T n , m ⋅ ∏ j = 1 n A j ) + ( ∑ i = 1 n − 1 ( T i , m − T i , m − 1 ) ⋅ ∏ j = 1 i A j ) \left(T_{n, m}\cdot \prod\limits_{j=1}^nA_j\right)+\left(\sum_{i=1}^{n-1}(T_{i,m}-T_{i, m-1})\cdot\prod\limits_{j=1}^iA_j\right) (Tn,m⋅j=1∏nAj)+(i=1∑n−1(Ti,m−Ti,m−1)⋅j=1∏iAj)
时间复杂度 O ( n ⋅ α ) O(n\cdot \alpha) O(n⋅α) ,其中 α \alpha α 代表求逆元的均摊复杂度.
#include <iostream>
#include <vector>
using namespace std;
int inv(int a, int p) {
int b = p - 2;
int ans = 1;
while (b) {
if (b & 1) ans = (long long) ans * a % p;
a = (long long) a * a % p;
b >>= 1;
}
return ans;
}
int main()
{
const int mod = 998244353;
int n;
long long m;
cin >> n >> m;
vector<int> A(n + 1);
for (int i = 1; i <= n; ++i) scanf("%d", &A[i]);
int ans = 0;
int prod = 1;
int Tm = 1, Tm_1 = 1;
int m_1 = (m - 1) % mod;
m %= mod;
for (int i = 1; i <= n; ++i) {
Tm = (long long) Tm * (m + i) % mod * (m - i + 1) % mod * inv(2 * i, mod) % mod * inv(2 * i - 1, mod) % mod;
Tm_1 = (long long) Tm_1 * (m_1 + i) % mod * (m_1 - i + 1) % mod * inv(2 * i, mod) % mod * inv(2 * i - 1, mod) % mod;
prod = 1LL * prod * A[i] % mod;
if (i < n) ans = (ans + 1LL * (Tm - Tm_1 + mod) * prod % mod) % mod;
else ans = (ans + 1LL * Tm * prod % mod) % mod;
}
cout << ans;
return 0;
}
#if 0
9 15
3 2 5 7 1 4 6 8 3
#endif
反异或 01 串
逆向思维:给定一个 01 01 01 串 T T T ,每步操作可以将最左端或最右端的 0 0 0 或 1 1 1 移除. 也可以对整个串进行一次逆向的反异或操作. 问最少需要移除多少个 1 1 1 .
由反异或操作的定义知
- 逆向的反异或操作要一定作用在 T T T 的某个子串 t t t 上.
- t t t 必须是回文串,且最中间的元素(如果长度为奇数)必须是 0 0 0 .
因此,不难发现
- 若不进行反异或操作,需要移除 O N E T \mathrm{ONE}_T ONET 个 1 1 1.
- 若进行一次反异或操作,需要移除 O N E T − O N E t / 2 \mathrm{ONE}_T-\mathrm{ONE}_t/2 ONET−ONEt/2 个 1 1 1.
其中 O N E ⋅ \mathrm{ONE}_\cdot ONE⋅ 为串 ⋅ \cdot ⋅ 中 1 1 1 的个数.
为避免考虑回文子串的奇偶性,可在串 T T T 的 ∣ T ∣ − 1 |T|-1 ∣T∣−1 个间隙中插入一个 0 0 0 得到新的串 T ′ T' T′ ,不难发现, T T T 中所有的回文串都对应 T ′ T' T′ 中一个长度为奇数的回文串.
令 f i f_i fi 代表以 i i i 为中心的最长回文串的右端点. o n e i one_i onei 代表以 i i i 为中心的最长回文串中 1 1 1 的数目. 通过 M a n a c h e r Manacher Manacher 算法可求得其值.
时间复杂度 O ( ∣ T ′ ∣ ) = O ( ∣ T ∣ ) O(|T'|)=O(|T|) O(∣T′∣)=O(∣T∣) .
#include <iostream>
#include <cstring>
using namespace std;
const int N = 2e6;
char T[N];
int f[N];
int sum[N];
int main()
{
scanf("%s", T);
int n = strlen(T);
int ones_T = (T[0] == '1');
for (int i = n - 1; i > 0; --i) {
ones_T += (T[i] == '1');
T[i * 2] = T[i];
T[i * 2 - 1] = '0';
}
n += n - 1;
for (int i = 0; i < n; ++i) sum[i + 1] = sum[i] + (T[i] == '1');
int ans = ones_T;
f[0] = 0;
int p = 0;
for (int i = 1; i < n; ++i) {
f[i] = i;
if (f[p] > i) f[i] += min(f[p] - i, f[2 * p - i] - (2 * p - i));
if (f[i] >= f[p]) {
p = i;
while (2 * p >= f[p] + 1 && T[f[p] + 1] == T[2 * p - (f[p] + 1)]) ++f[p];
}
if (T[i] == '0') {
int ones_t = (sum[f[i] + 1] - sum[i]) * 2;
ans = min(ans, ones_T - ones_t / 2);
}
}
printf("%d", ans);
return 0;
}