A

算法标签: 模拟

问题陈述

给你一个长度为 NNN 的整数序列:A=(A1,A2,…,AN)A = (A_1, A_2, \ldots, A_N)A=(A1,A2,,AN)
请判断在 AAA 中是否有相同元素连续出现三次或三次以上的位置。
更正式地说,请判断是否存在一个整数 iii1≤i≤N−21 \le i \le N-21iN2 中的 Ai=Ai+1=Ai+2A_i = A_{i+1} = A_{i+2}Ai=Ai+1=Ai+2

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 110;

int n, w[N];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n;
    for (int i = 0; i < n; ++i) cin >> w[i];

    int i = 0, j = 2;
    while (j < n) {
        if (w[i] == w[j] && w[i] == w[i + 1]) {
            cout << "Yes" << "\n";
            return 0;
        }
        i++, j++;
    }
    cout << "No" << "\n";

    return 0;
}

B

算法标签: 栈, 模拟

问题陈述

有一叠 100100100 纸牌,每张都标有整数 000

处理 QQQ 个查询。每个查询都属于以下情况之一:

  • 输入 111 :将一张标有整数 xxx 的卡片放在堆栈顶部。
  • 类型 222 :取出堆栈顶端的卡片,并输出写在该卡片上的整数。在此问题的限制条件下,牌堆中总是至少有一张牌。
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 210;

int n;
int stack[N], top;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    top = 99;
    cin >> n;
    while (n--) {
        int op, val;
        cin >> op;
        if (op == 1) {
            cin >> val;
            stack[++top] = val;
        }
        else cout << stack[top--] << "\n";
    }

    return 0;
}

C

算法标签: 前缀和, 贪心

问题陈述

NNN 个黑球和 MMM 个白球。
每个球都有一个值。第 iii 个黑球( 1≤i≤N1 \le i \le N1iN )的值是 BiB_iBi ,第 jjj 个白球( 1≤j≤M1 \le j \le M1jM )的值是 WjW_jWj

选择零个或更多的球,使得所选黑球的数量至少等于所选白球的数量。在所有这样的选择中,求所选球的数值之和的最大值。

思路

很容易想到时间复杂度O(n2)O(n ^ 2)O(n2)的做法, 但是球的数量10510 ^ 5105级别, 会超时, 因此需要优化到O(n2)O(n ^ 2)O(n2)以下

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

typedef long long LL;
const int N = 2e5 + 10;

int n, m;
int b[N], w[N], gre;
LL s1[N], s2[N];

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	cin >> n >> m;
	for (int i = 1; i <= n; ++i) cin >> b[i];
	for (int i = 1; i <= m; ++i) cin >> w[i];

	sort(b + 1, b + n + 1);
	reverse(b + 1, b + n + 1);
	sort(w + 1, w + m + 1);
	reverse(w + 1, w + m + 1);

	for (int i = 1; i <= n; ++i) {
		if (b[i] > 0) gre = i;
		s1[i] = (LL) s1[i - 1] + b[i];
	}
	for (int i = 1; i <= m; ++i) s2[i] = (LL) s2[i - 1] + w[i];

	LL res = 0;
	// 枚举选择白球的数量
	for (int k = 0; k <= min(n, m); ++k) {
		int cnt = max(k, gre);
		res = max(res, s2[k] + s1[cnt]);
	}

	cout << res << "\n";
	return 0;
}


D

算法标签: dfsdfsdfs

问题陈述

给你一个简单相连的无向图,图中有 NNN 个顶点,编号为 111NNN,有 MMM 条边,编号为 111MMM。边 iii 连接顶点 uiu_iuiviv_ivi,其标签为 wiw_iwi

在从顶点 111 到顶点 NNN 的所有简单路径(不经过同一顶点多次的路径)中,求路径上边的标签的最小 XOR。

关于 XOR 的说明

对于非负整数 AAABBB,它们的 XOR A⊕BA \oplus BAB 定义如下:

  • A⊕BA \oplus BAB 的二进制表示中,当且仅当 AAABBB 的相同数位中恰好有一位是 111 时,2k (k≥0)2^k \ (k \ge 0)2k (k0) 对应数位上的数字是 111;否则,它是 000

