给定 n ( 1 ≤ n ≤ 1 e 5 ) n\ \ (1\leq n\leq 1\mathrm{e}5) n (1≤n≤1e5)门课的成绩(整数) a 1 , ⋯ , a n ( 0 ≤ a i ≤ 100 ) a_1,\cdots,a_n\ \ (0\leq a_i\leq 100) a1,⋯,an (0≤ai≤100),求GPA,下取整保留到小数点后 k ( 1 ≤ k ≤ 1 e 5 ) k\ \ (1\leq k\leq 1\mathrm{e}5) k (1≤k≤1e5)位.
显然当且仅当 a = b a=b a=b时 a b \dfrac{a}{b} ba的整数部分为 1 1 1,其余情况整数部分为 0 0 0.
注意到 a b \dfrac{a}{b} ba小数点后第 1 1 1位即 10 a b \dfrac{10a}{b} b10a的整数部分,但 k k k最大为 1 e 5 1\mathrm{e}5 1e5,乘 1 e 5 1\mathrm{e}5 1e5次会爆掉.注意到对答案有贡献的只有 a a a模 b b b的余数,故每次 a ∗ = 10 a*=10 a∗=10,计算答案后 a % = b a\%=b a%=b即可.
void solve() {
int n, k; cin >> n >> k;
ll sum = 0;
for (int i = 0; i < n; i++) {
int a; cin >> a;
sum += a;
}
cout << sum / n << '.';
sum %= n;
for (int i = 0; i < k; i++) {
sum *= 10;
cout << sum / n;
sum %= n;
}
}
int main() {
solve();
}
有编号 1 ∼ n 1\sim n 1∼n的 n n n个任务,其中第 i ( 1 ≤ i ≤ n ) i\ \ (1\leq i\leq n) i (1≤i≤n)个任务消耗 h i h_i hi点生命和 s i s_i si点耐力,可获得 w i w_i wi的金币.初始时生命为 H H H,耐力为 S S S.当玩家生命 ≤ 0 \leq 0 ≤0时死亡.玩家耐力 < 0 <0 <0时,可用生命填补耐力的透支,如玩家耐力为 − 3 -3 −3且生命要减 3 3 3时,耐力会被置为 0 0 0,若玩家的生命不足以填补耐力的透支,玩家死亡.问玩家不死亡的前提下最多能获得多少金币.
第一行输入三个整数 n , H , S ( 1 ≤ n ≤ 1000 , 1 ≤ H ≤ 300 , 0 ≤ S ≤ 300 ) n,H,S\ \ (1\leq n\leq 1000,1\leq H\leq 300,0\leq S\leq 300) n,H,S (1≤n≤1000,1≤H≤300,0≤S≤300).接下来 n n n行每行输入三个整数 h , s , w ( 0 ≤ h , s ≤ 300 , 1 ≤ w ≤ 1 e 9 ) h,s,w\ \ (0\leq h,s\leq 300,1\leq w\leq 1\mathrm{e}9) h,s,w (0≤h,s≤300,1≤w≤1e9).
d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示只考虑前 i i i个任务、当前血量为 j j j、耐力为 k k k时的最大收益,压第一维,最终答案为 d p [ H ] [ S ] dp[H][S] dp[H][S].
状态转移方程: d p [ j ] [ k ] = max 1 ≤ i ≤ n d p [ j − h [ i ] + min { 0 , k − s [ i ] } ] [ max { 0 , k − s [ i ] } ] + w [ i ] \displaystyle dp[j][k]=\max_{1\leq i\leq n}dp[j-h[i]+\min\{0,k-s[i]\}][\max\{0,k-s[i]\}]+w[i] dp[j][k]=1≤i≤nmaxdp[j−h[i]+min{0,k−s[i]}][max{0,k−s[i]}]+w[i].
const int MAXN = 1005;
int n, H, S;
int h[MAXN], s[MAXN], w[MAXN]; // 消耗生命、消耗耐力、获得收益
ll dp[MAXN][MAXN]; // dp[i][j][k]表示只考虑前i个任务、当前血量为j、耐力为k时的最大收益
void solve() {
cin >> n >> H >> S;
for (int i = 1; i <= n; i++) cin >> h[i] >> s[i] >> w[i];
for (int i = 1; i <= n; i++) { // 枚举任务
for (int j = H; j; j--) { // 枚举生命,注意j≥1
for (int k = S; k >= 0; k--) { // 枚举耐力
if (j > h[i] && j + k > h[i] + s[i])
dp[j][k] = max(dp[j][k], dp[j - h[i] + min(0, k - s[i])][max(0, k - s[i])] + w[i]);
}
}
}
cout << dp[H][S];
}
int main() {
solve();
}
某盒子中重力场的方向在打开它之前不确定,盒子内部可视为一个二维网格,左下角的坐标为 ( 0 , 0 ) (0,0) (0,0),右上角的坐标为 ( 2 e 5 , 2 e 5 ) (2\mathrm{e}5,2\mathrm{e}5) (2e5,2e5).现有 n n n个事件,其中第 i ( 1 ≤ i ≤ n ) i\ \ (1\leq i\leq n) i (1≤i≤n)个事件 ( x i , y i ) (x_i,y_i) (xi,yi)表示在盒子中添加一个方块,其左下角坐标为 ( x i − 1 , y i − 1 ) (x_i-1,y_i-1) (xi−1,yi−1),右上角坐标为 ( x i , y i ) (x_i,y_i) (xi,yi).每个盒子中重力场的方向有 1 2 \dfrac{1}{2} 21的概率为水平的,有 1 2 \dfrac{1}{2} 21的概率为竖直的,水平的重力场会将所有方块推到盒子的左边,竖直的重力场会将所有方块推到盒子的下边.问每次打开一个盒子后所有方块构成的图形的周长.
第一行输入一个整数 n ( 1 ≤ n ≤ 2 e 5 ) n\ \ (1\leq n\leq 2\mathrm{e}5) n (1≤n≤2e5),表示事件数.接下来 n n n行每行输入两个整数 x , y ( 1 ≤ x , y ≤ 2 e 5 ) x,y\ \ (1\leq x,y\leq 2\mathrm{e}5) x,y (1≤x,y≤2e5),表示方块的坐标.数据保证任意两个方块坐标相异.
对每个事件输出两个整数,分别表示该盒子中重力场方向是竖直、水平时所有方块构成的图形的周长.
以竖直的重力场为例,水平同理.
(1)若新方块(红色,下同)所在列无其他方块,则 a n s + = 2 ans+=2 ans+=2,即方块上下两条边的长度.
(2)若新方块所在列的左边有方块(蓝色,下同),且原有的方块的高度之和不超过新方块的高度,则 a n s + + ans++ ans++,即新方块右边的长度.
(3)若新方块所在列的左边有方块,且原有的方块高度之和超过新方块的高度,则 a n s − − ans-- ans−−,即新方块左边的长度.
(4)新方块所在列的右边有方块的情况同理.
注意答案可能爆int.
const int MAXN = 2e5 + 5;
int hor[MAXN], ver[MAXN]; // 水平、竖直方向每个坐标的方块数
void solve() {
int n; cin >> n;
ll anshor = 0, ansver = 0;
while (n--) {
int x, y; cin >> x >> y;
if (!hor[x]) anshor += 2;
if (hor[x - 1] <= hor[x]) anshor++;
else anshor--;
if (hor[x + 1] <= hor[x]) anshor++;
else anshor--;
hor[x]++;
if (!ver[y]) ansver += 2;
if (ver[y - 1] <= ver[y]) ansver++;
else ansver--;
if (ver[y + 1] <= ver[y]) ansver++;
else ansver--;
ver[y]++;
cout << anshor << ' ' << ansver << endl;
}
}
int main() {
solve();
}
给定两 n × m n\times m n×m的 0 − 1 0-1 0−1矩阵 A A A和 B B B,两个矩阵中的 1 1 1各自连通(四连通),由它们得到一个 n × m n\times m n×m的 0 − 1 0-1 0−1矩阵 C C C,使得 C C C中的元素为 1 1 1当且仅当 A A A和 B B B的对应位置元素都为 1 1 1;否则 C C C中的元素为 0 0 0.现给定 n × m ( 3 ≤ n , m ≤ 500 ) n\times m\ \ (3\leq n,m\leq 500) n×m (3≤n,m≤500)的 0 − 1 0-1 0−1矩阵 C C C,构造两个满足上述要求的 0 − 1 0-1 0−1矩阵 A A A和 B B B,若有多组解,输出任一组.数据保证 C C C的外边界元素都为 0 0 0.
以如下图所示的矩阵为框架即可,其中红色、蓝色方格分别表示矩阵 A A A、 B B B中 1 1 1的位置
先将矩阵 A A A和 B B B中矩阵 C C C中为 1 1 1的位置置 1 1 1,对矩阵 C C C中为 0 0 0的位置保证 A A A与 B B B相反即可.
const int MAXN = 505;
int n, m;
int a[MAXN][MAXN], b[MAXN][MAXN], c[MAXN][MAXN];
void solve() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
char ch; cin >> ch;
c[i][j] = ch - '0';
if (c[i][j]) a[i][j] = b[i][j] = 1;
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m - 1; j++)
if (j == 1 || i & 1) a[i][j] = 1;
}
for (int i = 1; i <= n; i++) {
for (int j = m; j >= 2; j--)
if (j == m || (i % 2 == 0 && !a[i][j])) b[i][j] = 1;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) cout << a[i][j];
cout << endl;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) cout << b[i][j];
cout << endl;
}
}
int main() {
solve();
}
给一棵有根树的节点染黑色或白色,要求若一个节点染成黑色,则以它为根节点的子树中的节点也要染成黑色.给定整数 k ( 2 ≤ k ≤ 2 e 18 ) k\ \ (2\leq k\leq 2\mathrm{e}18) k (2≤k≤2e18),构造一棵以节点 1 1 1为根节点的有根树,使得它的染色方案恰有 k k k种,先输出节点数 n ( 1 ≤ n ≤ 1 e 5 ) n\ \ (1\leq n\leq 1\mathrm{e}5) n (1≤n≤1e5),再输出它的 ( n − 1 ) (n-1) (n−1)条边.数据保证有解.若有多组解,输出任一组.
设节点 u u u有 n n n个子节点 v 1 , ⋯ , v n v_1,\cdots,v_n v1,⋯,vn.若 u u u染黑色,则所有 v i ( 1 ≤ i ≤ n ) v_i\ \ (1\leq i\leq n) vi (1≤i≤n)只能染黑色,有 1 1 1种方案;若 u u u染白色,则所有 v i ( 1 ≤ i ≤ n ) v_i\ \ (1\leq i\leq n) vi (1≤i≤n)可染黑色或白色,有 2 n 2^n 2n种方案,故每个这样的单元的方案数为 2 n + 1 2^n+1 2n+1. d p [ u ] dp[u] dp[u]表示以 u u u为根节点的子树的方案数,显然 d p [ u ] = ∏ v ∈ s o n u d p [ v ] + 1 \displaystyle dp[u]=\prod_{v\in son_u}dp[v]+1 dp[u]=v∈sonu∏dp[v]+1,则最后整棵树染色的方案数有形式 k = ( 2 n 1 + 1 ) ( 2 n 2 + 1 ) ⋯ k=(2^{n_1}+1)(2^{n_2}+1)\cdots k=(2n1+1)(2n2+1)⋯.
由 2 2 2的幂次想到构造二叉树.考察如下四种单元的方案数(每个单元最上方的节点为子树的根节点):
(1)孤立的节点,方案数为 2 2 2.
(2)两个节点,方案数为 3 3 3.
(3)三个节点成链,方案数为 4 4 4.
(4)三个节点成树,方案数为 5 5 5.
综上,特判方案数为 2 2 2和 3 3 3的情况,二叉树不分叉的染色方案数为偶数,二叉树分叉的染色方案数为奇数,据此构造即可.
void solve() {
ll k; cin >> k;
vii ans;
int n = 1; // 当前用到的节点编号
while (k) {
if (k == 2) break; // 特判
if (k == 3) { // 特判
ans.push_back({ n,n + 1 });
n++;
break;
}
if (k & 1) {
ans.push_back({ n,n + 1 }), ans.push_back({ n,n + 2 }); // 分叉
n += 2;
k >>= 1;
}
else {
ans.push_back({ n,n + 1 }); // 不分叉
n++;
k--;
}
}
cout << n << endl;
for (auto& [u, v] : ans) cout << u << ' ' << v << endl;
}
int main() {
solve();
}
const int MAXN = 1e5 + 5;
ll k;
int n = 1; // 当前用到的节点编号
vi edges[MAXN];
void dfs(int u, ll cur) { // 当前节点、所需方案数
ll tmp = cur - 1; // 注意-1'
while (tmp % 2 == 0) {
edges[u].push_back(++n);
tmp >>= 1;
}
if (tmp == 1) return; // 只差全部染黑的情况
edges[u].push_back(++n);
dfs(n, tmp);
}
void solve() {
cin >> k;
dfs(1, k);
cout << n << endl;
for (int u = 1; u <= n; u++)
for (auto v : edges[u]) cout << u << ' ' << v << endl;
}
int main() {
solve();
}
有编号 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的施工队.现要建造 ( n − 1 ) (n-1) (n−1)条路连接这些城市,连接城市 i i i与城市 j j j时的花费为 gcd ( a i , a j ) \gcd(a_i,a_j) gcd(ai,aj).求连接 n n n个城市的最小代价.
a 1 , ⋯ , a n a_1,\cdots,a_n a1,⋯,an由如下代码产生:
第一行输入四个整数 n , L , R , s e e d ( 2 ≤ n ≤ 2 e 5 , 1 ≤ L ≤ R ≤ 2 e 5 , 1 ≤ s e e d ≤ 1 e 18 ) n,L,R,seed\ \ (2\leq n\leq 2\mathrm{e}5,1\leq L\leq R\leq 2\mathrm{e}5,1\leq seed\leq 1\mathrm{e}18) n,L,R,seed (2≤n≤2e5,1≤L≤R≤2e5,1≤seed≤1e18).
显然MST,但节点数最大为 2 e 5 2\mathrm{e}5 2e5,用Prim算法会TLE;图是 n n n阶无向完全图,边数为 n ( n − 1 ) 2 \dfrac{n(n-1)}{2} 2n(n−1),最大约 2 e 10 2\mathrm{e}10 2e10,用Kruskal算法会TLE.考察Kruskal算法最多能跑几阶无向完全图.令 m log m = 1 e 8 m\log m=1\mathrm{e}8 mlogm=1e8,解得 m = e W ( 1 e 8 ) ≈ 6.4 e 6 m=\mathrm{e}^{W(1\mathrm{e}8)}\approx 6.4\mathrm{e}6 m=eW(1e8)≈6.4e6.令 n ( n − 1 ) 2 = 6.4 e 6 \dfrac{n(n-1)}{2}=6.4\mathrm{e}6 2n(n−1)=6.4e6,解得 n ≈ 3758 n\approx 3758 n≈3758,此时求出各边边权的时间复杂度为 n ( n + 1 ) 2 ≈ 7 e 6 \dfrac{n(n+1)}{2}\approx 7\mathrm{e}6 2n(n+1)≈7e6.故 n ≤ 3758 n\leq 3758 n≤3758的情况可用Kruskal算法求解.
考察 n > 3758 n>3758 n>3758的情况:
(1) L = R L=R L=R时,显然答案为 ( n − 1 ) L (n-1)L (n−1)L.
*以下的解释不严谨,甚至可能错误,但代码可以AC.
(2) L ≠ R L\neq R L=R时,答案为 ( n − 1 ) (n-1) (n−1),因为 n n n较大时,元素间的 n ( n − 1 ) 2 \dfrac{n(n-1)}{2} 2n(n−1)对 gcd \gcd gcd中至少有 ( n − 1 ) (n-1) (n−1)个为 1 1 1,
而数组 a [ ] a[] a[]随机,可认为这 ( n − 1 ) (n-1) (n−1)个 1 1 1即构成MST的边的边权.
可以证明这样发生错误的概率足够小.
const int MAXN = 2e5 + 5, MAXM = MAXN << 2;
int L, R;
ull seed;
int a[MAXN];
ull xorshift64() {
ull x = seed;
x ^= x << 13, x ^= x >> 7, x ^= x << 17;
return seed = x;
}
int gen() { return xorshift64() % (R - L + 1) + L; }
namespace Kruskal {
int n, m; // 节点数、边数
struct Edge {
int u, v;
ll w;
bool operator<(const Edge& B) { return w < B.w; }
}edges[MAXM];
int fa[MAXN]; // 并查集的fa[]数组
void init() { // 初始化fa[]
for (int i = 1; i <= n; i++) fa[i] = i;
}
int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
ll kruskal() { // 返回最小生成树的最长边,图不连通时返回INFF
sort(edges, edges + m); // 按边权升序排列
ll res = 0; // 最小生成树的边权和
int cnt = 0; // 当前连的边数
for (int i = 0; i < m; i++) {
auto [u, v, w] = edges[i];
u = find(u), v = find(v);
if (u != v) {
fa[u] = v;
res += w;
cnt++;
}
}
if (cnt < n - 1) return INFF; // 图不连通
else return res;
}
}
using namespace Kruskal;
void solve() {
cin >> n >> L >> R >> seed;
for (int i = 1; i <= n; i++) a[i] = gen();
if (L == R) {
cout << (ll)L * (n - 1);
return;
}
if (n > 3758) {
cout << n - 1;
return;
}
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++)
edges[m++] = { i,j,gcd(a[i], a[j]) }, edges[m++] = { j,i,gcd(a[i], a[j]) };
}
init();
cout << kruskal();
}
int main() {
solve();
}
给定 n ( 1 ≤ n ≤ 4 e 5 ) n\ \ (1\leq n\leq 4\mathrm{e}5) n (1≤n≤4e5)个长度不超过 4 e 5 4\mathrm{e}5 4e5的字符串,问其中有多少对不同的字符串使得将两个字符串首尾相连并从中间切开后可得到两个相同的字符串.数据保证所有字符串的长度之和不超过 4 e 5 4\mathrm{e}5 4e5.
显然两个相同的字符串符合要求.
对不同的字符串,显然符合要求的只有上图中的情况,其中蓝色部分为串的公共前后缀,该长度的公共前后缀对答案的贡献即红色部分出现的次数.
用map对哈希值计数即可.
const int MAXN = 4e5 + 5;
namespace StringHash {
int n; // 字符串长度
char str[MAXN]; // 下标从1开始
const ll Base1 = 29, MOD1 = 1e9 + 7;
const ll Base2 = 131, MOD2 = 1e9 + 9;
ll ha1[MAXN], ha2[MAXN]; // 正着的哈希值
ll rha1[MAXN], rha2[MAXN]; // 反着的哈希值
ll pow1[MAXN], pow2[MAXN]; // Base1和Base2的乘方
void init() { // 预处理pow1[]、pow2[]
pow1[0] = pow2[0] = 1;
for (int i = 1; i <= n; i++) {
pow1[i] = pow1[i - 1] * Base1 % MOD1;
pow2[i] = pow2[i - 1] * Base2 % MOD2;
}
}
void pre() { // 预处理ha1[]、ha2[]
for (int i = 1; i <= n; i++) {
ha1[i] = (ha1[i - 1] * Base1 + str[i]) % MOD1;
ha2[i] = (ha2[i - 1] * Base2 + str[i]) % MOD2;
rha1[i] = (rha1[i - 1] * Base1 + str[n - i + 1]) % MOD1;
rha2[i] = (rha2[i - 1] * Base2 + str[n - i + 1]) % MOD2;
}
}
pll get_hash(int l, int r) { // 求子串str[l...r]正着的哈希值
ll res1 = ((ha1[r] - ha1[l - 1] * pow1[r - l + 1]) % MOD1 + MOD1) % MOD1;
ll res2 = ((ha2[r] - ha2[l - 1] * pow2[r - l + 1]) % MOD2 + MOD2) % MOD2;
return pll(res1, res2);
}
pll get_rhash(int l, int r) { // 求子串str[l...r]反着的哈希值
ll res1 = ((rha1[n - l + 1] - rha1[n - r] * pow1[r - l + 1]) % MOD1 + MOD1) % MOD1;
ll res2 = ((rha2[n - l + 1] - rha2[n - r] * pow2[r - l + 1]) % MOD2 + MOD2) % MOD2;
return pll(res1, res2);
}
bool IsPalindrome(int l, int r) { // 判断子串str[l...r]是否是回文串
return get_hash(l, r) == get_rhash(l, r);
}
pll add(pll a, pll b) {
ll res1 = (a.first + b.first) % MOD1;
ll res2 = (a.second + b.second) % MOD2;
return pll(res1, res2);
}
pll mul(pll& a, ll k) { // a *= Base的k次方
ll res1 = a.first * pow1[k] % MOD1;
ll res2 = a.second * pow2[k] % MOD2;
return pll(res1, res2);
}
};
using namespace StringHash;
map cnt1, cnt2; // 整个字符串出现的次数、字符串在其他字符串的内部出现的次数
void solve() {
n = MAXN - 1;
init();
ll ans = 0;
CaseT{
cin >> str + 1;
n = strlen(str + 1);
pre();
auto tmp = get_hash(1, n);
ans += cnt1[tmp]++; // 相同串出现的次数对答案的贡献,注意++的顺序
ans += cnt2[tmp]; // 该字符串在其他串的内部对答案的贡献
for (int len = 1; len * 2 <= n; len++) { // 枚举公共前后缀长度
auto pre = get_hash(1, len), suf = get_hash(n - len + 1, n);
if (pre == suf) {
auto mid = get_hash(len + 1, n - len); // 中间部分
ans += cnt1[mid]; // 与中间部分相同的串对答案的贡献
cnt2[mid]++; // 更新该字符串在其他串的内部出现的次数
}
}
}
cout << ans;
}
int main() {
solve();
}