2023 11月 AtCoder 做题记录

AGC037F

题目传送门

第一步,考虑判断序列是否合法。

通过对于属于等级 $k$ 的定义将定义反推:$s$ 中最小的元素 $x$,找到所有 $x$ 的连续段。设一个连续段的长度是 $len$,若 $len < l$ 则不合法,否则将这一段合并为 $\lfloor \frac {len}l \rfloor$ 个 $x + 1$,直到 $s$ 中只剩下一种元素,同时为了满足这个定义,这个元素一定是原 $s$ 最大值,且不再合并这个元素。

令 $s$ 最大值为 $m$,显然,合法情况有两种:

  • 1. 若 $m$ 的数量为 $1$,这种情况当且仅当原序列的长度为 $1$,$s$ 属于等级 $m$,合法。
  • 2. 若 $m$ 的数量不少于 $l$,$s$ 属于等级 $m + 1$,合法。

考虑将这个过程应用到个序列 $a$ 中。

从小到大考虑 $a$ 中出现过的值 $m$,当考虑到 $m$ 时,计算 $a$ 中所有最大值为 $w$ 的子段的中合法的数量,用栈维护。

现在,转化为问题:有一个序列 $s = s_0ms_1m \cdots $,其中 $s_i$ 为一个所有元素都 $<m$ 的序列,求这个序列中有多少个子段满足其属于等级 $m+1$。

设 $w$ 为 $s$ 中的最大值,对于一个序列 $s$ 维护信息:

  • $f_{s,i}$ 表示 $s$ 中至多能合并出 $i$ 个 $w$ 的前缀个数。
  • $g_{s,i}$ 表示 $s$ 中至多能合并出 $i$ 个 $w$ 的后缀个数。
  • $h_{s}$ 表示 $s$ 至多能合并出的 $w$ 的个数(由 $f,g$ 求出)。

根据 $f_s,g_s,h_s$,可求出 $∀w^\prime > w$,$s$ 中至多能合并出 $i$ 个 $w^\prime$ 的前缀与后缀个数,$s$ 至多能合并出的 $w^\prime$ 的个数。

假设我们现在对于 $s = s_0ws_1w \cdots ws_m$ 中的所有 $s_i$ 都求出了 $f_{s_i},g_{s_i},h_{s_i}$,则我们首先可以求出 $s_i$ 中至多能合并出 $i$ 个 $w$ 的前/后缀个数,以及 $s$ 至多能合并出的 $w$ 的个数。之后就可以求出答案了。

时空复杂度 \(O(n\) \(logn)\)

#include <bits/stdc++.h>

#define rint register int
#define int long long
#define endl '\n'

using namespace std;

const int N = 2e5 + 5;

int n, l;
int s[N], f[N], g[N];
int sf[N], sg[N];
int ans;
int top;

void pop()
{
    int k = 1;
	int x = s[top];
	
    while (s[top - 1] == x) top--, k++; 
    top--;
    
    if (k < l)
    {
        while (top != 0) pop();
        return;
    }
    
    for (rint i = 1; i <= k; i++)
    {
        sf[i] = sf[i - 1] + f[i + top];
		sg[i] = sg[i - 1] + g[i + top];		
	}

    for (rint i = l; i <= k; i++)
    {
        ans += sf[i - l + 1] * g[i + top];		
	}

    int m = k / l;

    for (rint i = 1; i <= m; i++)
    {
        f[top + i] = sf[k - l * (m - i + 1) + 1] - sf[max(k - l * (m - i + 2) + 1, 0ll)];
        g[top + i] = sg[min(l * (i + 1) - 1, k)] - sg[l * i - 1];		
	}

    int p = 0;

    for (rint i = l; i <= m; i++)
    {
		p += f[top + i - l + 1];
		ans -= p * g[top + i];	
	}

    for (rint i = 1; i <= m; i++)
    {
        s[++top] = x + 1;		
	}
}

signed main()
{
    cin >> n >> l;
	ans = n;
    
	for (rint i = 1; i <= n; i++)
    {
        int a;
		cin >> a;
        while (top != 0 && s[top] < a) pop();
        s[++top] = a;
		f[top] = g[top] = 1;
    }
    
    while (top != 0) pop();
    
    cout << ans << endl;
    
    return 0;
}

ARC102F

