The 2024 ICPC Asia Hangzhou Regional Contest (2024杭州区域赛AKHEM)

2024ICPC杭州--E《Elevator II》——找通解+贪心 显然,按照r从大到小排序之后,第一个的sum肯定是0,由于还要求输出路径,所以在计算sum的时候还要记录其前往无伤点的下一条路是谁,显然包含无伤点的那条路上一条路不存在,我们可以初始化为-1。如果上一段自己的l不如其okl左,那我连上一段l都无伤【这一段的终点在上一段起点的右边,下行不耗电】,显然上一段的okl也无伤【上一段的okl比起点还左,那更没问题了】接下来我们要做的就是找起点,规划前往无伤点这一段怎么走,显然,对于某一段【设为第k段】,额外的花费就是起点f到第k段的左端点耗电+sum[k] 阅读详情
AI摘要

本文分析了2024 ICPC亚洲区域赛中的多道算法题目,涵盖并查集、贪心策略、重链剖分及笛卡尔树等技术。重点讲解如何利用数据结构解决特定问题,并提供了多种实现方式及其时间复杂度。

A. AUS

分析:签到,很显然的并查集。

每个字母代表一个节点,s1和s2每个相同位置的对应两个字母代表一条边,在一个连通块中的字母一定是相同的映射值的。

我们想要s3和s1不相等,就要找到二者相同位置的字母不在同一连通块中(注意只要存在即可直接return)。

时间复杂度:$O(t \cdot (|s_1| + |s_3|))$

#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
#define ll long long

const int N = 27;
int p[N];

int find(int x)
{
    if(x == p[x] )
        return x;
    return p[x] = find(p[x]);
}

void merge(int x ,int y ){
    int rtx = find(x), rty = find(y);
    if(rtx != rty){
        p[rty] = rtx;
    }
}
void init(){
    for (int i = 0; i< N ; i++)
        p[i] = i;
}
void solve(){
    init();
    string s1 , s2 , s3;
    cin >> s1 >> s2 >> s3;
    if(s1.size() != s2.size()){
        cout << "NO" << endl;
        return;
    }
    if(s1.size() == s2.size() && s1.size() != s3.size()){
        cout << "YES" << endl;
        return;
    }

    int n = 26;
    vector<set<int>> adj(n + 1);
    for(int i = 0 ; i < s1.size(); i ++){
        int a = s1[i] - 'a', b = s2[i] - 'a';
        merge(a, b);
    }
    // ab  bc  cd
    for (int i = 0; i < s3.size() ; i ++){
        // 检查是不是存在不在一个集合里面的
        int a = s1[i] - 'a', b = s3[i] - 'a';
        // 如果两者都不在同一个集合里面就输出
        if(find(a) != find(b)){
            cout << "YES" << endl;
            return;
        }
    }
    cout << "NO" << endl;
}

int main(){
	ios_base::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	int t;
	cin >>t;
	while(t--) solve();
	return 0;
}

K. Kind of Bingo

分析:观察可以发现如果在某个地方x的标记可以达到某行全部被标记,则假设x这个地方前面本身已经有y个是这一行中的,我们一定不会把它换到x右侧,我们只会把不在这一行中的数去和应该在这一行却在x右边的数去换,所以我们最多可以换k个数,如果 k >= m则直接全换到前面,标记到m就行了,思路就是这么直接,如果k<m,就看哪一行最先达到m - k个标记,再加上 max(k - not_in, 0),避免 x 实际小于m。

时间复杂度:$O(t \cdot n \cdot m)$

#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
#define ll long long
const int N = 100010;
int row[N];  // 记录某一行已经标记了多少个

void solve(){
    int n , m , k ;
    cin >> n >> m >> k ;
    vector<vector<int>> a(n + 1, vector<int>(m + 1));
    for (int i = 0; i < n; i ++)
        row[i] = 0;
    
    for (int i = 0; i < n; i ++){
        for (int j = 0; j < m; j++){
            cin >> a[i][j];
        }
    }
    if (m - k <= 0)
    {
        cout << m << endl;
        return;
    }
    //  还要记录不在的数量
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            int x = a[i][j];
            int x_row = (x - 1) / m; // 计算出行
            row[x_row]++;
            if (row[x_row] == m - k)
            {
                int sum = i * m + (j + 1);
                int not_in = sum - (m - k); 
                // 这个数量小于k就需要补充一些
                int ans = sum + max(k - not_in, 0);
                cout << ans << endl;
                return;
            }
        }
    }
}

int main(){
	ios_base::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	int t;
	cin >>t;
	while(t--) solve();
	return 0;
}

H. Heavy-light Decomposition

分析:思维构造。

最长链的起点我们一定是会作为根的,对于次长链,和短链,以及相同的链,有什么比较好的构造方案呢?通过思考可以发现如果 最长链+任意一个可以接在长链上的短链 > 其余的任何链的长度,不管最长链是不是唯一,这时其余的链只需要接在根上即可。

