第 3 场 小白入门赛
1、厉不厉害你坤哥(暴力)
2、思维
3、暴力,前缀和,贪心
4、二分
5、DP
6、容斥,双指针
第 3 场 强者挑战赛
2、BFS
5、树上倍增求第k祖先
1. 召唤神坤
题意:
可以发现,如果我们钦定练习生,那么舞力值的。
因此对于而言,需要知道前的最大值跟后的最大值。可以通过数组来存前缀最大跟后缀最大。然后遍历每个即可。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define pb push_back
#define x first
#define y second
#define endl '\n'
const LL maxn = 4e05+7;
const LL N = 5e05+10;
const LL mod = 1e09+7;
const int inf = 0x3f3f3f3f;
const LL llinf = 5e18;
typedef pair<int,int>pl;
priority_queue<LL , vector<LL>, greater<LL> >mi;//小根堆
priority_queue<LL> ma;//大根堆
LL gcd(LL a, LL b){
return b > 0 ? gcd(b , a % b) : a;
}
LL lcm(LL a , LL b){
return a / gcd(a , b) * b;
}
int n , m;
vector<int>a(N , 0);
void init(int n){
for(int i = 0 ; i <= n ; i ++){
a[i] = 0;
}
}
void solve()
{
cin >> n;
int l[n + 5] ,r[n + 5];
memset(l , 0 , sizeof l);
memset(r , 0 , sizeof r);
for(int i = 1 ; i <= n ; i ++){
cin >> a[i];
}
for(int i = 1 ; i <= n ; i++){
l[i] = max(l[i - 1] , a[i]);
}
for(int i = n ; i >= 1 ; i --){
r[i] = max(r[i + 1] , a[i]);
}
int ans = 0;
for(int i = 2 ; i < n ; i ++){
ans = max(ans , (l[i - 1] + r[i + 1]) / a[i]);
}
cout << ans;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cout.precision(10);
int t=1;
// cin>>t;
while(t--)
{
solve();
}
return 0;
}
2. 聪明的交换策略
题意:
思路: 观察题意可以得出,最终的序列为连续的0 + 连续的1或者连续的1 + 连续的0。分别计算出两种情况所需要的交换数即可。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define pb push_back
#define x first
#define y second
#define endl '\n'
const LL maxn = 4e05+7;
const LL N = 5e05+10;
const LL mod = 1e09+7;
const int inf = 0x3f3f3f3f;
const LL llinf = 5e18;
typedef pair<int,int>pl;
priority_queue<LL , vector<LL>, greater<LL> >mi;//小根堆
priority_queue<LL> ma;//大根堆
LL gcd(LL a, LL b){
return b > 0 ? gcd(b , a % b) : a;
}
LL lcm(LL a , LL b){
return a / gcd(a , b) * b;
}
int n , m;
vector<int>a(N , 0);
void init(int n){
for(int i = 0 ; i <= n ; i ++){
a[i] = 0;
}
}
void solve()
{
cin >> n;
string s;
cin >> s;
for(int i = 0 ; i < n ; i ++){
a[i] = s[i] - '0';
}
int id = 0;
int ans = 0;
for(int i = 0 ; i < n ; i ++){
if(a[i] == 1){
ans += i - id;
id++;
}
}
id = n - 1;
int ans1 = 0;
for(int i = n - 1 ; i >= 0 ; i--){
if(a[i] == 1){
ans1 += id - i;
id--;
}
}
cout << min(ans , ans1);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cout.precision(10);
int t=1;
// cin>>t;
while(t--)
{
solve();
}
return 0;
}
3. 怪兽突击
题意:
思路:好像跟cf上有道题差不多思路,假设当前将前个怪兽全部击败一次,那么最终消耗的体力应当再加上。从前往后遍历所有的 ,然后取最小值即可。可以在遍历的时候顺便更新了。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define pb push_back
#define x first
#define y second
#define endl '\n'
#define int long long
const LL maxn = 4e05+7;
const LL N = 5e05+10;
const LL mod = 1e09+7;
const int inf = 0x3f3f3f3f;
const LL llinf = 5e18;
typedef pair<int,int>pl;
priority_queue<LL , vector<LL>, greater<LL> >mi;//小根堆
priority_queue<LL> ma;//大根堆
LL gcd(LL a, LL b){
return b > 0 ? gcd(b , a % b) : a;
}
LL lcm(LL a , LL b){
return a / gcd(a , b) * b;
}
int n , m;
vector<int>a(N , 0);
void init(int n){
for(int i = 0 ; i <= n ; i ++){
a[i] = 0;
}
}
void solve()
{
cin >> n >> m;
vector<int>b(N , 0);
vector<int>sum(N, 0);
for(int i = 1 ; i <= n ; i ++) {
cin >> a[i];
sum[i] = sum[i - 1] + a[i];
}
for(int i = 1 ; i <= n ; i ++){
cin >> b[i];
}
int mi = 1e9;
int ans = 1e18;
for(int i = 1 ; i <= n ; i ++){
mi = min(mi , a[i] + b[i]);
int tmp = sum[i] + max(m - i , 0 * 1LL) * mi;
ans = min(tmp , ans);
}
cout << ans;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cout.precision(10);
int t=1;
// cin>>t;
while(t--)
{
solve();
}
return 0;
}
4. 蓝桥快打
题意:
思路:首先求出小蓝能够进攻多少次,然后再二分攻击力看能否击败小桥。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define pb push_back
#define x first
#define y second
#define endl '\n'
const LL maxn = 4e05+7;
const LL N = 5e05+10;
const LL mod = 1e09+7;
const int inf = 0x3f3f3f3f;
const LL llinf = 5e18;
#define int long long
typedef pair<int,int>pl;
priority_queue<LL , vector<LL>, greater<LL> >mi;//小根堆
priority_queue<LL> ma;//大根堆
LL gcd(LL a, LL b){
return b > 0 ? gcd(b , a % b) : a;
}
LL lcm(LL a , LL b){
return a / gcd(a , b) * b;
}
int n , m;
vector<int>a(N , 0);
void init(int n){
for(int i = 0 ; i <= n ; i ++){
a[i] = 0;
}
}
void solve()
{
int a , b , c;
cin >> a >> b >> c;
int t = (a - 1) / c + 1;
auto check = [&](int x){
if(x * t >= b){
return true;
}
else{
return false;
}
};
int l = 1 , r = 1e9;
while(l < r){
int mid = (l + r) / 2;
if(check(mid)){
r = mid;
}
else{
l = mid + 1;
}
}
cout << l <<endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cout.precision(10);
int t=1;
cin>>t;
while(t--)
{
solve();
}
return 0;
}
5. 奇怪的段
题意:
思路:观察到数据较小,可以考虑DP解决问题。 定义表示前个数,分成段的最大权值和,于是发现第个数必然放在第段中。于是有状态转移方程:。最后输出即可,这道题就愉快的出来了。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define pb push_back
#define x first
#define y second
#define endl '\n'
const LL maxn = 4e05+7;
const LL N = 5e05+10;
const LL mod = 1e09+7;
const int inf = 0x3f3f3f3f;
const LL llinf = 5e18;
#define int long long
typedef pair<int,int>pl;
priority_queue<LL , vector<LL>, greater<LL> >mi;//小根堆
priority_queue<LL> ma;//大根堆
LL gcd(LL a, LL b){
return b > 0 ? gcd(b , a % b) : a;
}
LL lcm(LL a , LL b){
return a / gcd(a , b) * b;
}
int n , m;
vector<int>a(N , 0);
void init(int n){
for(int i = 0 ; i <= n ; i ++){
a[i] = 0;
}
}
void solve()
{
cin >> n >> m;
int dp[n + 5][m + 5];
int p[m + 5];
memset(dp , -0x3f , sizeof dp);
dp[0][0] = 0;
for(int i = 1 ; i <= n ; i ++){
cin >> a[i];
}
for(int i = 1 ;i <= m ; i ++){
cin >> p[i];
}
for(int i = 1 ; i <= n ; i ++){
for(int j = 1 ; j <= m ; j ++){
dp[i][j] = max(dp[i - 1][j] , dp[i - 1][j - 1]) + a[i] * p[j];
}
}
cout << dp[n][m];
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cout.precision(10);
int t=1;
while(t--)
{
solve();
}
return 0;
}
6. 小蓝的反击
题意:
思路:首先只考虑A的倍数的情况,可以发现:若区间范围内的乘积是A的倍数,那么也必然是A的倍数,以此类推....也就是说,若钦定区间左端点 ,可以找到最小的,使得是A的倍数,那么左端点是,满足题意的区间共有个。很容易想到:随着的增大也必然是越来越大的,因此用双指针来代表每个区间左端点 以及所对应的最小的。将所有都遍历完就是最终的满足A的倍数的区间总数。
假设P(A)代表了A的倍数的区间总数。根据容斥原理,,也就是 , 因此分别把两个P求出来相减即可。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define pb push_back
#define x first
#define y second
#define endl '\n'
#define int long long
const LL maxn = 4e05+7;
const LL N = 1e06+10;
const LL mod = 1e09+7;
const int inf = 0x3f3f3f3f;
const LL llinf = 5e18;
typedef pair<int,int>pl;
priority_queue<LL , vector<LL>, greater<LL> >mi;//小根堆
priority_queue<LL> ma;//大根堆
LL gcd(LL a, LL b){
return b > 0 ? gcd(b , a % b) : a;
}
LL lcm(LL a , LL b){
return a / gcd(a , b) * b;
}
int n , m;
vector<int>num(N , 0);
vector<LL>prime;//存储素数
bool vis[N+5];
void su()
{
for(int i=2;i<=N;i++)
{
if(!vis[i])
prime.pb(i);
for(int j=0;j < (int)prime.size() && prime[j] * i <= N;j ++)
{
vis[prime[j]*i]=1;
if(i % prime[j]==0)
break;
}
}
}
set<int>st;
vector<int>st_num;
int len;
int ans(int x){
int maxx[len];
int cnt[len];
memset(cnt , 0 , sizeof cnt);
memset(maxx , 0 , sizeof maxx);
for(int i = 0 ; i < len ; i++){
while(x % st_num[i] == 0){
maxx[i]++;
x /= st_num[i];
}
}
int out = 0;
int f = 1;
for(int i = 0 , j = 0 ; i < n ; i ++){
if(j <= i){
memset(cnt , 0 , sizeof cnt);
j = i;
int tmp = num[j];
for(int t = 0 ; t < len ; t ++){
while(tmp % st_num[t] == 0){
cnt[t]++;
tmp /= st_num[t];
}
}
j++;
f = 1;
}
for(int t = 0 ; t < len ; t ++){
if(cnt[t] < maxx[t]){
f = 0;
}
}
while(j < n && !f){
int tmp = num[j];
for(int t = 0 ; t < len ; t ++){
while(tmp % st_num[t] == 0){
cnt[t]++;
tmp /= st_num[t];
}
}
f = 1;
for(int t = 0 ; t < len ; t ++){
if(cnt[t] < maxx[t]){
f = 0;
break;
}
}
j++;
}
if(f){
out += (n - j + 1);
}
int tmp = num[i];
for(int t = 0 ; t < len ; t ++){
while(tmp % st_num[t] == 0){
cnt[t]--;
tmp /= st_num[t];
}
}
}
return out;
}
void solve()
{
int a , b;
cin >> n >> a >> b;
int A = a , B = b;
for(int i = 0 ; i < n ; i ++){
cin >> num[i];
}
int t = prime.size();
for(int i = 0 ; i < t ; i ++){
while(a % prime[i] == 0){
st.insert(prime[i]);
a /= prime[i];
}
}
if(a > 1){
st.insert(a);
}
for(int i = 0 ; i < t ; i ++){
while(b % prime[i] == 0){
st.insert(prime[i]);
b /= prime[i];
}
}
if(b > 1){
st.insert(b);
}
for(auto it : st){
st_num.pb(it);
}
len = st_num.size();
cout << ans(A) - ans(A * B / gcd(A , B))<< endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cout.precision(10);
su();
int t=1;
//cin>>t;
while(t--)
{
solve();
}
return 0;
}
2. 暖气冰场
题意:
思路:最短路/BFS,把所有格子第一次被暖气覆盖的时间求出来,然后求出所有时间的最大值即可。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define pb push_back
#define x first
#define y second
#define endl '\n'
const LL maxn = 4e05+7;
const LL N = 5e05+10;
const LL mod = 1e09+7;
const int inf = 0x3f3f3f3f;
const LL llinf = 5e18;
#define int long long
typedef pair<int,int>pl;
priority_queue<LL , vector<LL>, greater<LL> >mi;//小根堆
priority_queue<LL> ma;//大根堆
LL gcd(LL a, LL b){
return b > 0 ? gcd(b , a % b) : a;
}
LL lcm(LL a , LL b){
return a / gcd(a , b) * b;
}
int n , m;
vector<int>a(N , 0);
void init(int n){
for(int i = 0 ; i <= n ; i ++){
a[i] = 0;
}
}
int vis[1010][1010];
int tx[8] = {-1 , 1 , -1 , 1 , 1 , -1 , 0 , 0};
int ty[8] = {-1 , 1 , 1 , -1 , 0 , 0 , 1 , -1};
void solve()
{
cin >> n >> m;
int t;
cin >> t;
int mp[n + 5][m + 5];
memset(mp , 0x3f , sizeof mp);
queue<pair<int,int>>q;
for(int i = 0 ; i < t ; i ++){
int x , y;
cin >> x >> y;
mp[x][y] = 0;
vis[x][y] = 1;
q.push({x , y});
}
auto check = [&](int x , int y){
return x >=1 && x <= n && y >= 1 && y <= m && vis[x][y] == 0;
};
int ans = 0;
while(!q.empty()){
auto it = q.front();
q.pop();
int x = it.first;
int y = it.second;
for(int i = 0 ; i < 8 ; i ++){
int nx = x + tx[i];
int ny = y + ty[i];
if(check(nx , ny)){
mp[nx][ny] = mp[x][y] + 1;
vis[nx][ny] = 1;
q.push({nx , ny});
}
}
}
for(int i = 1 ; i <= n ; i ++){
for(int j = 1 ;j <= m ; j++){
ans = max(ans , mp[i][j]);
}
}
cout << ans;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cout.precision(10);
int t=1;
while(t--)
{
solve();
}
return 0;
}
5. 逃跑
题意:
思路:首先考虑小蓝位于节点之上,且他只会往深度更深的地方走,那么最终小蓝能够坚持的时间为节点所构成的子树的最大深度。假设这个最大深度为,他是可以通过一遍DFS求出来的。然后发现有。因此对于一个起点而言,小蓝需要先尽可能的往上走,因为越往上,其节点的就越大。所以只需要考虑往上最多能走到哪个祖先即可。然后得到即是起点所能坚持的最长时间。
参考倍增法求LCA的过程,求出每个节点的祖先情况即可。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define pb push_back
#define x first
#define y second
#define endl '\n'
#define int long long
const LL maxn = 4e05+7;
const LL N = 1e06+10;
const LL mod = 998244353;
const int inf = 0x3f3f3f3f;
const LL llinf = 5e18;
typedef pair<int,int>pl;
priority_queue<LL , vector<LL>, greater<LL> >mi;//小根堆
priority_queue<LL> ma;//大根堆
LL gcd(LL a, LL b){
return b > 0 ? gcd(b , a % b) : a;
}
LL lcm(LL a , LL b){
return a / gcd(a , b) * b;
}
int n , m;
vector<int>a(N , 0);
void init(int n){
for(int i = 0 ; i <= n ; i ++){
a[i] = 0;
}
}
int dp[N][30];
struct HLD {//轻重链剖分
int n;
std::vector<int> siz, top, dep, parent, in, out, seq , max_dep;//子树大小 所在重链的顶部节点 深度 父亲 子树DFS序的起点 子树DFS序的终点
std::vector<std::vector<int>> adj;
int cur = 1;
HLD() {}
HLD(int n) {
init(n);
}
void init(int n) {
this->n = n;
siz.resize(n);
top.resize(n);
dep.resize(n);
parent.resize(n);
in.resize(n);
out.resize(n);
seq.resize(n);
max_dep.resize(n);
cur = 0;
adj.assign(n, {});
}
void addEdge(int u, int v) {
adj[u].push_back(v);
adj[v].push_back(u);
}
void work(int root = 1) {
top[root] = root;
dep[root] = 0;
parent[root] = -1;
dfs1(root);
dfs2(root);
}
void dfs1(int u) {
if (parent[u] != -1) {
adj[u].erase(std::find(adj[u].begin(), adj[u].end(), parent[u]));
}
siz[u] = 1;
for (auto &v : adj[u]) {
parent[v] = u;
dep[v] = dep[u] + 1;
dfs1(v);
siz[u] += siz[v];
if (siz[v] > siz[adj[u][0]]) {
std::swap(v, adj[u][0]);
}
}
}
void dfs2(int u) {
in[u] = ++cur;
max_dep[u] = dep[u];
seq[in[u]] = u;
for (auto v : adj[u]) {
top[v] = v == adj[u][0] ? top[u] : v;
dfs2(v);
max_dep[u] = max(max_dep[u] , max_dep[v]);
}
out[u] = cur;
}
int lca(int u, int v) {
while (top[u] != top[v]) {
if (dep[top[u]] > dep[top[v]]) {
u = parent[top[u]];
} else {
v = parent[top[v]];
}
}
return dep[u] < dep[v] ? u : v;
}
void dfs3(){
for(int i = 1 ; i <= n - 5 ; i ++){
dp[i][0] = parent[i];
}
for(int i = 1 ; i <= n - 5; i ++){
for(int j = 1 ; j < 30 ; j ++){
if(dp[i][j - 1] < 1){
continue;
}
dp[i][j] = dp[dp[i][j - 1]][j - 1];
}
}
}
int find_an(int x , int y){
for(int i = 20 ; i >= 0 ; i--){
int t = 1 << i;
if(y >= t){
x = dp[x][i];
y -= t;
}
}
return x;
}
int dist(int u, int v) {
return dep[u] + dep[v] - 2 * dep[lca(u, v)];
}
int jump(int u, int k) {
if (dep[u] < k) {
return -1;
}
int d = dep[u] - k;
while (dep[top[u]] > d) {
u = parent[top[u]];
}
return seq[in[u] - dep[u] + d];
}
bool isAncester(int u, int v) {//是否为祖先
return in[u] <= in[v] && in[v] < out[u];
}
int rootedParent(int u, int v) {
std::swap(u , v);
if (u == v) {
return u;
}
if (!isAncester(u, v)) {
return parent[u];
}
auto it = std::upper_bound(adj[u].begin(), adj[u].end(), v, [&](int x, int y) {
return in[x] < in[y];
}) - 1;
return *it;
}
int rootedSize(int u, int v) {
if (u == v) {
return n;
}
if (!isAncester(v, u)) {
return siz[v];
}
return n - siz[rootedParent(u, v)];
}
int rootedLca(int a, int b, int c) {
return lca(a, b) ^ lca(b, c) ^ lca(c, a);
}
}hld;
LL qpow(LL a , LL b)//快速幂
{
LL sum=1;
while(b){
if(b&1){
sum=sum*a%mod;
}
a=a*a%mod;
b>>=1;
}
return sum;
}
void solve()
{
cin >> n;
hld.init(n + 5);
memset(dp , 0 ,sizeof dp);
for(int i = 1 ; i < n ; i ++){
int u , v;
cin >> u >> v;
hld.addEdge(u , v);
}
hld.work();
hld.dfs3();
int ans = 0;
for(int i = 2 ; i <= n ; i ++){
int t = hld.dep[i];
if(t <= 2){
ans += hld.max_dep[i];
}
else{
int to = (t - 1) / 2;
int an = hld.find_an(i , to);
ans += hld.max_dep[an];
}
}
ans %= mod;
cout << (ans * qpow(n , mod - 2)) % mod<< endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cout.precision(10);
int t=1;
//cin>>t;
while(t--)
{
solve();
}
return 0;
}