题目传送门
题目大意为,给定长度为 \(n\) 的排列 \(p\), 可以进行无限次操作, 问最终能否将其排成升序. 其中, 一次操作定义为选择 \(i\) 使得 $ 2 \leq i \leq n-1$ 且 \(p_{i-1}>p_i>p_{i+1}\). 交换 \(p_{i-1},p_{i+1}\)

在上一道题的求解中,用到了逆向思维,这道题同样如此。

对于此题,假设 择 \(i\) 使得 $ 2 \leq i \leq n-1$ 且 \(p_{i-1}>p_i>p_{i+1}\). 交换 \(p_{i-1},p_{i+1}\) 中,\(i\) 为合法点。如果 \(p_i \ne i\)\(i\) 为合法点,显然,无论怎样操作都会自相矛盾对吧,发现, 若 \(i\) 为合法点, 则在所有操作之前, \(p_i=i\)

通过这个性质我们可以先筛掉一些一定不合法的序列。

但是只通过这个性质并不全。

定义数组 bool a[i]=[i==p[i]], 如果 \(a\) 中有连续的 \(3\)\(0\) 出现, 无解. 考虑将 \(a\) 分成若干个区间, 使得同一个区间内没有两个连续的数相等. 易证区间与区间之间不存在数的交换。个区间的值域的范围和下标的范围应该要一样. 在一个区间内, 考虑把 \(a_i=0\) 的数单独拿出来, 如果其最长下降子序列的长度大于等于三则无解. 考虑有 \(3\) 个数 \(a>b>c\), 显然它们是无法交换过来的.

此做法时空复杂度 \(O(n)\)

#include <bits/stdc++.h>

#define rint register int
#define int long long
#define endl '\n'

using namespace std;

const int N = 5e5 + 5;

int n, p[N];
bool a[N];
int minn[N];
int s[N], top;

signed main()
{
    cin >> n;
    p[n + 1] = n + 1;   
	 
	for (rint i = 1; i <= n; i++)
	{
		cin >> p[i];
	}
    
    for (rint i = 1; i <= n; i++)
    {
        if (p[i - 1] != i - 1 && p[i] != i && p[i + 1] != i + 1)
        {
			puts("No");
			return 0;
		}
		else if (i == p[i])
		{
			a[i] = 1;
		}
    }

    rint j = 0;
    for (rint i = 1; i <= n; i = j + 1)
    {
        j = i;
        while (j < n && a[j] != a[j + 1]) j++;
        top = 0;
        for (rint k = i; k <= j; k++)
        {
            if (p[k] < i || j < p[k])
            {
				puts("No");
				return 0;
			}
            if (!a[k])
            {
                s[++top] = p[k];				
			}
        }
        minn[top] = s[top];
        for (rint k = top - 1; k >= 1; k--)
        {
            minn[k] = min(minn[k + 1], s[k]);			
		}
        int maxx = s[1];
        for (rint k = 2; k < top; k++)
        {
            maxx = max(maxx, s[k]);
            if (s[k] > minn[k] && s[k] < maxx)
            {
				puts("No");
				return 0;
			}			
        }
    }

    puts("Yes");
    
    return 0;
}

AGC039D

题目传送门

傻逼数学题平面几何

结论对于任意 \(\triangle ABC\),三段弧中点的坐标之和,就是 \(\triangle ABC\) 内心的坐标。

由垂心角平分线定理和欧拉线很容易就能整出来。代码不粘了。

AGC021E

题目传送门

很好的一道组合数学题:

我们可以先手搓几个样例玩玩,通过总结,发现最多能喂 \(k\) 只变色龙的都是这样的序列:先是一段 \(A\),然后一段一段 \(AB\) 相连的东西(严格来说是 \(BA\) 相连......)

然后最后一段先是 \(A\) 后是 \(B\)

如果最后喂给那只被钦定的变色龙的 \(A<B\) 时,最多只能弄出 \(x\) 只红变色龙,显然,手上的那个球只能是 \(A\)。否则要拯救那只变色龙,手上的球只能是 \(B\)

通过 $x-1 $个 \(B\) 的位置确定整个序列

方案数是 \(C^{k-1}_{x-1}\)

ABC197F

题目传送门

回文字符串的特点是对称性,不难想到这个题是双向搜索

先考虑长度为偶数的情况,初始两个位置分别是 \(1,n\),每次两个位置各选择一条连出的边,满足边上的字母相同,并向另一个端点移动,最终两个位置重合。