可是我们发现短链必须要 0 \le \text{len} < \text{mx} - 1 才能起加固长链的作用,假如 len = mx - 1 会发现这条链的尝试是 最长链 + 1 显然与最长链冲突。

case1:如果存在短链,则最长链+短链一定可以>其余任何链的长度,所以只需要把其余的链都连到根上即可。

case2:如果不存在短链,就代表我们加固不了长链,其余的链还是直接往根上连最优,这个时候发现,不能存在和最长链相同长度的链,否则该链连到根上长度会变成最长链 + 1,显然冲突。

本质上无解的情况就是:存在两个或以上最长链且不存在一个 len < 最长链-1的短链。

时间复杂度:$O(t \cdot (n + k \log k))$

#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
#define ll long long
#define PII pair<int , int> 
#define x first
#define y second

// 无解的情况::存在两个或以上最长链且不存在一个 len < 最长链-1的短链
// 其余所有的情况 都可以将满足 len < 最长链-1的短链的短链连到最长链的第二个点的上
// 保证了最长链+短链 > 其他所有链  其余所有链直接连接最长链的起点上即可】

void solve(){
	int n, k; 	
	cin >> n >> k;
	vector<int> fa(n + 1 , 0);  
	priority_queue<PII> q;  // 也可以数组直接排序,反正思路清楚一定不会超时的
	int mn = 1e9;
	for(int i = 0 ; i < k ; i ++){
		int l, r;
		cin >> l >> r;
		int len = r - l;
		q.push({len, l});
		mn = min(mn, len);
		for(int i = l + 1 ; i <= r ; i ++){
			fa[i] = i - 1;
		}
	}
	if(k == 1){
		for (int i = 1; i <= n; i ++ ){
			cout << fa[i] << (i == n ? endl : " ");
		}
		return;
	}
	int mx = q.top().x;   			// 最长链的长度
	if(mn >= 0 && mn < mx - 1 ){    // 存在短链则一定有解
		auto l_mx = q.top().y;
		q.pop();
		while(q.size()){
			auto [len, l] = q.top();
			q.pop();
			if(len == mn){  	  // 短链接到长链第二个点上
				fa[l] = l_mx + 1;
				continue;
			}
			fa[l] = l_mx;
		}
		for (int i = 1;i <= n ; i++){
			cout << fa[i] << (i == n ? endl : " ");
		}
		return;
	} 
	int l_mx = q.top().y;
	q.pop(); 					  // 不存在短链则不能存在两个或更多最长链
	if (q.top().x == mx)  
	{
		cout << "IMPOSSIBLE" << endl;
	}
	else
	{
		while(q.size()){
			auto l = q.top().y;
			q.pop();
			fa[l] = l_mx;
		}
		for (int i = 1; i <= n; i++)
		{
			cout << fa[i] << (i == n ? endl : " ");
		}
	}
}

int main(){
	ios_base::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	int t;
	cin >>t;
	while(t--) solve();
	return 0;
}

E. Elevator II

分析:思维贪心。

发现sumr - suml一定是一个基础的花费,现在我们想尽可能每次上升都做有用功,减少额外花销,发现如果最开始我们就在最高点,就可以贪心的选r大的进行运人,因为每次的额外花销只和上一次的r有关,上一次的r越大越好,r >= l_{now} 就没有额外花销,所以从最高点开始一定不会产生额外花销。

现在我们唯一的目标就是把走到最上面的这个过程额外花销最小化,需要采取一个贪心的策略。

如果当前的位置是 now ,下一个要送的人是 [l, r]。

Case1:如果l \le \text{now} \le r,则一定不会产生额外花销,还可以让电梯now往上走,所以我们先走这样的点。

Case2:如果没有这样的点,我们只能走 l \ge \text{now} \text{ and } r \ge \text{now} 的,本质上额外花销产生的原因就是某段区间没有被上升任务覆盖,为了使额外花销最少,我们要先走 l 最小的产生的额外花销是 l - now 。

这样子走一定可以把覆盖的都走过,其实就是一个简单的区间覆盖问题。

我们保证最小花销到达最高点之后,此后就不会再产生额外花销了,因为我们第二次贪心每次挑r大的走,则上一个r一定大于当前的l。

唯一需要求的就是求从f到最高点之间有多大的区间没有被覆盖,答案就是:

$\text{mn\_cost} = \text{cot\_cover} + \text{sumr} - \text{suml}$

时间复杂度:$O(\sum n \log n)$

