时间限制: 1.0s 内存限制: 256.0MB 本题总分:10 分
小蓝有一个神奇的炉子用于将普通金属 O 冶炼成为一种特殊金属 X。这个 炉子有一个称作转换率的属性 V , V V,V V,V 是一个正整数,这意味着消耗 V V V 个普通金属 O 恰好可以冶炼出一个特殊金属 X,当普通金属 O 的数目不足 V V V 时,无法继续冶炼。
现在给出了 N N N 条冶炼记录,每条记录中包含两个整数 A A A 和 B B B,这表示本次投入了 A A A 个普通金属 O,最终冶炼出了 B B B 个特殊金属 X。每条记录都是独立的,这意味着上一次没消耗完的普通金属 O 不会累加到下一次的冶炼当中。
根据这 N N N 条冶炼记录,请你推测出转换率 V V V 的最小值和最大值分别可能是多少,题目保证评测数据不存在无解的情况。
第一行一个整数 N N N,表示冶炼记录的数目。
接下来输入 N N N 行,每行两个整数 A 、 B A、B A、B,含义如题目所述。
输出两个整数,分别表示 V V V 可能的最小值和最大值,中间用空格分开。
3
75 3
53 2
59 2
20 25
当 V = 20 V = 20 V=20 时,有:⌊ 75 75 75 20 20 20⌋ = 3 = 3 =3,⌊ 53 53 53 20 20 20⌋ = 2 = 2 =2,⌊ 59 59 59 20 20 20⌋ = 2 = 2 =2,可以看到符合所有冶炼记录。
当 V = 25 V = 25 V=25 时,有:⌊ 75 75 75 25 25 25⌋ = 3 = 3 =3,⌊ 53 53 53 25 25 25⌋ = 2 = 2 =2,⌊ 59 59 59 25 25 25⌋ = 2 = 2 =2,可以看到符合所有冶炼记录。
且再也找不到比 20 20 20 更小或者比 25 25 25 更大的符合条件的 V V V 值了。
对于 30 30 30% 的评测用例, 1 ≤ N ≤ 1 0 2 1\le N \le 10^2 1≤N≤102。
对于 60 60 60% 的评测用例, 1 ≤ N ≤ 1 0 3 1\le N \le 10^3 1≤N≤103。
对于 100 100 100% 的评测用例, 1 ≤ N ≤ 1 0 4 , 1 ≤ B ≤ A ≤ 1 0 9 1\le N \le 10^4,1\le B\le A\le 10^9 1≤N≤104,1≤B≤A≤109。
笨蒟蒻当初是推公式写的:
首先,最大值很好看出就是 a b \frac{a}{b} ba,主要是最小值怎么计算?就拿 75 75 75 和 3 3 3 来说,它要冶炼出来的金属不能超过 4 4 4 个,假设我们就让他 4 4 4 个呢,看它最小能满足什么情况,那么就是 75 4 \frac{75}{4} 475 得出来的是 18 18 18,但是 18 18 18 是不行的, 18 18 18 是那个第一个能冶炼出来的金属,必须要是在 18 18 18 的基础上 + 1 +1 +1 才可以,就是 19 19 19。有人说,最小值公式可以是 a b + 1 \frac{a}{b+1} b+1a 上取整,但这样是不可以的,假如说 12 12 12 和 3 3 3,那么我上取整的答案是 12 4 = 3 \frac{12}{4}=3 412=3,显然这样是不可以的,所以必须要是下取整 + 1 +1 +1,公式如下 a b + 1 + 1 \frac{a}{b+1}+1 b+1a+1,那么这就是最终答案了。
除了公式,当然也可以二分来写,这样也是能过的。我们可以直接二分最大值和最小值的答案,判断是否每个都能满足。
#include
#include
using namespace std;
#define int long long
#define inf 0x3f3f3f3f
int n, u, v = inf;
signed main()
{
cin >> n;
for (int i = 0; i < n; i++)
{
int a, b;
cin >> a >> b;
u = max(u, a / (b + 1) + 1);
v = min(v, a / b);
}
cout << u << " " << v << endl;
return 0;
}
#include
#include
using namespace std;
#define int long long
#define inf 0x3f3f3f3f
const int N = 1e4 + 10;
int n, k = inf, a[N], b[N];
signed main()
{
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> a[i] >> b[i];
if (a[i] / b[i] < k)k = a[i] / b[i];
}
int l = 1, r = k;
while (l < r)
{
int mid = l + r >> 1;
int i = 0;
for (i = 0; i < n; i++)
if (a[i] / mid != b[i])break;
if (i == n)r = mid;
else l = mid + 1;
}
cout << l << " " << k << endl;
return 0;
}
时间限制: 2.0s 内存限制: 256.0MB 本题总分:10 分
N N N 架飞机准备降落到某个只有一条跑道的机场。其中第 i i i 架飞机在 T i T_{i} Ti 时刻到达机场上空,到达时它的剩余油料还可以继续盘旋 D i D_{i} Di 个单位时间,即它最早可以于 T i T_{i} Ti 时刻开始降落,最晚可以于 T i + D i T_{i} + D_{i} Ti+Di 时刻开始降落。降落过程需要 L i L_{i} Li 个单位时间。
一架飞机降落完毕时,另一架飞机可以立即在同一时刻开始降落,但是不能在前一架飞机完成降落前开始降落。
请你判断 N N N 架飞机是否可以全部安全降落。
输入包含多组数据。
第一行包含一个整数 T T T,代表测试数据的组数。
对于每组数据,第一行包含一个整数 N N N。
以下 N N N 行,每行包含三个整数: T i T_{i} Ti, D i D_{i} Di 和 L i L_{i} Li。
对于每组数据,输出 Y E S YES YES 或者 N O NO NO,代表是否可以全部安全降落。
2 3
0 100 10
10 10 10
0 2 20
3
0 10 20
10 10 20
20 10 20
YES
NO
对于第一组数据,可以安排第 3 3 3 架飞机于 0 0 0 时刻开始降落, 20 20 20 时刻完成降落。安排第 2 2 2 架飞机于 20 20 20 时刻开始降落, 30 30 30 时刻完成降落。安排第 1 1 1 架飞机于 30 30 30 时刻开始降落, 40 40 40 时刻完成降落。
对于第二组数据,无论如何安排,都会有飞机不能及时降落。
对于 30 30 30% 的数据, N ≤ 2 N \le 2 N≤2。
对于 100 100 100% 的数据, 1 ≤ T ≤ 10 , 1 ≤ N ≤ 10 , 0 ≤ T i , D i , L i ≤ 1 0 5 1\le T \le 10,1\le N \le 10,0\le T_{i},D_{i},L_{i} \le10^{5} 1≤T≤10,1≤N≤10,0≤Ti,Di,Li≤105。
好几种贪心方式,不妨来看看:
第一种贪心方式:“先到先服务”,先对t排序,然后哪个下落早,哪个先走,但这样是错误的,设两个飞机分别为 t 1 , l 1 , d 1 , t 2 , l 2 , d 2 t1 ,l1, d1, t2, l2, d2 t1,l1,d1,t2,l2,d2,只要满足d1 >= t2 + l2
即证得失败,因为未限制 d d d,例:0 5 5 1 3 2
第二种贪心方式:“最晚开始降落早的先降落”,对 t + d t+d t+d 排序,然后哪个能先早的走就走,但这样也是错误的,笨蒟蒻考试的时候就这么想的,设两个飞机分别为 t 1 , l 1 , d 1 , t 2 , l 2 , d 2 t1, l1, d1, t2, l2, d2 t1,l1,d1,t2,l2,d2,只要满足L2 <= t1 + d1 - t2
即证得失败,因为未限制 L 2 L2 L2,例:0 5 10 1 6 2
第三种贪心方式:最晚降地的早降落,对 t + d + l t+d+l t+d+l 排序,然后贪心,但这样也是错误的,设两个飞机分别为 t 1 , l 1 , d 1 , t 2 , l 2 , d 2 t1 ,l1, d1, t2, l2, d2 t1,l1,d1,t2,l2,d2,只要满足t1 + l1 >= t2 + d2
则证明失败,例:2 5 3 0 4 7
那么怎么做?我们可以用全排列的方法做,就是枚举所有可能降落的情况,判断是否能全部满足,时间复杂度 O ( T n n ! ) O(Tnn!) O(Tnn!) 约为 3 e 8 3e8 3e8 理论上会超时,但是剪枝之后,实际上飞快,因为找到一组有解的即可。
还有状态压缩dp。
这就得用状态压缩dp去写了,怎么做?设 f [ i ] f[i] f[i] 是表示第 i i i 种情况能够降落的最小时间,把下标当成二进制来思考,那么这个降落就和飞机的顺序没有关系了,就不用考虑顺序了。然后在能满足飞机能落地的情况下,再去不断更新最小值,那么这个有多少种情况呢?有 1024 1024 1024 种情况,不断去更新,最终判断f[(1 << n) - 1]
是否成立即可。
#include
#include
#include
using namespace std;
const int N = 15;
struct Edge
{
int t, d, l;
}e[N];
bool st[N];
int n;
bool dfs(int u, int last)
{
if (u == n) return true;
for (int i = 0; i < n; i++)
{
int t = e[i].t, d = e[i].d, l = e[i].l;
if (!st[i] && t + d >= last)
{
st[i] = true;
if (dfs(u + 1, max(t, last) + l)) return true;
st[i] = false;
}
}
return false;
}
int main()
{
int T;
cin >> T;
while (T--)
{
cin >> n;
for (int i = 0; i < n; i++) scanf("%d%d%d", &e[i].t, &e[i].d, &e[i].l);
memset(st, false, sizeof(st));
if (dfs(0, 0)) cout << "YES" << endl;
else cout << "NO" << endl;
}
return 0;
}
#include
#include
#include
using namespace std;
#define inf 0x3f3f3f3f
const int N = 15, M = (1 << 10) + 10;
struct Edge
{
int t, d, l;
}e[N];
int T, n;
int f[M];
int main()
{
cin >> T;
while(T--)
{
cin >> n;
for(int i = 0; i < n; i++)
cin >> e[i].t >> e[i].d >> e[i].l;
memset(f, inf, sizeof(f));
f[0] = 0;
for(int i = 1; i < 1 << n; i++)
for(int j = 0; j < n; j++)
if(i >> j & 1)
{
int t = e[j].t, d = e[j].d, l = e[j].l;
if(t + d >= f[i - (1 << j)])
f[i] = min(f[i], max(f[i - (1 << j)], t) + l);
}
if(f[(1 << n) - 1] != inf) cout << "YES" << endl;
else cout << "NO" << endl;
}
return 0;
}
时间限制: 1.0s 内存限制: 256.0MB 本题总分:15 分
对于一个长度为 K K K 的整数数列: A 1 , A 2 , … , A K A_{1},A_{2},\dots,A_{K} A1,A2,…,AK,我们称之为接龙数列当且仅当 A i A_{i} Ai 的首位数字恰好等于 A i − 1 A_{i−1} Ai−1 的末位数字 ( 2 ≤ i ≤ K ) (2\le i\le K) (2≤i≤K)。
例如 12 , 23 , 35 , 56 , 61 , 11 12,23,35,56,61,11 12,23,35,56,61,11 是接龙数列; 12 , 23 , 34 , 56 12,23,34,56 12,23,34,56 不是接龙数列,因为 56 56 56 的首位数字不等于 34 34 34 的末位数字。所有长度为 1 1 1 的整数数列都是接龙数列。
现在给定一个长度为 N N N 的数列 A 1 , A 2 , … , A N A_{1},A_{2},\dots,A_{N} A1,A2,…,AN,请你计算最少从中删除多少个数,可以使剩下的序列是接龙序列?
第一行包含一个整数 N N N。
第二行包含 N N N 个整数 A 1 , A 2 , … , A N A_{1},A_{2},\dots,A_{N} A1,A2,…,AN。
一个整数代表答案。
5
11 121 22 12 2023
1
删除 22 22 22,剩余 11 , 121 , 12 , 2023 11,121,12,2023 11,121,12,2023 是接龙数列。
对于 20 20 20% 的数据, 1 ≤ N ≤ 20 1\le N \le 20 1≤N≤20。
对于 50 50 50% 的数据, 1 ≤ N ≤ 10000 1\le N \le 10000 1≤N≤10000。
对于 100 100 100% 的数据, 1 ≤ N ≤ 1 0 5 1\le N \le 10^{5} 1≤N≤105, 1 ≤ A i ≤ 1 0 9 1\le A_{i} \le 10^{9} 1≤Ai≤109。所有 A i A_{i} Ai 保证不包含前导 0 0 0。
不难看出这题用 LIS,可这满打满算也只能拿 50 50 50% 的分数,毕竟是 O ( n 2 ) O(n^2) O(n2),如何 AC 呢?
其实我们只要关心最后一个数结尾的序列的最长值就行了,那就可以直接优化掉一个循环!然后其实找首位和尾位也可以优化的,时间复杂度 O ( n ) O(n) O(n)。
#include
#include
#include
using namespace std;
#define int long long
int n,res;
int f[15];
signed main()
{
scanf("%lld", &n);
for(int i = 1; i <= n; i++)
{
char x[15];
scanf("%s", x);
int l = x[0] - '0',r = x[strlen(x) - 1] - '0';
f[r] = max(f[r], max(1ll, f[l] + 1));
}
for(int i = 0; i < 10; i++) res = max(res, f[i]);
cout << n - res << endl;
return 0;
}
时间限制: 2.0s 内存限制: 256.0MB 本题总分:15 分
小蓝得到了一副大小为 M × N M\times N M×N 的格子地图,可以将其视作一个只包含字符 ‘0’(代表海水)和 ‘1’(代表陆地)的二维数组,地图之外可以视作全部是海水, 每个岛屿由在上/下/左/右四个方向上相邻的 ‘1’ 相连接而形成。
在岛屿 A 所占据的格子中,如果可以从中选出 k k k 个不同的格子,使得 他们的坐标能够组成一个这样的排列: ( x 0 , y 0 ) , ( x 1 , y 1 ) , . . . , ( x k − 1 , y k − 1 ) (x_{0},y_{0}),(x_{1},y_{1}),...,(x_{k−1},y_{k−1}) (x0,y0),(x1,y1),...,(xk−1,yk−1),其中 ( x ( i + 1 ) (x_{(i+1)} (x(i+1)% k , y ( i + 1 ) _{k},y_{(i+1)} k,y(i+1)% k ) _{k} ) k) 是由 ( x i , y i ) (x_{i},y_{i}) (xi,yi) 通过上/下/左/右移动一次得来的 ( 0 ≤ i ≤ k − 1 ) (0 \le i \le k−1) (0≤i≤k−1), 此时这 k k k 个格子就构成了一个 “环”。如果另一个岛屿 B 所占据的格子全部位于 这个 “环” 内部,此时我们将岛屿 B 视作是岛屿 A 的子岛屿。若 B 是 A 的子岛屿,C 又是 B 的子岛屿,那 C 也是 A 的子岛屿。
请问这个地图上共有多少个岛屿?在进行统计时不需要统计子岛屿的数目。
第一行一个整数 T T T,表示有 T T T 组测试数据。
接下来输入 T T T 组数据。对于每组数据,第一行包含两个用空格分隔的整数 M 、 N M、N M、N 表示地图大小;接下来输入 M M M 行,每行包含 N N N 个字符,字符只可能是 ‘0’ 或 ‘1’。
对于每组数据,输出一行,包含一个整数表示答案。
2
5 5
01111
11001
10101
10001
11111
5 6
111111
100001
010101
100001
111111
1
3
对于第一组数据,包含两个岛屿,下面用不同的数字进行了区分:
01111
11001
10201
10001
11111
岛屿 2 2 2 在岛屿 1 1 1 的 “环” 内部,所以岛屿 2 2 2 是岛屿 1 1 1 的子岛屿,答案为 1 1 1。
对于第二组数据,包含三个岛屿,下面用不同的数字进行了区分:
111111
100001
020301
100001
111111
注意岛屿 3 3 3 并不是岛屿 1 1 1 或者岛屿 2 2 2 的子岛屿,因为岛屿 1 1 1 和岛屿 2 2 2 中均没有 “环”。
对于 30 30 30% 的评测用例, 1 ≤ M , N ≤ 10 1\le M,N \le 10 1≤M,N≤10。
对于 100 100 100% 的评测用例, 1 ≤ T ≤ 10 , 1 ≤ M , N ≤ 50 1\le T \le 10,1\le M,N \le 50 1≤T≤10,1≤M,N≤50。
一道 dfs/bfs 的题?如何思考?最关键的是那个环!那么该怎么判断那个岛是否在环里呢?笨蒟蒻是这样想的,一开始先找 1 1 1 对吧,大多数人也这样想,然后那个 1 1 1 如果在边界,那必定就是一个岛(要算进去答案的),就这样一直找完 1 1 1 的所有点,那然后如果不是呢?或者说每个点都不在边界呢?那咱就找 0 0 0 呗。这样想啊,假设它不是一个环,那么从那个点开始往外走,就一定有出路!因为它没有被包围,只要一直找,看出路是否能找到边界即可!那么问题就简化为一个四联通+八联通的dfs/bfs了(笨蒟蒻当时是写 dfs 的,当然用 bfs 也是可以的),四联通就是找 1 1 1,把每个点都变成’#',当然用其它符号也是可以的,然后8联通找 0 0 0,找 0 0 0 的出路,能否到边界记得一定要剪枝,不然过不了吧(猜的)。然后时间复杂度呢? O ( T n 2 ) O(Tn^{2}) O(Tn2)(剪枝)。
再来说说网上的题解是怎么讲的,也非常有意思。首先在最外围补一圈 0 0 0,比如说:
01111
11001
10101
10001
11111
然后把它变成:
0000000
0011110
0110010
0101010
0100010
0111110
0000000
然后就可以从最上面 ( 0 , 0 ) \left(0,0\right) (0,0) 开始dfs/bfs了找所有八联通的 0 0 0,然后标记,剩下的没有被标记过的联通块的个数就是答案了,那么如何找呢?就用四联通 bfs/dfs 即可找出,时间复杂度 O ( T n 2 ) O(Tn^{2}) O(Tn2),但同样两遍 dfs,这个不容易爆,因为我写的是 1 1 1 和 0 0 0 同时找,它这个是先找 0 0 0 再找连通块。
#include
#include
#include
using namespace std;
const int N = 55;
char g[N][N];
bool st[N][N];
bool flag;
int t, ans;
int n, m;
int dx[] = {0, 0, -1, 1}, dy[] = {1, -1, 0, 0};
void dfs2(int x, int y)
{
if (flag)return;
st[x][y] = true;
for (int i = -1; i <= 1; i++)
for (int j = -1; j <= 1; j++)
{
if (i == 0 && j == 0) continue;
int nx = i + x, ny = j + y;
if (nx < 0 || nx >= n || ny < 0 || ny >= m)
{
flag = true;
break;
}
if (st[nx][ny])
continue;
if (g[nx][ny] == '0') dfs2(nx, ny);
}
}
void dfs1(int x, int y)
{
g[x][y] = '#';
for (int i = 0; i < 4; i++)
{
int nx = x + dx[i], ny = y + dy[i];
if (nx < 0 || nx >= n || ny < 0 || ny >= m)
{
flag = true;
continue;
}
if (g[nx][ny] == '1') dfs1(nx, ny);
if (g[nx][ny] == '0') dfs2(nx, ny);
}
}
int main()
{
cin >> t;
while (t--)
{
ans = 0;
cin >> n >> m;
memset(g, 0, sizeof(g));
memset(st, 0, sizeof(st));
for (int i = 0; i < n; i++) scanf("%s", g[i]);
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
{
if (g[i][j] == '1')
{
dfs1(i, j);
if (flag)ans++;
memset(st, false, sizeof(st));
flag = false;
}
}
cout << ans << endl;
}
return 0;
}
#include
#include
#include
using namespace std;
const int N=55;
char g[N][N];
bool st[N][N];
bool flag;
int t, ans;
int n, m;
int dx[] = {0, 0, -1, 1}, dy[] = {1, -1, 0, 0};
void dfs_0(int x, int y)
{
st[x][y] = true;
for(int i = -1; i <= 1; i++)
for(int j = -1; j <= 1; j++)
{
if(i == 0 && j == 0) continue;
int nx = x + i, ny = y + j;
if(nx < 0 || nx > n + 1 || ny < 0 || ny > m + 1 || st[nx][ny]) continue;
if(!g[nx][ny]) dfs_0(nx, ny);
}
}
void dfs_1(int x, int y)
{
st[x][y] = true;
for(int i = 0; i < 4; i++)
{
int nx = x + dx[i], ny = y + dy[i];
if(nx < 1 || nx > n || ny < 1 || ny > m || st[nx][ny]) continue;
dfs_1(nx, ny);
}
}
int main()
{
cin>>t;
while(t--)
{
memset(g, 0, sizeof(g));
memset(st, false, sizeof(st));
cin >> n >> m;
ans = 0;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
{
char c;
cin >> c;
g[i][j] = c - '0';
}
dfs_0(0, 0);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
{
if(!st[i][j])
{
dfs_1(i, j);
ans++;
}
}
cout << ans << endl;
}
return 0;
}
时间限制: 1.0s 内存限制: 256.0MB 本题总分:20 分
程序猿圈子里正在流行一种很新的简写方法:对于一个字符串,只保留首尾字符,将首尾字符之间的所有字符用这部分的长度代替。例如 internationalization 简写成 i18n,Kubernetes (注意连字符不是字符串的一部分)简写成 K8s, Lanqiao 简写成 L5o 等。
在本题中,我们规定长度大于等于 K K K 的字符串都可以采用这种简写方法 (长度小于 K K K 的字符串不配使用这种简写)。
给定一个字符串 S S S 和两个字符 c 1 c_{1} c1 和 c 2 c_{2} c2,请你计算 S S S 有多少个以 c 1 c_{1} c1 开头 c 2 c_{2} c2 结尾的子串可以采用这种简写?
第一行包含一个整数 K K K。 第二行包含一个字符串 S S S 和两个字符 c 1 c_{1} c1 和 c 2 c_{2} c2。
一个整数代表答案。
4
abababdb a b
6
符合条件的子串如下所示,中括号内是该子串:
[abab]abdb
[ababab]db
[abababdb]
ab[abab]db
ab[ababdb]
abab[abdb]
对于 20 20 20% 的数据, 2 ≤ K ≤ ∣ S ∣ ≤ 10000 2\le K \le |S|\le 10000 2≤K≤∣S∣≤10000。
对于 100 100 100% 的数据, 2 ≤ K ≤ ∣ S ∣ ≤ 5 × 1 0 5 2 \le K \le |S|\le 5\times 10^{5} 2≤K≤∣S∣≤5×105。 S S S 只包含小写字母。 c 1 c_{1} c1 和 c 2 c_{2} c2 都 是小写字母。
∣ S ∣ |S| ∣S∣ 代表字符串 S S S 的长度。
这题其实有很多方法的,可以用二分、双指针、前缀和。一定要记得要开 ll!
那么笨蒟蒻当时是用双指针来写的,因为好写,但其实都一样的。具体思路就是枚举每一位 i i i,找到对应的 j j j 即可,由于递增,所以 j j j 不用改变的。
那二分呢,就是用二分找第一个满足对应的 j j j 即可。
前缀和呢,维护给定首字母出现的次数,然后用 j j j 维护,找到尾字母时,然后累加答案。
#include
#include
#include
#include
using namespace std;
#define int long long
const int N = 5e5 + 10;
int k, ans;
int u[N], v[N];
int cnt1, cnt2;
string s;
char a[2], b[2];
signed main()
{
cin >> k >> s >> a >> b;
for (int i = 0; i < s.size(); i++)
{
if(s[i] == a[0]) u[cnt1++] = i + 1;
if(s[i] == b[0]) v[cnt2++] = i + 1;
}
int j = 0;
for (int i = 0; i < cnt1; i++)
{
while(v[j] - u[i] < k - 1 && j < cnt2) j++;
if(j >= cnt2) break;
ans += cnt2 - j;
}
cout << ans << endl;
return 0;
}
#include
#include
#include
#include
using namespace std;
#define int long long
const int N = 5e5 + 10;
int k, ans;
int u[N], v[N];
int cnt1, cnt2;
string s;
char a[2], b[2];
signed main()
{
cin >> k >> s >> a >> b;
for (int i = 0; i < s.size(); i++)
{
if (s[i] == a[0]) u[cnt1++] = i + 1;
if (s[i] == b[0]) v[cnt2++] = i + 1;
}
for (int i = 0; i < cnt1; i++)
{
int l = 0, r = cnt2;
while (l < r)
{
int mid = l + r >> 1;
if (v[mid] - u[i] >= k - 1) r = mid;
else l = mid + 1;
}
ans += cnt2 - l;
}
cout << ans << endl;
return 0;
}
#include
#include
#include
#include
using namespace std;
#define int long long
const int N = 5e5 + 10;
int k, ans;
int u[N];
int cnt1, cnt2;
string s;
char a[2], b[2];
signed main()
{
cin >> k >> s >> a >> b;
for (int i = 0, j = 0; i < s.size(); i++)
{
if (s[i] == a[0]) j++;
u[i] = j;
if (i > k - 2 && s[i] == b[0]) ans += u[i - k + 1];
}
cout << ans << endl;
return 0;
}
时间限制: 1.0s 内存限制: 256.0MB 本题总分:20 分
给定一个长度为 N N N 的整数数列: A 1 , A 2 , … , A N A_{1},A_{2},\dots,A_{N} A1,A2,…,AN。你要重复以下操作 K K K 次: 每次选择数列中最小的整数(如果最小值不止一个,选择最靠前的),将其删除。并把与它相邻的整数加上被删除的数值。
输出 K K K 次操作后的序列。
第一行包含两个整数 N N N 和 K K K。
第二行包含 N N N 个整数, A 1 , A 2 , A 3 , … , A N A_{1},A_{2},A_{3},\dots,A_{N} A1,A2,A3,…,AN。
输出 N − K N−K N−K 个整数,中间用一个空格隔开,代表 K K K 次操作后的序列。
5 3
1 4 2 8 7
17 7
数列变化如下,中括号里的数是当次操作中被选择的数:
[1] 4 2 8 7
5 [2] 8 7
对于 20 20 20% 的数据, 1 ≤ K < N ≤ 10000 1\le K < N \le10000 1≤K<N≤10000。
对于 100 100 100% 的数据, 1 ≤ K < N ≤ 5 × 105 , 0 ≤ A i ≤ 1 0 8 1\le K < N \le 5\times 105,0\le A_{i} \le 10^{8} 1≤K<N≤5×105,0≤Ai≤108。
当时想用双链表写的,因为手写链表写的比较少,不熟练,最后写是写了一个,还是单链表都有bug,不敢交,还是写了个暴力上去,拿 20 20 20% 太菜了qwq。
说说写法:可以用优先队列维护最小值+双链表都行。还有线段树的方法。
#include
#include
#include
#include
using namespace std;
#define int long long
#define fi first
#define se second
typedef pairPII;
const int N = 5e5 + 10;
int n, k;
int a[N], l[N], r[N];
priority_queue, greater>q;
void dele(int id)
{
l[r[id]] = l[id];
r[l[id]] = r[id];
a[l[id]] += a[id];
a[r[id]] += a[id];
}
signed main()
{
scanf("%lld%lld", &n, &k);
r[0] = 1, l[n + 1] = n;
for(int i = 1; i <= n; i++)
{
scanf("%lld", &a[i]);
l[i] = i - 1, r[i] = i + 1;
q.push({a[i], i});
}
while(k--)
{
auto t = q.top();
q.pop();
int u = t.fi, v = t.se;
if(u != a[v])
{
q.push({a[v], v});
k++;
}
else dele(v);
}
for(int i = r[0]; i != n + 1; i = r[i]) cout << a[i] << ' ';
return 0;
}
// 空着先,有空再补
时间限制: 5.0s 内存限制: 256.0MB 本题总分:25 分
某景区一共有 N N N 个景点,编号 1 1 1 到 N N N。景点之间共有 N − 1 N −1 N−1 条双向的摆渡车线路相连,形成一棵树状结构。在景点之间往返只能通过这些摆渡车进行, 需要花费一定的时间。
小明是这个景区的资深导游,他每天都要按固定顺序带客人游览其中 K K K 个景点: A 1 , A 2 , … , A K A_{1},A_{2},\dots,A_{K} A1,A2,…,AK。今天由于时间原因,小明决定跳过其中一个景点,只带游客按顺序游览其中 K − 1 K−1 K−1 个景点。具体来说,如果小明选择跳过 A i A_{i} Ai,那么他会按顺序带游客游览 A 1 , A 2 , … , A i − 1 , A i + 1 , … , A K , ( 1 ≤ i ≤ K ) A_{1},A_{2},\dots,A_{i−1},A_{i+1},\dots ,A_{K}, (1\le i\le K) A1,A2,…,Ai−1,Ai+1,…,AK,(1≤i≤K)。
请你对任意一个 A i A_{i} Ai,计算如果跳过这个景点,小明需要花费多少时间在景点之间的摆渡车上?
第一行包含 2 2 2 个整数 N N N 和 K K K。
以下 N − 1 N−1 N−1 行,每行包含 3 3 3 个整数 u , v u,v u,v 和 t t t,代表景点 u u u 和 v v v 之间有摆渡车线路,花费 t t t 个单位时间。
最后一行包含 K K K 个整数 A 1 , A 2 , … , A K A_{1},A_{2},\dots,A_{K} A1,A2,…,AK 代表原定游览线路。
输出 K K K 个整数,其中第 i i i 个代表跳过 A i A_{i} Ai 之后,花费在摆渡车上的时间。
6 4
1 2 1
1 3 1
3 4 2
3 5 2
4 6 3
2 6 5 1
10 7 13 14
原路线是 2 → 6 → 5 → 1 2→6→5→1 2→6→5→1。
当跳过 2 2 2 时,路线是 6 → 5 → 1 6 → 5 → 1 6→5→1,其中 6 → 5 6 → 5 6→5 花费时间 3 + 2 + 2 = 7 3 + 2 + 2 = 7 3+2+2=7, 5 → 1 5→1 5→1 花费时间 2 + 1 = 3 2 + 1 = 3 2+1=3,总时间花费 10 10 10。
当跳过 6 6 6 时,路线是 2 → 5 → 1 2 → 5 → 1 2→5→1,其中 2 → 5 2 → 5 2→5 花费时间 1 + 1 + 2 = 4 1 + 1 + 2 = 4 1+1+2=4, 5 → 1 5→1 5→1 花费时间 2 + 1 = 3 2 + 1 = 3 2+1=3,总时间花费 7 7 7。
当跳过 5 5 5 时,路线是 2 → 6 → 1 2 → 6 → 1 2→6→1,其中 2 → 6 2 → 6 2→6 花费时间 1 + 1 + 2 + 3 = 7 1 + 1 + 2 + 3 = 7 1+1+2+3=7, 6 → 1 6→1 6→1 花费时间 3 + 2 + 1 = 6 3 + 2 + 1 = 6 3+2+1=6,总时间花费 13 13 13。
当跳过 1 1 1 时,路线时 2 → 6 → 5 2 → 6 → 5 2→6→5,其中 2 → 6 2 → 6 2→6 花费时间 1 + 1 + 2 + 3 = 7 1 + 1 + 2 + 3 = 7 1+1+2+3=7, 6 → 5 6→5 6→5 花费时间 3 + 2 + 2 = 7 3 + 2 + 2 = 7 3+2+2=7,总时间花费 14 14 14。
对于 20 20 20% 的数据, 2 ≤ K ≤ N ≤ 1 0 2 2\le K \le N \le 10^{2} 2≤K≤N≤102。
对于 40 40 40% 的数据, 2 ≤ K ≤ N ≤ 1 0 4 2\le K \le N \le10^{4} 2≤K≤N≤104。
对于 100 100 100% 的数据, 2 ≤ K ≤ N ≤ 1 0 5 2 \le K \le N \le 10^{5} 2≤K≤N≤105, 1 ≤ u , v , A i ≤ N 1 \le u,v,A_{i} \le N 1≤u,v,Ai≤N, 1 ≤ t ≤ 1 0 5 1 \le t \le 10^{5} 1≤t≤105。保证 A i A_{i} Ai 两两不同。
笨蒟蒻居然还有时间看这种题目,讲的是什么呢?让我们从一个地方依此走到好几处地方,求花费的路径最小时间。一眼图论,考前只背过简单的的板子,难受。太菜了,当时没学lca,不会……可是笨蒟蒻自己当时都忘记用floyd骗分了QwQ。
先来一波floyd骗分大法,理论上能至少通过 20 20 20%。
floyd骗分大法,代码如下:
#include
#include
#include
using namespace std;
const int N = 5e3 + 10;
#define int long long
#define inf 0x3f3f3f3f
int n, k, ans;
int kk[N], f[N][N];
void floyd()
{
for(int u = 1; u <= n; u++)
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
f[i][j] = min(f[i][j], f[i][u] + f[u][j]);
}
signed main()
{
cin >> n >> k;
memset(f, inf, sizeof(f));
for(int i = 1; i <= n; i++) f[i][i] = 0;
for(int i = 0; i < n - 1; i++)
{
int a, b, c;
scanf("%lld%lld%lld", &a, &b, &c);
f[a][b] = f[b][a] = min(f[a][b], c);
}
floyd();
for(int i = 1; i <= k; i++) scanf("%lld", &kk[i]);
for(int i = 2; i <= k; i++) ans += f[kk[i - 1]][kk[i]];
for(int i = 1; i <= k; i++)
{
if(i == 1) printf("%lld ", ans - f[kk[1]][kk[2]]);
else if(i == k) printf("%lld\n", ans - f[kk[k - 1]][kk[k]]);
else printf("%lld ", ans - f[kk[i - 1]][kk[i]] - f[kk[i]][kk[i + 1]] + f[kk[i - 1]][kk[i + 1]]);
}
return 0;
}
OK,回归本题,这题要用到 lca,然后前缀和维护一下即可。求 lca 好几种方法,一种是倍增,一种是 tarjan,还有树剖。
简单说一下倍增思路:倍增是一种在线的做法,先预处理一下所有结点的深度 d e p t h depth depth,以及到根节点的距离 d i s t dist dist,还有每个点跳 2 k 2^k 2k 的祖宗结点记为 f a fa fa,最后求 lca 的时候记得从后往前跳,用的思想是二进制拼凑,最后的两点间距离就是dist[a] + dist[b] - 2 * dist[lca(a, b)]
。时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)。
简单说一下 tarjan 思路:tarjan 是一种离线做法,先预处理一下到根节点的距离 d i s t dist dist,然后先把所有询问存储下来,设置一个状态 s t st st,已经搜过的点设为 1 1 1,正在搜索的点设为 2 2 2,没被搜索的点设为 0 0 0,然后正在搜索的点对于已经搜过的点就是一条分支吧,利用这个来把所有最近公共祖先找出来,然后算距离存到 r e s res res 里。时间复杂度 O ( n + m ) O(n + m) O(n+m)。
简单说一下树剖思路:树剖也是一种在线做法,先预处理出所有树相关的信息(深度,父节点,重儿子,距离,重链头,儿子数量等),然后找 lca 就找每个链头,看是否相同。时间复杂度 O ( 2 n + l o g n ) O(2n + logn) O(2n+logn)。
#include
#include
#include
using namespace std;
#define int long long
#define inf 0x3f3f3f3f
const int N = 1e5 + 10, M = N * 2;
int n, m, sum;
int e[M], ne[M], w[M], h[N], idx;
int depth[N], q[N], dist[N], v[N];
int fa[N][18];
void add(int a, int b, int c)
{
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
void bfs(int root)
{
memset(depth, inf, sizeof(depth));
depth[0] = 0, depth[root] = 1;
int hh = 0, tt = 0;
q[0] = root;
while(hh <= tt)
{
int t = q[hh++];
for(int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if(depth[j] > depth[t] + 1)
{
depth[j] = depth[t] + 1;
q[++tt] = j;
fa[j][0] = t;
dist[j] = dist[t] + w[i];
for(int k = 1; k < 18; k++)
fa[j][k] = fa[fa[j][k - 1]][k - 1];
}
}
}
}
int lca(int a, int b)
{
if(depth[a] < depth[b]) swap(a, b);
for(int k = 17; k >= 0; k--)
if(depth[fa[a][k]] >= depth[b]) a = fa[a][k];
if(a == b) return a;
for(int k = 17; k >= 0; k--)
if(fa[a][k] != fa[b][k])
{
a = fa[a][k];
b = fa[b][k];
}
return fa[a][0];
}
int getdis(int a, int b)
{
return dist[a] + dist[b] - 2 * dist[lca(a, b)];
}
signed main()
{
cin >> n >> m;
memset(h, -1, sizeof(h));
for(int i = 1; i < n; i++)
{
int a, b, c;
scanf("%lld%lld%lld", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
bfs(1);
for(int i = 1; i <= m; i++) scanf("%lld", &v[i]);
for(int i = 2; i <= m; i++) sum += getdis(v[i], v[i - 1]);
for(int i = 1; i <= m; i++)
{
if(i == 1) printf("%lld ", sum - getdis(v[i], v[i + 1]));
else if(i == m) printf("%lld\n", sum - getdis(v[i - 1], v[i]));
else printf("%lld ", sum - getdis(v[i - 1], v[i]) - getdis(v[i], v[i + 1]) + getdis(v[i - 1], v[i + 1]));
}
return 0;
}
#include
#include
#include
#include
using namespace std;
#define int long long
#define fi first
#define se second
#define pb push_back
const int N = 1e5 + 10, M = 2 * N;
typedef pairPII;
int n, m, ord, ans;
int e[M], ne[M], w[M], h[N], idx;
int dist[N], p[N], st[N], v[N], res[M * 2];
vectorquery[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int find(int x)
{
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}
void dfs(int u, int fa)
{
for(int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if(j == fa) continue;
dist[j] = dist[u] + w[i];
dfs(j, u);
}
}
void tarjan(int u)
{
st[u] = 1;
for(int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if(!st[j])
{
tarjan(j);
p[j] = u;
}
}
for(auto item : query[u])
{
int y = item.fi, id = item.se;
if(st[y] == 2)
{
int anc = find(y);
res[id] = dist[u] + dist[y] - dist[anc] * 2;
}
}
st[u] = 2;
}
signed main()
{
cin >> n >> m;
memset(h, -1, sizeof(h));
for(int i = 1; i < n; i++)
{
int a, b, c;
scanf("%lld%lld%lld", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
for(int i = 0; i < m; i++) scanf("%lld", &v[i]);
for(int i = 0; i < m - 1; i++)
{
query[v[i]].pb({v[i + 1], ord});
query[v[i + 1]].pb({v[i], ord});
ord++;
}
for(int i = 0; i < m - 2; i++)
{
query[v[i]].pb({v[i + 2], ord});
query[v[i + 2]].pb({v[i], ord});
ord++;
}
for(int i = 1; i <= n; i++) p[i] = i;
dfs(1, -1);
tarjan(1);
for(int i = 0; i < m - 1; i++) ans += res[i];
for(int i = 0; i < m; i++)
{
if(!i) printf("%lld ", ans - res[i]);
else if(i == m - 1) printf("%lld\n", ans - res[i - 1]);
else printf("%lld ", ans - res[i - 1] - res[i] + res[i + m - 2]);
}
return 0;
}
#include
#include
#include
#include
#include
#include
#include
时间限制: 1.0s 内存限制: 256.0MB 本题总分:25 分
给定一棵由 n n n 个结点组成的树以及 m m m 个不重复的无序数对 ( a 1 , b 1 ) , ( a 2 , b 2 ) , … , ( a m , b m ) (a_{1},b_{1}), (a_{2},b_{2}),\dots, (a_{m},b_{m}) (a1,b1),(a2,b2),…,(am,bm),其中 a i a_{i} ai 互不相同, b i b_{i} bi 互不相同, a i , b j ( 1 ≤ i , j ≤ m ) a_{i} , b_{j}(1\le i, j\le m) ai,bj(1≤i,j≤m)。
小明想知道是否能够选择一条树上的边砍断,使得对于每个 ( a i , b i ) (a_{i},b_{i}) (ai,bi) 满足 a i a_{i} ai 和 b i b_{i} bi 不连通,如果可以则输出应该断掉的边的编号(编号按输入顺序从 1 1 1 开始),否则输出 − 1 -1 −1。
输入共 n + m n+ m n+m 行,第一行为两个正整数 n , m n,m n,m。
后面 n − 1 n−1 n−1 行,每行两个正整数 x i , y i x_{i},y_{i} xi,yi 表示第 i i i 条边的两个端点。
后面 m m m 行,每行两个正整数 a i , b i a_{i} , b_{i} ai,bi。
一行一个整数,表示答案,如有多个答案,输出编号最大的一个。
6 2
1 2
2 3
4 3
2 5
6 5
3 6
4 5
4
断开第 2 2 2 条边后形成两个连通块: { 3 , 4 } , { 1 , 2 , 5 , 6 } \left\{3,4 \right\},\left\{1,2,5,6\right\} {3,4},{1,2,5,6},满足 3 3 3 和 6 6 6 不连通, 4 4 4 和 5 5 5 不连通。
断开第 4 4 4 条边后形成两个连通块: { 1 , 2 , 3 , 4 } , { 5 , 6 } \left\{1,2,3,4\right\},\left\{5,6\right\} {1,2,3,4},{5,6},同样满足 3 3 3 和 6 6 6 不连通, 4 4 4 和 5 5 5 不连通。
4 4 4 编号更大,因此答案为 4 4 4。
对于 30 30 30% 的数据,保证 1 < n ≤ 1000 1 < n \le 1000 1<n≤1000。
对于 100 100 100% 的数据,保证 $1 < n \le 10^{5},1 ≤ m \le \frac{n}{2} $。
当时写这个的时候一点都不会,没啥水平,只会骗分。ok,然后说说如何思考本题。
思考一下,每两个结点之间都经过那一条边,那个边假如被经过了 m m m 次,那砍掉就能使得不连通,输出众多边的最小编号即可,反之无解。这就有思路了。
当然我们不可能一个一个边往上找,要用差分的方法,在最近公共祖先那个边 − 2 -2 −2,两个结点 + 1 +1 +1,这样即完成了差分操作,最后累计求和即可。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include