设这个相同的位置是 $k$,此时 $1\rightarrow k$ 的路径和 $n\rightarrow k$ 的路径所形成的字符串相同。将 $n\rightarrow k$ 反过来,再在前面接上 $1\rightarrow k$,最终会得到一条满足条件的路径。把两个位置放进状态中,再 bfs。

奇数的情况,找到两个位置之间恰好隔了一条边的情况即可

AGC022F

题目传送门
一道不错的 \(dp\),但是实力不够,学了一下 APijifengC 的题解。

得到的数肯定都是 \(\sum 2^a(-1)^bx^i\) 的形式,只关心最后得到的系数的集合即可。

考虑这个集合的性质:

  • \(\forall i\in A,\, \exists j,\, \rvert i\lvert=2^j\) ,即集合中每个元素的绝对值都是 \(2\) 的次幂。
  • \(∑ _{i∈A}i=1\),即集合中元素的和为 \(1\)
  • \(2^i\)\(-2^{i}\) \(∈A\),那么\(2^{i-1}\)\(-2^{i-1}\) \(∈A\)
  • \(1\)\(-1\) 中有一个且仅有一个属于 \(A\)
  • 所有指数小于等于 \(i\) 的数的和为 \(1\)

\(f_{i,j}\) 表示已经放了 \(i\) 个元素,并且这些元素的和为 \(1+jV\),其中 \(V\) 为现在要放的 \(2\) 的次幂是多少。

枚举选了 \(a\)\(V\),选了 \(b\)\(−V\),元素的和变为 \(1+(j+a-b)V\)。因为下一个填的数就要比现在的数乘 \(2\),所以 \(f_{i,j}\) 转移到 \(f_{i+a+b,\frac{j+a-b}{2}}\)

需要满足将这些数任意标正负号之后,和为 \(1\),那只需要满足 \(j \equiv a+b \pmod 2\)\(a+b\ge \rvert j\lvert\)

然后考虑对这个系数集合分配原来的 \(x^i\),这个就是多重组合数的形式 ($ \frac{n!}{\prod a!}$),把 \(\frac{1}{\prod a!}\) 的部分拆到转移上,即 \(f_{i,j} \times \frac{1}{a!b!}\rightarrow f_{i+a+b,\frac{j+a-b}{2}}\)

初始状态为 \(f_{0,-1}=1\),答案 \(n!f_{n,0}\)

signed main()
{
    cin >> n;
    
    f[0][N - 1] = 1;
    init();

    for (rint i = 0; i < n; i++)
    {
        for (rint j = -n; j <= n; j++)
        {
            if (f[i][j + N])
            {
                for (rint a = 0; a <= n; a++)
                {
                    for (rint b = 0; i + a + b <= n; b++)
                    {
                        int s = a + b;
						int x = i + s;
                        int y = (j + a - b) >> 1;
						if ((a || b) && !((j + a + b) % 2) && abs(j) <= a + b)
                        {
                            if (i == 0 && a + b != 1)
                            {
								continue;
							}
                            f[x][y + N] = (f[x][y + N] + f[i][j + N] * inv[a] % P * inv[b]) % P;
                        }						
					}
                }
            }			
		}
    }
    
	cout << f[n][N] * fac[n] % P << endl;
	
    return 0;
}

AGC056D

题目传送门

\(n\) 是奇数时,枚举 Alice 第一次选的数,剩下的数两两匹配,若 Bob 选了其中一个,Alice 就选另一个。

显然,奇数位和它右边的偶数位匹配最优。

设奇数位的和为 \(q\),偶数位的和为 \(p\),此时 Alice 获胜当且仅当 \(l \le q + a_i \land p + a_i \le r\)

\(n\) 是偶数时,仍匹配。枚举 Alice 第一次选的数和这个数匹配的数,剩下的数仍是奇数位匹配右边偶数位最优。

若 Bob 选了这个数匹配的数,Alice 可以新开一个匹配,否则 Alice 选 Bob 选的数匹配的数即可。

想法很好,但代码我写出来的很冗长,就不放了。

AGC027E

题目传送门

考虑对字母进行赋值计算即可。

不妨设,\(a = 1, b = 2\) ,显然,操作之后,字符的值之和模 \(3\) 都是固定的。因此,可以便捷的求出之后会变成哪个字符。

考虑 dp 状态, 加上除了自己外的其它 \(f\) 值之和,\(f_{p_0} + f_{p_1} + f_{p_2} - f_{p_{s_i}}\),其中 \(p_i\) 表示上一个为 \(i\) 的字符的位置。最后答案为 f[n]