例如,3⊕5=63 \oplus 5 = 635=6(二进制为:011⊕101=110011 \oplus 101 = 110011101=110)。

一般来说,kkk 个整数 p1,…,pkp_1, \dots, p_kp1,,pk 的 XOR 定义为 (⋯((p1⊕p2)⊕p3)⊕⋯⊕pk)(\cdots ((p_1 \oplus p_2) \oplus p_3) \oplus \cdots \oplus p_k)(((p1p2)p3)pk)。可以证明它与 p1,…,pkp_1, \dots, p_kp1,,pk 的顺序无关。

思路

注意到, 点的数量不大于101010, 直接暴搜

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

typedef long long LL;
const int N = 12, M = N * N;

int n, m;
int head[N], edge_end[M], next_edge[M], edge_index;
LL w[M];
bool vis[N];
LL res = 9e18;

void add(int ver1, int ver2, LL val) {
    edge_end[edge_index] = ver2, next_edge[edge_index] = head[ver1], w[edge_index] = val, head[ver1] = edge_index++;
}

void dfs(int u, LL curr) {
    if (u == n) {
        res = min(res, curr);
        return;
    }

    for (int i = head[u]; ~i; i = next_edge[i]) {
        int ver = edge_end[i];
        if (!vis[ver]) {
            vis[ver] = true;
            dfs(ver, curr ^ w[i]);
            vis[ver] = false;
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    memset(head, -1, sizeof head);
    cin >> n >> m;

    while (m--) {
        int u, v;
        LL val;
        cin >> u >> v >> val;
        add(u, v, val);
        add(v, u, val);
    }

    vis[1] = true;
    dfs(1, 0);

    cout << res << "\n";
    return 0;
}

E

算法标签: 贪心, dfsdfsdfs

问题陈述

给你整数 N,MN, MN,M 和长度为 MMM 的三个整数序列:X=(X1,X2,…,XM)X = (X_1, X_2, \ldots, X_M)X=(X1,X2,,XM)Y=(Y1,Y2,…,YM)Y = (Y_1, Y_2, \ldots, Y_M)Y=(Y1,Y2,,YM)Z=(Z1,Z2,…,ZM)Z = (Z_1, Z_2, \ldots, Z_M)Z=(Z1,Z2,,ZM)。保证 XXXYYY 中的所有元素都在 111NNN 之间。
我们称长度为 NNN 的非负整数序列 A=(A1,A2,…,AN)A = (A_1, A_2, \ldots, A_N)A=(A1,A2,,AN)好序列,当且仅当它满足以下条件时:

  • 对于每一个有 1≤i≤M1 \le i \le M1iM 的整数 iiiAXiA_{X_i}AXiAYiA_{Y_i}AYi 的 XOR 为 ZiZ_iZi
    判断是否存在一个好序列 A=(A1,A2,…,AN)A=(A_1,A_2,\ldots,A_N)A=(A1,A2,,AN),如果存在,找出一个使其元素之和 ∑i=1NAi\displaystyle \sum_{i=1}^N A_ii=1NAi 最小的好序列。
关于 XOR 的说明

对于非负整数 AAABBB,它们的 XOR A⊕BA \oplus BAB 定义如下:

  • A⊕BA \oplus BAB 的二进制表示中,当且仅当 AAABBB 的相同数位中恰好有一位是 111 时,2k (k≥0)2^k \ (k \ge 0)2k (k0) 对应数位上的数字是 111;否则,它是 000
    例如,3⊕5=63 \oplus 5 = 635=6(二进制为:011⊕101=110011 \oplus 101 = 110011101=110)。

思路

由题意, 分为两步

  • 是否存在该序列?
  • 如何最小化AiA_iAi
首先判断是否无解

AXi⊕AYi=ZiA_{X_i} \oplus A_{Y_i} = Z_iAXiAYi=Zi, 对于异或操作有以下性质
a⊕b=c,a⊕c=b,b⊕c=aa \oplus b = c, a \oplus c = b, b \oplus c = aab=c,ac=b,bc=a, 异或是可逆的

因为Xi,Yi,ZiX_i,Y_i, Z_iXi,Yi,Zi是确定的, AXiA_{X_i}AXiAYiA_{Y_i}AYi只要确定一个数, 就可以确定另外一个数

在这里插入图片描述
会形成如图的连通块, 在连通块中知道确定一个数, 整个连通块的数都会被确定
如果从uuuvvv出现了两条不一样的路径, 出现矛盾

如何找这些环的边?
在这里插入图片描述
维护并查集, 检查Z=Spath1⊕Spath2Z = S_{path_1} \oplus S_{path_2}Z=Spath1Spath2, 如果不相等无解

如何维护最小值?

在连通块中确定任何一个元素就能确定任意一个元素, 考虑树的根节点

因为异或操作是逐位异或的, 因此位与位之间是独立的, 每一位单独考虑, 因为要求最小, 因此要求111的数量是最小的

算法流程
  1. 将图构建成生成树, 检查非树边
  2. 求最小值, 逐位考虑, 看根节点是选000还是选111, 最后知道了根节点的值, 然后整个树的值就知道了
XOR 的性质

XOR 操作具有以下性质:

  • 自反性
    A⊕A=0 A \oplus A = 0 AA=0
  • 结合律
    (A⊕B)⊕C=A⊕(B⊕C) (A \oplus B) \oplus C = A \oplus (B \oplus C) (AB)C=A(BC)
  • 交换律
    A⊕B=B⊕A A \oplus B = B \oplus A AB=BA

由于这些性质,调整根节点的值 A[root]A[root]A[root]A[root]⊕offA[root] \oplus offA[root]off 会使得所有顶点的值 A[x]A[x]A[x] 变为 A[x]⊕offA[x] \oplus offA[x]off

一致性调整

假设根节点的值被调整为 A[root]⊕offA[root] \oplus offA[root]off,那么对于任意顶点 xxx,其值 A[x]A[x]A[x] 会被调整为:

A[x]⊕off=(A[root]⊕path_xor)⊕off=(A[root]⊕off)⊕path_xor A[x] \oplus off = (A[root] \oplus \text{path\_xor}) \oplus off = (A[root] \oplus off) \oplus \text{path\_xor} A[x]off=(A[root]path_xor)off=(A[root]off)path_xor

其中,path_xor\text{path\_xor}path_xor 是从根节点到顶点 xxx 的路径上所有边的 XOR 值
这种调整方式确保了所有顶点的值仍然满足边的 XOR 条件

#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>

using namespace std;

typedef long long LL;
const int N = 2e5 + 10, M = 2e5 + 10;

int n, m;
int head[N], edge_end[M], next_edge[M], w[M], edge_index;
int res[N];

void add(int ver1, int ver2, LL val) {
	edge_end[edge_index] = ver2, next_edge[edge_index] = head[ver1], w[edge_index] = val, head[ver1] = edge_index++;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	memset(head, -1, sizeof head);
	cin >> n >> m;

	for (int i = 0; i < m; ++i) {
		int u, v, w;
		cin >> u >> v >> w;
		u--, v--;
		add(u, v, w);
		add(v, u, w);
	}

	memset(res, -1, sizeof res);

	for (int x = 0; x < n; ++x) {
		if (res[x] != -1) continue;

		vector<int> q;
		q.push_back(x);
		res[x] = 0;
		int cnt[30]{0};

		for (int i = 0; i < q.size(); ++i) {
			int u = q[i];
			for (int j = 0; j < 30; ++j) cnt[j] += res[u] >> j & 1;

			for (int j = head[u]; ~j; j = next_edge[j]) {
				int ver = edge_end[j];

				if (res[ver] == -1) {
					res[ver] = res[u] ^ w[j];
					q.push_back(ver);
				}
				else if (res[ver] != (res[u] ^ w[j])) {
					cout << -1 << "\n";
					return 0;
				}
			}
		}

		int off = 0;
		for (int j = 0; j < 30; ++j) {
			if (cnt[j] > q.size() - cnt[j]) {
				off ^= 1 << j;
			}
		}

		for (int ver : q) res[ver] ^= off;
	}


	for (int i = 0; i < n; ++i) cout << res[i] << " ";
	cout << "\n";

	return 0;
}

F

算法标签: 分治, 树状数组, Fenwick−TreeFenwick-TreeFenwickTree

问题陈述

给你一个整数 N,MN, MN,M 和一个长度为 NNN 的非负整数序列 A=(A1,A2,…,AN)A = (A_1, A_2, \ldots, A_N)A=(A1,A2,,AN)

k=0,1,…,M−1k = 0, 1, \ldots, M-1k=0,1,,M1 求解下面的问题:

定义一个整数序列 B=(B1,B2,…,BN)B = (B_1, B_2, \ldots, B_N)B=(B1,B2,,BN),使 BiB_iBi 除以 MMM 后的余数是 Ai+kA_i + kAi+k。求 BBB 中的倒数。

什么是倒数?
序列 (A1,A2,…,AN)(A_1, A_2, \dots, A_N)(A1,A2,,AN) 的反转数是满足 1≤i<j≤N1 \le i < j \le N1i<jNAi>AjA_i > A_jAi>Aj 的整数对 (i,j)(i, j)(i,j) 的个数。

思路

Bi=(Ai+k)mod  MB_i = (A_i + k) \mod MBi=(Ai+k)modM. 求BBB的逆序对数量, 首先考虑简单情况, 如果没有mod  M\mod MmodM, 输出就是原AiA_iAi的逆序对数量

考虑简单情况, Bi=Ai+1B_i = A_i + 1Bi=Ai+1, 哪些逆序对数量发生改变?
Bi=M−1B_i= M - 1Bi=M1的那些数如果加111, mod  M\mod MmodM变为000, 逆序对数量发生变化, 而对于0≤Bi≤M−20 \le B_i \le M - 20BiM2的数字, 加上111再对MMM取模逆序对数量不会发生变化

因此考虑Bi=M−1B_i = M - 1Bi=M1的情况
在这里插入图片描述
会与每一个后面的非M−1M - 1M1的位置形成逆序对, 因为在mod  M\mod MmodM情况下, M−1M - 1M1就是最大值
在这里插入图片描述
逆序对的变化量Δcnt=−[M−1]+[0]\Delta cnt = -[M - 1] + [0]Δcnt=[M1]+[0], 也就是减去变化前M−1M - 1M1位置的逆序对数量, 加上变为000后与前面形成的逆序对数量
假设当前位置是ppp, 减去的逆序对数量等于n−p−cntM−1n - p - cnt_{M - 1}npcntM1

因为每个数加111, 因此变为000后前面与前面形成的逆序对数量等于p−1p - 1p1
以上是k=1k = 1k=1的逆序对数量, 以此类推计算到到k=M−1k = M - 1k=M1, 也就是mod  M\mod MmodM的最大余数

iii个数什么时候变成MMM?
M−AiM - A_iMAi时刻mod  M\mod MmodM变为000, 算法时间复杂度O(nlog⁡n)O(n\log n)O(nlogn)

#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>

using namespace std;

typedef long long LL;
const int N = 2e5 + 10;

int n, m;
int w[N], tr[N];
vector<int> change[N];

int lowbit(int val) {
	return val & -val;
}

void add(int u, int val){
	for (int i = u; i <= m; i += lowbit(i)) tr[i] += val;
}

int query(int u) {
	int res = 0;
	for (int i = u; i; i -= lowbit(i)) res += tr[i];
	return res;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	cin >> n >> m;

	LL res = 0;
	for (int i = 1; i <= n; ++i) {
		cin >> w[i];
		change[m - w[i]].push_back(i);
		res += query(m - w[i] - 1);
		add(m - w[i], 1);
	}

	cout << res << "\n";

	for (int k = 1; k < m; ++k) {
		// 枚举在k时刻从m - 1变为0的所有位置
		for (int j = 0, sz = change[k].size(); j < sz; ++j) {
			int p = change[k][j];
			res -= n - p - (sz - j - 1);
			res += p - 1 - j;
		}
		cout << res << "\n";
	}

	return 0;
}

G

算法标签: FWTFWTFWT, 状态压缩dpdpdp

问题陈述

有一个 H×WH \times WH×W 网格,每个单元格都包含 000111。从顶部起第 iii 行和从左侧起第 jjj 列的单元格中包含一个整数 Ai,jA_{i,j}Ai,j

您可以按照任意顺序执行以下两种操作任意多次:

  • 操作 X:选择一个整数 xxx1≤x≤H1 \leq x \leq H1xH)。对于每一个整数 1≤y≤W1 \leq y \leq W1yW,将 Ax,yA_{x,y}Ax,y 替换为 1−Ax,y1 - A_{x,y}1Ax,y
  • 操作 Y:选择一个整数 yyy1≤y≤W1 \leq y \leq W1yW)。对于每一个整数 1≤x≤H1 \leq x \leq H1xH,将 Ax,yA_{x,y}Ax,y 替换为 1−Ax,y1 - A_{x,y}1Ax,y