#include <bits/stdc++.h>
using namespace std;
#define endl "\n"
#define ll long long
const int N = 100010;
struct node{
    int l, r, id;
} e[N];
void solve()
{
    ll n, f , cost = 0;
    cin >> n >> f;
    vector<int> vis( n + 1 ,  0);
    vector<ll> ans_id;
    for (ll i = 1; i <= n; i++)
    {
        cin >> e[i].l >> e[i].r;
        cost += e[i].r - e[i].l;
        e[i].id = i;
    }
    sort(e + 1, e + 1 + n , [&](node lx, node ly)
    {
        return lx.l < ly.l; 
    });
    ll now = f, not_cover = 0;                  // 寻找f到ed多长的区间没有被覆盖就是not_cover
    int cnt = 0;   
    for (int i = 1; i <= n ; i ++)
    {
        if(e[i].l <= now && e[i].r >= now){   // 这是肯定要选的覆盖的任务
            ans_id.push_back(e[i].id);
            now = e[i].r;
        }else if( e[i].l >= now && e[i].r >= now ){
            not_cover += e[i].l - now;       // 这个时候会产生额外花销,因为没有覆盖
            ans_id.push_back(e[i].id);
            now = e[i].r;
        }else{
            e[++cnt] = e[i]; //存还没有遍历到的
        }
    }
    sort(e + 1, e + 1 + cnt, [&](node lx, node ly)
         { return lx.r > ly.r; });
    // 答案就是cost + not_cover,现在只需要把没有完成的任务按照r排序即可
    for (int i = 1; i <= cnt; i ++){
        ans_id.push_back(e[i].id);
    }
    cout << cost + not_cover << endl;
    for (int i = 1; i <= n; i++){
        cout << ans_id[i - 1] << (i == n ? "\n" : " ");
    }
    return;
}

int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    ll t;
    cin >> t;
    while (t--)
        solve();
    return 0;
}

M. Make It Divisible

题意:

给定一个长度为 n 的序列 b 和一个整数 k。你需要找到所有在 1 到 k 范围内的整数 x,使得新序列 a(其中a_i = b_i + x)满足一个特殊性质,这个性质被称为“可除序列”。

一个序列是“可除序列”,指的是它的每一个子区间都满足:区间内存在一个“支配者”元素,它可以整除这个区间内的所有其他元素。

分析:数学,笛卡尔树。

不难推断,这个“支配者”必定是该区间内的最小值。

如果直接从 1 到 k 检查每个 x,当 k 很大时一定会超时。所以必须先缩小 x 的范围。

假设mn是数组中最小的数。

  • 根据取模的性质:

  • ((b_i+x) - (mn+x)) \equiv 0 \pmod{mn+x},即 (b_i - mn) \equiv 0 \pmod{mn+x}。

  • 这个条件必须对所有 i 都成立。因此,mn+x必须是所有 b_i - mn 的公约数。

  • 所以,mn + x 必然是 \gcd(b_1 - mn, b_2 - mn, \dots) 的一个因子。代码中的变量 g 就是这个最大公约数。

同样的性质可以推广到任意区间中,可以用相邻两个数的差分$b_i - b_{i-1}$的绝对值来求出公约数(和用最小值$b_i - mn$是一个道理)。

我们就把x的搜索范围从可能巨大的 k,缩小到了 g的所有因子。对于每个因子 d,一个候选的 x 就是 d - mn。

现在我们有了一小组候选的 x,但还需要验证它们是否真的满足条件。条件是“任意子区间的最小值都能整除该区间所有数”。

我们不可能真的去检查所有$O(n^2)$个子区间。这里用了一个非常聪明的方法:单调栈,这和构建笛卡尔树的思想完全一致。

因为对于笛卡尔树中的任意一个节点,它都是其整个子树(包括它自己)所对应原始区间的最小值。只要一个节点能整除它的所有“直接孩子”,它就必然能整除它的所有“子孙节点”!。

  • 这段代码为序列中的每一个元素 b_i,找到了一个最大的连续区间 [b_{i,l}, b_{i,r}],使得 b_i 是这个区间内的最小值。

  • 这实际上是在说,在笛卡尔树的结构中,以 b_i 为根节点的子树,对应的就是 [b_{i,l}, b_{i,r}]这个区间。

  • 现在,我们只需要验证一个简化后的条件:对于每个i,b_i + x是否能整除它所支配的区间 [b_{i,l}, b_{i,r}]内的所有 b_j + x。如果这个条件对所有 i 都成立,那么整个序列就是“可除序列”。

auto check = [&](ll add) -> bool
    {
        for (ll i = 1; i <= n; i++)
        {
            ll div = b[i].v + add;
            for (ll j = b[i].l; j <= b[i].r; j++)  // 这一层循环可以优化
            {
                if ((b[j].v + add) % div != 0)
                {
                    return false;
                }
            }
        }
        return true;
    };

这个 check 函数的复杂度是 $O(n^2)$ 的,但因为它只在 g 的因子(数量很少)上被调用,如果不放心的话可以用rmq快速求区间gcd,check的时间复杂度就是$O(n \log n)$。