const int N = 1e5 + 5;
const int mod = 1000000007;

int n, ans;
int f[N], p[N], s[N];
char c[N];

bool check(char c[], int n)
{
	bool flag = 0;
	for (rint i = 1; i < n; i++)
	{
		if (c[i] == c[i + 1])
		{
			flag = 1;
		}
	}
	return flag;
}

signed main()
{
    cin >> c + 1;
    n = strlen(c + 1);
    
    if (!check(c, n))
    {
		cout << 1 << endl;
		return 0;
	}
    
    for (rint i = 1; i <= n; i++)
    {
        s[i] = (s[i - 1] + c[i] - 'a' + 1) % 3; 
    }
    
    for (rint i = 1; i <= n; i++)
    {
        if (s[i])
        {
			f[i]++;
		}                                                          
        f[i] = (f[i] + f[p[0]] + f[p[1]] + f[p[2]] - f[p[s[i]]]) % mod; 
        p[s[i]] = i;                                                          
    }
    
    cout << f[n] << endl;
    
    return 0;
}

ARC110E

题目传送门

和 AGC027E 整体做法和思路是一样的

现将字母转换成数字,\(A=1,B=2,C=3\)

给定的串分成好几段,每段异或和不为 \(0\),且不全是同一种字符(\(len≠1\))

每个段分别合并成一个字符,这样一个合法的划分方案就对应一个答案,注意,一个答案可以由若干个合法方案得到

\(f_i\) 表示 \(S\) 的后缀 \(i\) 的答案。

转移方程为: \(f_i =[xor(i,n)=0] + f_{g_{i,1}+1} + f_{g_{i,2}+1} + f_{g_{i,3}+1}\)\(xor(a,b)\) 表示 \(a\)\(b\) 的异或和。\(g_{i,j}=\min_{i \le r \le n} \{r|xor(i,r)=j\}\),即从 \(i\) 开始最短的一段拼出 \(j\) 的前缀。最后答案为 \(f[1]\)

const int N = 1e6 + 5;
const int mod = 1000000007;

int n;
int c[N], f[N];
int g[N][4];
int tag;

bool check(int c[], int n)
{
	bool flag = 0;
	for (rint i = 1; i < n; i++)
	{
		if (c[i] != c[i + 1])
		{
			flag = 1;
		}
	}
	return flag;
}

signed main()
{
    cin >> n;
    
    for (rint i = 1; i <= n; i++)
    {
        char a;
        cin >> a;
        c[i] = a - 'A' + 1;
        //cout << c[i] << endl;
    }
    
    if (!check(c, n))
    {
        cout << 1 << endl;
        return 0;
    }
    
    for (rint i = n + 1; i != 0; i--)
    {
        g[i][c[i]] = i;
        g[i][c[i] ^ 1] = g[i + 1][1];
        g[i][c[i] ^ 2] = g[i + 1][2];
        g[i][c[i] ^ 3] = g[i + 1][3];

        tag ^= c[i];
        if (tag == 0) f[i] = 1;
		if (tag != 0) f[i] = 0;
		
        if (g[i][1])
        {
			f[i] += f[g[i][1] + 1];
			f[i] %= mod;
		}
        if (g[i][2])
        {
			f[i] += f[g[i][2] + 1];
			f[i] %= mod;
		}
        if (g[i][3])
        {
			f[i] += f[g[i][3] + 1];
			f[i] %= mod;
		}
    }
    if (tag == 0)
    {
		cout << f[1] - 1 << endl;
	}
	else
	{
		cout << f[1] << endl;
	}
    
    return 0;
}

AGC001F

题目传送门

刚开始想的是反向拓扑排序,后来发现时空复杂度是 \(O(NK)\),铁定超时。之后用线段树维护,预估复杂度能到 \(O(n\) \(logn)\),然后我写了两个小时没过样例。想看看题解有没有也用线段树+反向拓扑的。然后第一篇 linghuchong_巨佬 的题解用归并排序秒掉了,\(ORZ\)

构造序列 \(S\) 使得 \(S_{p_i}=i\),变成交换相邻的差值大于等于 \(k\) 的数。

想实现这个问题可以直接冒泡排序,把大的尽可能往右挪,时空复杂度 \(O(n^2)\)

考虑优化,归并排序。一个右边的数能比左边的数先进行归并就要保证它加 \(k\) 小于等于左边的数的后缀最小值即可。每次归并时记录左边的后缀最小值。对于随机的数据,复杂度甚至接近 \(O(n)\)