求替换后 ∑x=1H∑y=1WAx,y\displaystyle \sum_{x=1}^H \sum_{y=1}^W A_{x,y}x=1Hy=1WAx,y 的最小值。

思路

列的全部情况是O(218)O(2 ^ {18})O(218), 然后枚举行, 但是时间复杂度来到了O(H×2W)O(H\times 2 ^{W})O(H×2W), 因为HHH的范围是2×1052 \times 10 ^ 52×105, 会超时

考虑每一列的操作数是XXX, 设每一行的000的数量是c0c_0c0, 111的数量是c1c_1c1, 当c0>c1c_0 > c_1c0>c1的时候这一行不需要翻转
c0≤c1c_0 \le c_1c0c1的情况下, 翻转取得的结果会变好, 因此这一行最小的111的数量就是min⁡(c1,m−c1)\min (c1, m - c1)min(c1,mc1)

设每一行111的数量是pip_ipi, 那么总的111的数量的最小值就是

min⁡0≤X<2W{p(Bi⊕X),W−p(Bi⊕X)} \min _{ 0 \le X < 2 ^ W} \left \{ p(B_i \oplus X), W - p(B_i \oplus X) \right \} 0X<2Wmin{p(BiX),Wp(BiX)}

动态规划: f[X][j][c]f[X][j][c]f[X][j][c], 代表考虑前jjj列, 并且Bi⊕XB_i \oplus XBiXXXXccc位上不同的的数量, 换句话说, 就是列的操作数是XXX的情况下, 需要翻转的行的数量

