A - meeting (树的直径)
题意:
链接:https://ac.nowcoder.com/acm/contest/884/A
给你一颗由 n 个点构成的树,和 k 个不同的点,让你找一点,使得该点到那 k 个点的最大距离最小,输出最小距离。
解题思路:
两次dfs求出树的直径(注:全都要是那 K 个点的,其他点不算,就是求树的直径的起点和终点都必须是这k个点里的点,因为 题意说的),然后最直径除以 2 向上取整就是答案。为什么呢?其实很好理解,就把他理解为中位数就可以,可以把树的直径看作是一个坐标轴,而且直径的长度就是坐标轴的大小,选取坐标轴的中心,那么其他的 k 里的点到坐标轴中心的点肯定在坐标轴范围内,否者该直径肯定是小于真正的直径,因为如果由其他的 k 里的点到坐标轴中心超过了左边或右边,那么就可以用这条边来替换原来的坐标轴中的左边或右边了。
AC代码:
#include<bits/stdc++.h>
#define up(i, x, y) for(int i = x; i <= y; i++)
#define down(i, x, y) for(int i = x; i >= y; i--)
#define bug printf("*********\n")
#define debug(x) cout<<#x"=["<<x<<"]" <<endl
#define IO ios::sync_with_stdio(false),cin.tie(0)
typedef long long ll;
typedef unsigned long long ull;
const double eps = 1e-8;
const int mod = 1e9 + 7;
const int maxn = 1e5 + 7;
const double pi = acos(-1);
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3fLL;
using namespace std;
vector<int> vec[maxn];
int dis[maxn];
int vis[maxn];
int point[maxn];
int dis_max = 0;
int t_max;
void dfs(int now, int step)
{
if(point[now] && step > dis_max)
{
dis_max = step; // 取最大距离
t_max = now; //保留终点
}
for(int i = 0; i < vec[now].size(); i++)
{
int t = vec[now][i];
if(vis[t] == 0)
{
vis[t] = 1;
dfs(t, step + 1);
}
}
}
int main()
{
int n, k; scanf("%d %d", &n, &k);
for(int i = 1; i <= n - 1; i++)
{
int x, y; scanf("%d %d", &x, &y);
vec[x].push_back(y);
vec[y].push_back(x);
}
int s = 1;
for(int i = 1; i <= k; i++)
{
int t; scanf("%d", &t);
point[t] = 1;
s = t;
}
memset(vis, 0, sizeof(vis)); vis[s] = 1;
dfs(s, 0);
memset(vis, 0, sizeof(vis));vis[t_max] = 1;
dfs(t_max, 0);
int ans = (dis_max + 1) / 2 ; //向上取整
cout << ans <<'\n';
}
C - sequence (单调栈+线段树/ST表)
题意:
链接:https://ac.nowcoder.com/acm/contest/884/C
给你两个序列,a1 ... an 和 b1 ... bn , 让你求 。
范围:
解题思路:
就是从1 - n枚举每一个值,让其作为最小值,用单调栈可以O(n)求出每个最小值的左右范围即 L[i], R[i] (小白书有模板),就是再 L[i] - R[i] 这个范围内 最小值是 a[i] ,再往左或往右一点点最小值就不是 a[i] 了, 这样固定好 a[i] ,然后接下来就是求在 L[i] - R[i]区间上 并且夸过 i 这个点的 b的和,如果a[i] > 0 就求最最大的 b 的和,反之亦然。
形式化表示就是: , 反之亦然。
那么怎么求是问题的关键,可以预处理出一个 b 序列的前缀和,然后 在 (L[i], i - 1) 区间上取前缀和的最小值,在(i, R[i]) 区间取前缀和的最大值,那么让最大值减去最小值就是最大值了, 反之亦然。 求区间最大值或最小值可以用线段树维护,也可以用ST表。AC代码使用的是线段树。
AC代码:
#include<bits/stdc++.h>
#define up(i, x, y) for(ll i = x; i <= y; i++)
#define down(i, x, y) for(ll i = x; i >= y; i--)
#define bug printf("*********\n")
#define debug(x) cout<<#x"=["<<x<<"]" <<endl
#define IO ios::sync_with_stdio(false),cin.tie(0)
typedef long long ll;
typedef unsigned long long ull;
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll maxn = 3e6 + 7;
const double pi = acos(-1);
const ll inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3fLL;
using namespace std;
ll st[maxn]; // 栈
ll L[maxn], R[maxn];
ll a[maxn], b[maxn];
ll sum[maxn];
struct Node{ll l, r, w;} t[maxn * 4], t2[maxn * 4];
void build(ll k, ll l, ll r)
{
t[k].l = l, t[k].r = r;
if(l == r)
{
t[k].w = sum[l];
return ;
}
ll mid = (l + r) / 2;
build(k << 1, l, mid);
build(k << 1 | 1, mid + 1, r);
t[k].w = max(t[k << 1].w, t[k << 1 | 1].w);
}
void query(ll k, ll l, ll r, ll &x)
{
if(l <= t[k].l && t[k].r <= r)
{
x = max(x, t[k].w);
return ;
}
ll mid = (t[k].l + t[k].r) >> 1;
if(l <= mid) query(k << 1, l, r, x);
if(mid + 1 <= r) query(k << 1 | 1, l, r, x);
}
void build2(ll k, ll l, ll r)
{
t2[k].l = l, t2[k].r = r;
if(l == r)
{
t2[k].w = sum[l];
return ;
}
ll mid = (l + r) >> 1;
build2(k << 1, l, mid);
build2(k << 1 | 1, mid + 1, r);
t2[k].w = min(t2[k << 1].w, t2[k << 1 | 1].w);
}
void query2(ll k, ll l, ll r, ll &x)
{
if(l <= t2[k].l && t2[k].r <= r)
{
x = min(x, t2[k].w);
return ;
}
ll mid = (t2[k].l + t2[k].r) >> 1;
if(l <= mid) query2(k << 1, l, r, x);
if(mid + 1 <= r) query2(k << 1 | 1, l, r, x);
}
int main()
{
ll n; scanf("%lld", &n);
for(ll i = 1; i <= n; i++) scanf("%lld", &a[i]);
for(ll i = 1; i <= n; i++) {scanf("%lld", &b[i]); sum[i] = sum[i - 1] + b[i];}
build(1, 1, n);
build2(1, 1, n);
/******** 单调栈模板 ************/
ll s = 0, t = 0;
for(ll i = 1; i <= n; i++)
{
while(t > s && a[ st[t - 1] ] >= a[i]) t--;
L[i] = t == 0 ? 1 : st[t - 1] + 1;
st[t++] = i;
}
s = t = 0;
for(ll i = n; i >= 1; i--)
{
while(t > s && a[st[t - 1]] >= a[i]) t--;
R[i] = t == 0 ? n : st[t - 1] - 1;
st[t++] = i;
}
/********************************/
ll ans = -1e18;
for(ll i = 1; i <= n; i++)
{
ll l = L[i], r = R[i];
// debug(l);
// debug(r);
if(a[i] > 0)
{
ll min_ = sum[l - 1], max_ = sum[i]; //query max 初值的选者需要注意一下
if(l == i) //特殊情况
{
query(1, l, r, max_);
ans = max((max_ - sum[l - 1]) * a[i], ans);
}
else
{
query(1, i, r, max_);
query2(1, l ,i - 1, min_);
ans = max(ans, (max_ - min_) * a[i]);
}
}
else{
ll min_ = sum[i], max_ = sum[l - 1]; //query max
if(l == i)
{
query2(1, l, r, min_);
ans = max((min_ - sum[l - 1]) * a[i], ans);
}
else
{
query2(1, i ,r, min_);
query(1, l, i - 1, max_);
ans = max(ans, (min_ - max_) * a[i]);
}
}
}
cout<<ans<<'\n';
}
J-free (迪杰斯特拉)
题意:
链接:https://ac.nowcoder.com/acm/contest/884/J
给你 n个点,m条边(1-10^3 有自环和重边)给你起点和终点,还有 K 次特权,每使用一次特权就可以把一条边的权值变为 0 ,最多使用K次特权,问从 s 出发 到 t 最少花费。
解题思路:
可以用多层图的思想,但是这个题目比较简单没那么麻烦直接用堆优化的迪杰斯特拉就足够了,无非是更新点递推的时候多一种递推而已,就是这条边不使用来更新结果,看代码就知道了,很明显。
AC代码:
#include<bits/stdc++.h>
#define up(i, x, y) for(ll i = x; i <= y; i++)
#define down(i, x, y) for(ll i = x; i >= y; i--)
#define bug prllf("*********\n")
#define debug(x) cout<<#x"=["<<x<<"]" <<endl
#define IO ios::sync_with_stdio(false),cin.tie(0)
typedef long long ll;
typedef unsigned long long ull;
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll maxn = 1e3 + 7;
const double pi = acos(-1);
const ll inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3fLL;
using namespace std;
typedef pair<ll, ll> P;
ll n, m, S, T, K;
struct node
{
ll to, w, time;
node() {};
node(ll a, ll b, ll c) : to(a), w(b), time(c) {};
bool operator < (const node &a) const
{
return w > a.w;
}
};
priority_queue<node> que;
vector<node> vec[maxn];
ll dis[maxn][maxn];
void djk(ll s)
{
for(ll i = 0; i <= n; i++)
{
for(int j = 0; j < 30; j++)
{
dis[i][j] = inf;
}
}
dis[s][0] = 0;
que.push(node(s, 0, 0));
while(!que.empty())
{
node v = que.top(); que.pop();
if(dis[v.to][v.time] != v.w) continue;
for(ll i = 0; i < vec[v.to].size(); i++)
{
node t = vec[v.to][i];
if( v.time < K && dis[v.to][v.time] < dis[t.to][v.time +1] ) // 使用特权往后递推
{
dis[t.to][v.time + 1] = dis[v.to][v.time];
que.push(node(t.to, dis[t.to][v.time + 1], v.time + 1));
}
if( dis[v.to][v.time] + t.w < dis[t.to][v.time] ) //不使用特权往后推
{
dis[t.to][v.time] = dis[v.to][v.time] + t.w;
que.push(node(t.to, dis[t.to][v.time], v.time));
}
}
}
}
int main()
{
scanf("%lld %lld %lld %lld %lld", &n, &m, &S, &T, &K);
for(ll i = 1; i <= m; i++)
{
ll x, y, w; scanf("%lld %lld %lld", &x, &y, &w);
// if(x == y) continue;
vec[x].push_back(node(y, w, 0));
vec[y].push_back(node(x, w, 0));
}
djk(S);
ll ans = inf;
for(ll i = 0; i <= K; i++)
{
ans = min(ans, dis[T][i]);
}
cout << ans << '\n';
}
K-number(思维 || DP)
题意:
给你一个串('0'-'9' )让你求他共有多少字串是300的倍数。1 <= |s| <= 10^5
解题思路:
先预处理这个串,算一个前缀和,然后对前缀和中每一位都取模 3 ,然后你会发现,如果 i 点的前缀和是 x 那么 i 点之前的 j 点前缀和也是 x ,那么在这个【j + 1, i】区间内一定是三的倍数。很简单,因为你用 sum[i] - sum[j] = 0 ,说明区间 [j + 1, i] 中间和可以被三整除。所以o(n)可以解决。
AC代码:
#include<bits/stdc++.h>
#define up(i, x, y) for(ll i = x; i <= y; i++)
#define down(i, x, y) for(ll i = x; i >= y; i--)
#define bug printf("*********\n")
#define debug(x) cout<<#x"=["<<x<<"]" <<endl
#define IO ios::sync_with_stdio(false),cin.tie(0)
typedef long long ll;
typedef unsigned long long ull;
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll maxn = 3e6 + 7;
const double pi = acos(-1);
const ll inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3fLL;
using namespace std;
char s[maxn];
int sum[maxn];
int mp[maxn];
int ans;
int main()
{
scanf("%s", s + 1);
int len = strlen(s + 1);
for(int i = 1; i <= len; i++)
sum[i] = (sum[i - 1] + s[i] - '0') % 3;
for(int i = 1; i <= len; i++)
{
mp[sum[i]]++; // ① 位置
if(s[i] == '0')
{
ans ++; // 因为一个单独的0也算答案
if(s[i - 1] == '0')
{
ans += mp[ sum[i] ] - 1; // 减去自己在 ① 位置加的1
}
}
}
cout<<ans<<'\n';
}