const int N = 5e5 + 5;
const int inf = 1e9;

int n, k;
int minn[N], pos[N];
int a[N], b[N];

void merge(int l, int r)
{
	if (l == r)
    {
		return ;
	}
    int mid = (l + r) >> 1;
    merge(l, mid);
	merge(mid + 1, r);
    int _min = inf;
	int idx = l - 1;
	int x = l, y = mid + 1;
    for (rint i = mid; i >= l; i--)
    {
        _min = min(_min, a[i]),
        minn[i] = _min;		
	}
    while (x <= mid && y <= r)
    {
        if (minn[x] >= a[y] + k)
        {
            b[++idx] = a[y];
			y++;			
		}
        else
        {
            b[++idx] = a[x];
			x++;			
		}
    }
    while (x <= mid)
    {
        b[++idx] = a[x];
		x++;		
	}
    while (y <= r)
    {
        b[++idx] = a[y];
		y++;		
	}
    for (rint i = l; i <= r; i++)
    {
		a[i] = b[i];
	}
}

signed main()
{
    cin >> n >> k;
    for (rint i = 1; i <= n; i++)
    {
		int x;
		cin >> x;
		a[x] = i;
	}
    merge(1, n);
    for (rint i = 1; i <= n; i++)
    {
		pos[a[i]] = i;
	}
    for (rint i = 1; i <= n; i++)
    {
		cout << pos[i] << endl;
	}
    return 0;
}

AGC052D

题目传送门

挺不错的一道题,顺手写个题解交了。

对于一个序列是否能分成两个 LIS 长度相等子序列。我们要注意审题,他说的是两个长度相等的对吧。

也就是说,当对于是整体最长上升子序列长度 \(len\)\(len\) 为偶数,即 !(len % 2) 的时候,这个显然是有解的。

然后呢?先不要着急考虑奇数的情况,对于刚才偶数的情况还没有处理完,这个 \(len\) 怎么计算呢。设 \(f_i\) 是以 \(i\) 为结尾的最长上升子序列长度,那么不难得出:

\[len=max_{1≤i≤n} f_i \]

计算出 \(len\),就解决了偶数的情况。

接下来考虑奇数情况。\(g_i\) 表示以 \(i\) 为开头的最长上升子序列长度,我们不妨设 \(len = 2 × x + 1\)\(x∈N_+\)

两个子序列的最长上升子序列长度至少是 \(x + 1\),那么,是不是个 最长上升子序列之外的元素,存在某个长为 \(x + 1\) 的上升子序列覆盖着它呢?

设这个上升子序列 \(P\)\(P_1,P_2......P_{k+1}\)

对于一个序列,满足 \(f_i≠f_{p_k} or f_i=f_{j≠i}\)\(k≤x+1\) 的是选取序列,剩下的为剩余序列。

对于无论是选取序列还是剩余序列,我们发现,有不超过 \(x + 1\) 个不同的以 \(i\) 为结尾的最长上升子序列长度对吧。所以,对于选取序列,最长上升子序列长度是要不小于 \(x + 1\) 的,并且剩余序列的最长上升子序列长度同样要不小于 $ x + 1$。

所以,求出 \(f_i,g_i\),如果 \(f_i + g_i ≥ len / 2 + 2\),累计一次答案,如果最后累计结果 \(cnt > len\),那么就有解,否则无解。

时空复杂度 \(O(n\) \(log\) \(n)\)

int T, n;
int p[N], id[N];
int f[N], g[N];

signed main()
{
    cin >> T;
    while (T--)
    {
        cin >> n;
        for (rint i = 1; i <= n; i++)
        {
			cin >> p[i];
		}
        
		for (rint i = 1; i <= n; i++) id[i] = n + 1;
        
        int len = 0;
        
        for (rint i = 1; i <= n; i++)
        {
            f[i] = lower_bound(id + 1, id + n + 1, p[i]) - id;
            id[f[i]] = p[i];
            len = max(len, f[i]);
        }
        if (!(len % 2))
        {
            puts("YES");
            continue;
        }
        
        for (rint i = 1; i <= n; i++) id[i] = n + 1;
        
        for (rint i = n; i != 0; i--)
        {
            g[i] = lower_bound(id + 1, id + n + 1, n - p[i]) - id;
            id[g[i]] = n - p[i];
        }
        
        int cnt = 0;
        for (rint i = 1; i <= n; i++)
        {
			if ((f[i] + g[i]) >= (len / 2 + 2))
			{
				cnt++;
			}
		}
            
        if (cnt > len)
        {
            puts("YES");			
		}
        else
        {
            puts("NO");			
		}
    }
    
    return 0;
}