因为每一行的最小值可以确定, 因此答案可以转化为

∑c=0Wdp[X][W][c]×min⁡(c,W−c). \sum_{c=0}^W dp[X][W][c] \times \min(c, W - c). c=0Wdp[X][W][c]×min(c,Wc).

时间复杂度O(W2×2W)O(W ^ 2\times 2 ^ W)O(W2×2W)

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 2e5 + 10, INF = 0x3f3f3f3f;

int r, c;
int row[N];

int f[20][20][1 << 20];
int ans = INF;
char str[20];

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	cin >> r >> c;
	for (int i = 1; i <= r; ++i) {
		cin >> str;
		for (int j = 0; j < c; ++j) {
			row[i] = row[i] << 1 | (str[j] - '0');
		}
		f[0][0][row[i]] += 1;
	}

	for (int i = 1; i <= c; ++i) {
		for (int j = 0; j <= c; ++j) {
			// 枚举对列的所有操作X
			for (int k = 0; k < 1 << c; ++k) {
				// 不翻转第i列
				f[i][j][k] += f[i - 1][j][k];
				// 翻转第i列
				f[i][j + 1][k] += f[i - 1][j][k ^ (1 << (i - 1))];
			}
		}
	}

	for (int k = 0; k < 1 << c; ++k) {
		int sum = 0;

		// 枚举翻转的列数
		for (int j = 0; j <= c; ++j) {
			sum += f[c][j][k] * min(j, c - j);
		}
		ans = min(ans, sum);
	}

	cout << ans << "\n";
	return 0;
}
Logo

集算法之大成!助力oier实现梦想!

更多推荐