下列代码中前两个提供另一个思路存单调区间的思路,比较了解笛卡尔树的可以直接看第代码三。

代码一(单调栈不加优化):

时间复杂度:$O(d(g) \cdot n^2)$

但是竟然没有超时,只能说当数据量很大时有效的因子实在是太少了。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define endl "\n"
struct node
{
    ll l, r, v = 0;
};

ll gcd(ll a, ll b)
{
    if (a == 0)
        return b;
    return gcd(b % a, a);
}

void solve()
{
    ll n, k;
    cin >> n >> k;
    vector<node> b(n + 2);
    ll mn = 1e9, mx = 0;
    for (ll i = 1; i <= n; i++)
    {
        cin >> b[i].v;
        mn = min(mn, b[i].v);
        mx = max(mx, b[i].v);
        b[i].l = i, b[i].r = i;
    }
    ll g = 0;
    // 生成所有的 mn
    if (mn == mx)
    {
        cout << k << " " << k * (k + 1) / 2 << endl;
        return;
    }
    for (ll i = 1; i <= n; i++)
    {
        if (b[i].v == mn)
            continue;
        g = gcd(g, b[i].v - mn);
    }
    // 单调栈求出所有的区间
    stack<ll> q;
    q.push(0);
    for (ll i = 1; i <= n; i++)
    {
        while (b[q.top()].v > b[i].v)
            q.pop();
        b[i].l = q.top() + 1;
        q.push(i);
    }
    while (q.size())
        q.pop();
    q.push(n + 1);
    for (ll i = n; i >= 1; i--)
    {
        while (b[q.top()].v > b[i].v)
            q.pop();
        b[i].r = q.top() - 1;
        q.push(i);
    }
    // 初始化每个节点管理的区间
    ll cnt = 0, sum = 0;
    auto check = [&](ll x) -> bool
    {
        for (ll i = 1; i <= n; i++)
        {
            ll div = b[i].v + x;
            for (ll j = b[i].l; j <= b[i].r; j++)
            {
                if ((b[j].v + x) % div != 0)
                {
                    return false;
                }
            }
        }
        return true;
    };
    for (ll i = 1; i * i <= g; i++)
    {
        if (g % i == 0)
        { // 这是一个因数
            ll x = i - mn;
            if (x > 0 && x <= k)
            {
                if (check(x))
                {
                    cnt++;
                    sum += x;
                }
            }
            ll j = g / i;
            x = j - mn;
            if (j != i && x > 0 && x <= k)
            {
                if (check(x))
                {
                    cnt++;
                    sum += x;
                }
            }
        }
    }
    cout << cnt << " " << sum << endl;
}

int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    ll t;
    cin >> t;
    while (t--)
        solve();
}
代码二(单调栈RMQ优化):

理论时间复杂度:$O(n \log n + d(g) \cdot n)$。

但和上面的代码速度接近~

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl "\n"
struct elem{
    ll l, r, v = 0 ;
};
const int N = 200010, M = 18;
int n, m;
int w[N], f[N][M];