ARC107D

ARC107D

定义 \(f_{i,j}\) 为选了 \(i\) 个数,和为 \(j\)。这里不需要考虑分数造成的影响,这种影响在转移方程时会被弥补掉。

考虑如何转移:

  • \(f_{i-1,j-1}\) \(→\) \(f_{i,j}\)
  • \(\frac{1}{2^i}\) 可以被选,所以所有的数可都翻倍

综上,转移方程为 \(f_{i,j} = f_{i - 1,j - 1} + f_{i,j} * 2\)

signed main()
{
    cin >> n >> k;
    f[0][0] = 1;
    for (rint i = 1; i <= n; i++)
    {
        for (rint j = i; j >= 1; j--)
        {
            f[i][j] = f[i - 1][j - 1];
            int k = j << 1;
            if (i >= k)
            {
				f[i][j] += f[i][k];
			}
            f[i][j] %= mod;
        }
    }
    cout << f[n][k] << endl;
    return 0;
}

ARC159D

题目传送门

$f_i$ 表示以 $r_i$ 结尾的 LIS 长度。转移方程为 $f_i=\max(\max\{r_i+f_j-r_j|r_j\ge l_i\},\max\{r_i-l_i+1+f_j|r_j<l_i\})$。

然后离散化 $l_i$ 和 $r_i$ 后用两棵线段树维护 $f_i$ 和 $f_i-r_i$ 的最大值。

AGC007D

题目传送门

\(f[i]\) 表示收到前 \(i\) 个糖果的最小时间,其必然在第 \(x[i]\) 这个位置。

\(f[i]=\min(f[j]+x[i]-x[j]+\max(2\times(x[i]-x[j+1]),T))\)

对于 \(x[i]-x[j]\) ,所有 \(f[n]\) 必然是从头走到尾,所以要加上 \(x[n]\)

时空复杂度 \(O(n)\)


signed main()
{
	cin >> n >> m >> t;
	
	for (rint i = 1; i <= n; i++)
	{
		cin >> a[i];
	}
	
	int j = 0;
	
	for (rint i = 1; i <= n; i++)
	{
		while ((a[i] - a[j + 1]) * 2 >= t && j < n)
		{
			minn = min(minn, f[j] - 2 * a[j + 1]);
			j++;
		}
		f[i] = min(2 * a[i] + minn, f[j] + t);
	}
	
	cout << f[n] + m << endl;
	
	return 0;
}

ARC064E

题目传送门

圆外点到圆内的距离最短:和圆心连线。

将所有圆都看作圆心一个点,求出的距离必定是最短距离。

给任意两点间建边,边权就是圆心距离减去两个圆的半径,之后最短路即可。

邻接矩阵朴素 djkstra 就能过,没必要堆优化,时空复杂度 \(O(n^2)\)

signed main()
{
    cin >> x[0] >> y[0] >> x[1] >> y[1] >> n;
    
    for (rint i = 0; i < n; i++)
    {
		cin >> x[i + 2] >> y[i + 2] >> r[i + 2];
	}
	
	n += 2;
    
    for (rint i = 0; i < n; i++)
    {
		for (rint j = i + 1; j < n; j++)
		{
			double d = sqrt((x[j] - x[i]) * (x[j] - x[i]) + (y[j] - y[i]) * (y[j] - y[i]));
			a[i][j] = a[j][i] = max(0.0, d - r[i] - r[j]);
		}
	}
	
	for (rint i = 1; i < n; i++)
    {
		dist[i] = inf;
	}
	
	for (rint j = 0; j < n; j++)
	{
		int x = -1;
		for (rint i = 0; i < n; i++)
		{
			if (!v[i] && (x == -1 || dist[i] < dist[x]))
			{
				x = i;
			}	
		}
		v[x] = 1;
		for (rint i = 0; i < n; i++)
		{
			dist[i] = min(dist[i], dist[x] + a[x][i]);
		}
	}
	
	cout << fixed << setprecision(15) << dist[1] << endl;
	
    return 0;
}
posted @ 2023-11-03 22:02  PassName  阅读(19)  评论(0编辑  收藏  举报