void solve(){
    ll n, k;
    cin >> n >> k;
    vector<elem> b(n + 2);
    ll mn = 1e9 , mx = 0;
    for (ll i = 1; i <= n; i++)
    {
        cin >> b[i].v;
        mn = min(mn, b[i].v);
        mx = max(mx, b[i].v);
        b[i].l = i, b[i].r = i;
    }
    ll g = 0;
    if(mn == mx){ //直接退出
        cout << k << " " << k * (k + 1) / 2 << endl;
        return;
    }
    for (ll i = 1; i <= n; i++)
    {
        if (b[i].v == mn)
            continue;
        g = __gcd(g, b[i].v - mn);
    }
    // 单调栈求出所有的区间
    stack<ll> q;
    q.push(0);
    for (ll i = 1; i <= n; i++)
    {
        while (b[q.top()].v > b[i].v)
            q.pop();
        b[i].l = q.top() + 1;
        q.push(i);
    }
    while (q.size())
        q.pop();
    q.push(n + 1);
    for (ll i = n; i >= 1; i--)
    {
        while (b[q.top()].v > b[i].v)
            q.pop();
        b[i].r = q.top() - 1;
        q.push(i);
    }
    // 初始化每个节点管理的区间
    ll cnt = 0, sum = 0;
    auto init = [&]() -> void
    {
        for (int j = 0; j < M; j++)
        {
            for (int i = 1; i + (1 << j) - 1 <= n; i++)
                if (!j)
                    f[i][j] = abs(b[i].v - b[i-1].v);  
    // 不能减去mn 因为我们没有单独去求[b[i].l,b[i].r]这个小区间的mn
                else
                    f[i][j] = __gcd(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
        }
    };
    init();
    auto ask = [&](int la, int rb)->int{   // RMQ询问区间gcd
        int len = rb - la + 1;
        int j = log(len) / log(2);
        return __gcd(f[la][j], f[rb - (1 << j) + 1][j]);
    };

    auto check = [&](ll add) -> bool
    {
        for (ll i = 1; i <= n; i++)
        {
            ll div = b[i].v + add;
            int la = b[i].l, rb = b[i].r;
            if(la == rb)
                continue;
            if (ask(la + 1, rb) % div != 0)
            {
                return false;
            }
        }
        return true;
    };
    vector<int> d; // 存因数
    for (ll i = 1; i * i <= g; i++)
    {
        if (g % i == 0)
        {
            d.push_back(i);
            if (i * i != g)
                d.push_back(g / i);
        }
    }
    for (auto x : d)
    {
        int add = x - mn;
        if (add > 0 && add <= k)
        {
            if (check(add))
            {
                cnt++;
                sum += add;
            }
        }
    }
    cout << cnt << " " << sum << endl;
}

int main(){
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    ll t;
    cin >> t;
    while(t --)
        solve();
}
代码三(笛卡尔树遍历):

复杂度近似:$O(n \log n + d(g) \cdot n)$,效率和上面代码类似。

本质上树遍历的思路是比较直接的。

笛卡尔树建树代码(详细注释):

int ls[N], rs[N], stk[N];

void build() {
    // top 是栈顶指针,指向 sta 数组中的栈顶位置。0代表栈为空。
    int top = 0;

    // 每次建树前,重置左右孩子数组,防止多组测试数据之间互相影响。
    for (int i = 1; i <= n; i++) ls[i] = rs[i] = 0;

    // 从左到右依次处理每个节点 i
    for (int i = 1; i <= n; i++) {
        // pos 是一个临时指针,从当前栈顶开始,用于寻找节点 i 的插入位置。
        // 这样可以保留原始的栈顶位置 top,方便后续比较。
        int pos = top;

        // --- 核心:单调栈操作 ---
        // 这个循环的目的是找到 i 在“右轮廓”上的正确位置。
        // 只要栈不为空(pos>0),且栈顶节点的值大于当前节点 b[i] 的值,
        // 就不断将栈顶指针向栈底移动(模拟出栈)。
        // 循环结束后,sta[pos] 将是 i 的父节点。
        while (pos > 0 && b[sta[pos]] > b[i]) {
            pos--;
        }

        // --- 建立连接关系 ---

        // 如果循环结束后栈不为空(pos > 0),
        // 那么新的栈顶 sta[pos] 就是 i 的父节点。
        // 因为 i 的索引比父节点大(即 i 在父节点的右边),所以 i 成为其父节点的右孩子。
        if (pos > 0) {
            rs[sta[pos]] = i;
        }

        // 如果 pos < top,说明刚刚的 while 循环至少弹出了一个元素。
        // sta[pos+1] 就是最后一个被弹出的元素。这个被弹出的子树现在整体作为 i 的左孩子。
        // sta[pos+1] 是这个子树的根,所以它被连接为 i 的左孩子。
        if (pos < top) {
            ls[i] = sta[pos + 1];
        }

        // --- 更新栈 ---
        // 将当前节点 i 的索引压入栈中,放在 pos 指向的位置的下一个。
        sta[++pos] = i;
        // 更新真实的栈顶指针,完成本次操作。
        top = pos;
    }
}

完整代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5e4 + 10; 
int n, k; 
int a[N], b[N];
// 构建笛卡尔树的全局变量
int ls[N], rs[N], sta[N];

void build(){  // 建笛卡尔树
    int top = 0;
    // 初始化,防止旧数据影响
    for(int i = 1; i <= n; i++) ls[i] = rs[i] = 0;
    for(int i = 1; i <= n; i++){
        int pos = top;
        while(pos > 0 && b[sta[pos]] > b[i]){
            pos--;
        }
        if(pos > 0){
            rs[sta[pos]] = i;
        }
        if(pos < top){
            ls[i] = sta[pos+1];
        }
        sta[++pos] = i;
        top = pos;
    }
}

bool dfs(int node, int fa_node){
    // 如果父节点存在,且父节点的值不能整除当前节点的值,则不合法
    if (fa_node != 0 && b[node] % fa_node != 0) {
        return false;
    }
    // 递归检查左子树和右子树
    bool left_ok = true, right_ok = true;
    if (ls[node]) {
        left_ok = dfs(ls[node], b[node]);
    }
    if (rs[node]) {
        right_ok = dfs(rs[node], b[node]);
    }
    return left_ok && right_ok;
}

// check(x)函数: 验证给定的x是否合法
// 这个函数是你的代码的精华,它正确地实现了对一个解的验证
bool check(int add){
    for(int i = 1; i <= n; i++) b[i] = a[i] + add;
    build();
    // 从笛卡尔树的根节点sta[1]开始DFS,根没有父节点,父节点值设为0
    return dfs(sta[1], 0);
}

void solve(){
    cin >> n >> k;
    int mn = 1e9 , mx = 0 ;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
        mn = min(mn, a[i]);
        mx = max(mx, a[i]);
    }
    // 特殊情况:mx == mn
    if(mn == mx){
        cout << k << " " << k * (k + 1) / 2 << "\n";
        return;
    }

    // 步骤1:根据题解思路,生成候选x
    // 计算整个序列相邻元素的差分的GCD
    int g = 0;
    for(int i = 1; i < n; i++){
        g = __gcd(g, abs(a[i + 1] - mn));
    }
    // 找到g的所有因子d
    vector<int>d;
    for(int i = 1; i * i <= g; i++){
        if(g % i == 0){
            d.push_back(i);
            if(i * i != g) d.push_back(g / i);
        }
    }

    int cnt = 0, sum = 0;
    // 步骤2:遍历所有候选x,并用check函数进行验证
    for(int x : d){
        // 根据 (mn + x) = x 反解出候选x
        int add = x - mn;
        // 检查x是否在合法范围内
        if(add < 1 || add > k) continue;
        // 调用验证函数
        if(check(add)) {
            cnt++;
            sum += add;
        }
    }

    cout << cnt << " " << sum << "\n";
}

signed main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int t;
    cin >> t;
    while(t--) solve();
    return 0;
}

S4 Hana SD -信贷管理 - 03 自动信用控制将针对同一信用控制范围的不同的客户风险类别和信贷组执行不同的信用控制政策。对于ECC系统而言,无需单独设置检查规则,可在自动信贷控制进行设置即可,如图1-5-2。在S4 HANA版本中,自动信贷控制和检查规则是分离的。S4系统需要在定义自动信贷控制的时候,给字段“SAP信用管理”打钩,激活检查规则,然后需要单独在检查规则页签设置检查规则。接下来,我们分别介绍ECC和S4 HANA系统中两种不同配置方式.6.2SPRO。 阅读详情

相关推荐

【Cadence】stb仿真和ac仿真——以一个简单的全差分反相放大器仿真为例

最近在补一些仿真方法学的问题,以及一些一直以来都有点模糊的概念,简单记录一下。搭建一个以两个CMOS反相器构成的全差分放大器(28nm工艺,PMOS和NMOS尺寸一致)激励的设置方便共模和差模仿真用同一个TB(这里有一个问题:这个放大器需要在开环下仿真还是闭环下?哪些放大器应用在开环,哪些闭环?这种应用场景不同会导致需要仿真得到开环还是闭环增益,或者环路增益?(我知道放大器在设计的时候肯定都是闭环应用的多,但是什么时候没必要闭环应用?光验证ac和stb仿真是否一样的话有必要有回路吗?

weixin_44951108的博客 5万+

The 2024 ICPC Asia Hangzhou Regional Contest部分题解

很久之前vp的了,现在才将题目补的差不多

Vcoistnt的博客 5984

图解链表 —— JAVA中的单链表基本操作

文章目录1. 什么是链表2. 链表的种类3. 创建单链表3.1 创建 Node 类(车厢类)3.2 创建 MySingleList (火车类)4. 单链表的基本操作4.1 增加链表元素4.1.1 在链表头部添加元素4.1.2 在链表任意位置添加元素4.1.3 在链表尾部添加元素4.2 删除链表元素4.2.1 删除第一个节点元素4.2.2 删除任意位置 index 元素4.2.3 删除链表中指定元素的第一个节点4.2.4 删除链表的所有指定元素4.3 查找链表元素4.3.1 判断链表中是否包含元素 data4

AlinaQ05的博客 2502

2024ICPC杭州站 A题

【代码】2024ICPC杭州站 A题。

Kagari22的博客 585

深入解析SGLang框架:从架构设计到核心组件实现

本文深入解析了SGLang框架的架构设计与核心组件实现。SGLang是一个专为大语言模型推理设计的高效框架,其核心优势在于清晰的分层架构、高效的调度策略以及精细的内存管理。文章详细剖析了其客户端接入、服务器端调度、模型执行引擎及Tokenizer/Detokenizer管理器等关键组件,揭示了其实现高性能推理的内部机制。

coffee的博客 577

win10 离线安装.Net Framework3.5

起因,安装软件比如3dmax2014等需要.Net Framework3.5支撑,那么需要安装低版本的.Net Framework3.5 闲话少说。 1.下载 NetFx3.cab 链接: https://pan.baidu.com/s/124to7F3BOy4yAlY61OjbDg 提取码: kixu 2.将NetFx3.cab 放入 系统盘的windows目录下(示例 C:\Wi...

m0_37615458的博客 7723

The 2023 ICPC Asia Hangzhou Regional Contest (The 2nd Universal Cup. Stage 22: Hangzhou)D、J、M题题解

这道题一定要看清题,题中给的就是一个V图,就不用再排序了。然后针对于这个图,我们发现最大的有可能的三个范围实际上就是全部的sum/n或者是最低点左边的全部和加上右边第一个除以对应的个数或者是最低点右边的和加上最低点左边的数除以相应的个数,这个可以大概理解一下,这个代码中要注意的是,我们最后要保留9位输出的话,也就是意味着我们要使用double类型的变量,但输入时数据为int类型,因此我们直接用int接受,但最后使用出发求职的时候用1.0L*res这种方式使得结果变为double类型。

wzxtsl的博客 557

fMRI原始数据分割的笔记

好记性不如烂笔头_fMRI小白入门笔记

ZY_brain的博客 2897

2024ICPC杭州站 K题

【代码】2024ICPC杭州站 K题。

Kagari22的博客 543

OSGEarth三维可视化避坑指南:如何高效加载SHP/TIF地理数据?

本文详细介绍了OSGEarth在三维地球可视化中高效加载SHP/TIF地理数据的实战策略,包括空间参考系配置、性能优化、二三维联动等关键技巧。通过具体案例和代码示例,帮助开发者解决坐标偏移、性能卡顿等常见问题,提升三维GIS开发效率。

weixin_29216957的博客 222

Apache Flink连载(一):Apache Flink是什么?

Apache Flink 是一个框架和分布式处理引擎,用于在。

qq_32020645的博客 1216

2023ICPC区域赛杭州站

2023ICPC区域赛杭州站

djhws144的博客 1966

华为OD机试 - TLV格式(Python/JS/C/C++ 2025 C卷 100分)

两端通过TLV格式的报文来通信,现在收到对端的一个TLV格式的消息包,要求生成匹配后的(tag,length,valueOffset)列表。和需要匹配的n个tag对应的n行匹配结果,每一行由长度和偏移量组成。第二行:需要匹配的tag数量n(0 < n < 1000)后面N行:需要匹配n个tag值(十进制表示),递增排列。标签10(0A)存在,长度3,valueOffset=2。标签15(0F)长度4,valueOffset=2。标签14(0E)长度2,valueOffset=8。

学Java,找哪吒 2041

2024年,ICPC比赛、CCPC比赛、CCF-CSP考试、蓝桥杯比赛、天梯赛日程

第48届(2023) ICPC EC Final 2024年1月12-14日 上海大学。第九届CCSP全国总决赛 2024年10月?院校报名时间:2023年10月7日——2023年12月15日。秦皇岛分站赛 2024年?日 东北大学秦皇岛分校?女生专场 重庆 2024年?日 重庆对外经贸大学?高职专场 嘉兴 2024年?比赛日期:2024年4月?桂林分站赛 2024年?日 桂林电子科技大学?哈尔滨分站赛 2024年?陕西邀请赛 2024年?深圳分站赛 2024年?报名截止日期: 2024 年 3 月?

海岛Blog 5万+

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

2023年ICPC杭州站题解

假设这条边的两个端点是 i j,如果 i j 不是 1 2 的话,直接先让 i 和 i - 2 连,有边再让 i 和 i - 1 连,如果还是有边就说明是星形,如果没有边就让 j 和 i - 2、i - 1 连(很容易判断就不详细说了),如果 i j 是 1 2 的话就需要单独注意一下。,看到这就很容易想到那个 n/2 是两两之间连一次吧,如果从头到尾都没碰到相连的边,说明不可能是星形(因为根本没有中间的那个点),一定是链,一旦碰到一个相连的边,就立刻停下。标记),其他就正常跑最短路,然后距离在。

Texcavator的博客 5085

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

2024 ICPC Asia Pacific Championship 签到题

跑一边dijkstra并记录路径,得到从起点开始的距离数组,根据记录的路径往回走,对于路径中的每个点枚举它能走的其他所有边,寻找最小值即可。:给出 n 行只含0与1的字符串,要求调整其中每个 1 和 0 所在字符串的位置,使得每个字符串要么全为 1,要么全部为0(不能新增字符串),求最小调整次数。:对于每个字符串,假设其中 ‘1’ 的数量为 a,‘0’ 的数量为b,调整其中数量更少的字符一定是最优选择,即答案中一定包括。一个字符串在清理掉数量少的‘异类’后,变为全部为 0 或者全部为 1 的字符串。

yaoye222的博客 2012

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

2023ICPC杭州区域赛

链中每个节点最多有两条边,而菊 中心节点大于等于三条边相连,因此只需找到任意一条连接的边,再找出两个不同于此边上的节点,判断此边上的节点(若是菊,则有一个一定是中心节点)是否可以与这两个不同的点连接,如果中心节点可以和这两个节点相连,则是菊,否则是链。从b点开始,左边的数均比b大,加上后平均值一定比当前的数大,但是此时如果向右边规划,不能保证加上右边的数平均数一定会增大,因为无法保证右边的数比当前的平均数大。V图:长度>=3,且包含一个最小值,最小值前面的数降序排列,最小值后面的数升序排列。

2301_80281705的博客 1820

2023年ICPC亚洲区域赛(杭州)

我们可以将a[2]+a[3],a[4]+a[5]…先找到两个相连的节点a,b,再找两个除a,b外的任意结点c,d.如果a,c有边,则判断a,d是否有边,有边为菊,无边为链,如果a,c无边则判断b,c与b,d,如果两者都有边,则为菊,否则为链.一个长度为n的整数序列a,此序列呈V图:对于1

2302_80707071的博客 1011

2024ICPC杭州--H《Heavy-light Decomposition》--树的构造

所以我们只要判断按既定规则挂完所有长度小于maxlen的链后,root的儿子是否挂了链,如果挂了,那么长度为maxlen的链就可以挂root,否则是impossible。【由于根基链是长度最长的,所以剩下的链长度最多就是maxlen,不会大于maxlen】我们发现,所有长度为1的链都可以挂在4号,长度为2的都可以挂在3号,...,长度为maxlen-1的可以挂在1号。一个非叶节点的所有儿子中,子树节点数最大的儿子就是重儿子。也就是说,从根基链的末开始,可挂的长度是0,1,2,...maxlen-1。

2301_80204127的博客 946

视频隐私脱敏工程化:CSRT/KCF追踪算法在本地推理中的实现与边界

在短视频内容合规审查中,动态遮挡(Dynamic Masking)是高频且高耗时的环节。传统手动关键帧打码不仅效率低下,且在目标快速移动或遮挡时极易出现遮罩漂移。本文从计算机视觉工程角度,解析基于CSRT与KCF算法的目标追踪方案在视频隐私处理中的技术路径、性能权衡及落地实践。

Mysssone的博客 248

【算法拾光】链表 哈希表|K个一组翻转链表 + 两数之和

K个一组翻转链表需分段反转,借助哑节点与前驱指针维护连接,避免断链;两数之和利用哈希表实现O(1)查找,空间换时间,高效定位目标组合。前者考察链表指针操作,后者聚焦映射关系构建,均为面试高频题。

wuqingyigexiaoba的博客 320

把 KV Cache 压到 FP4:BitDecoding-FP4 的算法-系统协同设计复盘

长上下文推理卡在KV Cache的显存与带宽上,而现有FP4方案要么精度崩、要么速度废。这篇文章复盘BitDecoding-FP4如何用三个算法补丁加一个系统重构,在B300上把服务吞吐提升2.4–3.0倍,精度几乎不掉。

helloagent的博客 193

排序算法深度笔记(下):非比较类排序的线性突破与工程边界

排序算法时间复杂度额外空间复杂度稳定性是否基于比较选择排序O(N^2)(O(1))不稳定是冒泡排序O(N^2)(O(1))稳定是插入排序O(N^2)(O(1))稳定是归并排序O(N\log N)(O(N))稳定是快速排序O(N\log N)O(log N)不稳定是堆排序O(N\log N)O(1)不稳定是计数排序(O(N + M))O(M))稳定否基数排序O(N)稳定否桶排序平均O(N) / 最坏O(N^2)

2401_87069538的博客 596

优选算法:分治-归并排序

交易逆序对的总数

Wangshijie1015的博客 136

算法日常・每日刷题--<动态规划>42

本题为“不相交的线”,与最长公共子序列问题相似。使用动态规划求解:定义 dp[i][j] 表示 nums1[0:i] 与 nums2[0:j] 的最大不相交连线数。若元素相等,则继承左上值加1;否则取上方或左方最大值。最终返回 dp[m][n],即两数组的最大不相交连线数。时间复杂度 O(mn),空间复杂度 O(mn)。

2503_92261635的博客 22

408 算法 06|堆与 Top-K:最小的 10 个数,到底该用数组还是堆

2022(42) 找 100 万个数里最小的 10 个,官方给了"10 元素有序数组"和"大根堆"两种答案。我把两种都实现并数了比较次数:k=10 时两者几乎打平,k=10000 时堆少 111 倍。另附 2021(42) 计数排序的比较次数与稳定性实测。

m0_56226131的博客 358
上一篇: The 2024 ICPC Asia Nanjing Regional Contest(2024南京区域赛EJKBG)
下一篇: Codeforces Round 1054 (Div. 3)-G. Buratsuta 3
西望云天
博客等级 码龄3年 74粉丝 21原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值