代码源每日一题Div.1 (201-207)

201 - 摘桃子

题目链接

根据题意可以列出
( a l + a l + 1 + ⋯ + a r )   m o d   k = r − l + 1 (a_l+a_{l+1}+\cdots+a_r) \bmod k=r-l+1 (al+al+1++ar)modk=rl+1
其中, 0 ≤ r − l + 1 < k 0\leq r-l+1<k 0rl+1<k
设序列的前缀和为 S S S,则上式可变为
( S r − S l − 1 )   m o d   k = r − ( l − 1 ) (S_r-S_{l-1}) \bmod k = r-(l-1) (SrSl1)modk=r(l1)
在模 k k k的意义下进行移项,可变为
S r − r ≡ S l − 1 − ( l − 1 ) ( m o d k ) S_r-r≡S_{l-1}-(l-1) \pmod k SrrSl1(l1)(modk)

推到这一步,说明我们需要用一个新的序列 b b b来解决这道题目,令 b i = ( ( S i − i )   m o d   k ) + k )   m o d   k b_i=((S_i-i) \bmod k) + k) \bmod k bi=((Sii)modk)+k)modk,那么我们就需要找满足下列条件的 l , r l,r l,r的组数:

  1. 满足 r − l + 1 < k , 1 ≤ l , r ≤ n r-l+1<k,1\leq l,r\leq n rl+1<k,1l,rn
  2. b l − 1 = b r b_{l-1}=b_r bl1=br

如何算出组数?我们可以用map维护,用 m p [ x ] mp[x] mp[x]当前区间内有多少个 i i i满足 b i = x b_i=x bi=x

然后我们用 i i i从左到右遍历数组 b b b,同时维护一个以 i i i为右端点的长度小于 k k k的最长区间。我们维护一个指针表征左端点的位置。每当我们 i i i向右移动时,看移动后的 i i i所对应的 b i b_i bi在之前维护的区间里有多少个跟 b i b_i bi一样的值(这里区间实际上是 [ l − 1 , r ) [l-1,r) [l1,r))。将这个数量统计到答案中去,然后将 b i b_i bi扔进map中去,如果左指针要向右移动,就将移动前所指的数值从map中拿出去。

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;

LL n, k, ans = 0;
LL a[200050], pre[200050], b[200050];

void main2() {
	cin >> n >> k;
	pre[0] = 0;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
		pre[i] = pre[i - 1] + a[i];
		b[i] = ((pre[i] - i) % k + k) % k;
	}
	map<LL, LL> mp;
	int lp = 0;
	mp[0] = 1;
	for (int i = 1; i <= n; ++i) {
		ans = ans + mp[b[i]];
		if (i - lp + 1 >= k) {
			--mp[b[lp++]];
		}
		++mp[b[i]];
	}
	cout << ans;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	LL _;
//	cin >> _;
	_ = 1;
	while (_--) main2();
	return 0;
}

202 - 路径计数2

题目链接

我们观察到,障碍物的数量很少,但是整体的方格很大,所以我们可以从障碍物的角度入手这道题目。总体的可行方案数等于所有方案数-不可行方案数。因为障碍物少,所以我们可以先求出不可行的方案数。

首先,两点之间的总方案数的求法:假设两点坐标 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1,y_1),(x_2,y_2) (x1,y1),(x2,y2)且满足 x 1 < x 2 , y 1 < y 2 x_1<x_2,y_1<y_2 x1<x2,y1<y2,那么总方案数就是 C ( x 2 − x 1 + y 2 − y 1 , x 2 − x 1 ) C(x_2-x_1+y_2-y_1,x_2-x_1) C(x2x1+y2y1,x2x1)。即一共有 x 2 − x 1 + y 2 − y 1 x_2-x_1+y_2-y_1 x2x1+y2y1次移动,其中有 x 2 − x 1 x_2-x_1 x2x1次选择向下移动的方案数。

f [ i ] f[i] f[i]表示从 ( 1 , 1 ) (1,1) (1,1)到第 i i i个障碍物(设坐标 ( x i , y i ) (x_i,y_i) (xi,yi))的合法路径条数。

假设存在障碍物坐标 ( x 2 , y 2 ) (x_2,y_2) (x2,y2),且只存在一个障碍物坐标 ( x 1 , y 1 ) (x_1,y_1) (x1,y1)满足 x 1 < x 2 , y 1 < y 2 x_1<x_2,y_1<y_2 x1<x2,y1<y2,那么我们可以认为,从 ( 1 , 1 ) (1,1) (1,1) ( x 2 , y 2 ) (x_2,y_2) (x2,y2)的可行路径一定不经过 ( x 1 , y 1 ) (x_1,y_1) (x1,y1),换句话说,所有包含 ( x 1 , y 1 ) (x_1,y_1) (x1,y1) ( x 2 , y 2 ) (x_2,y_2) (x2,y2)的路径都是不合法路径。那么从 ( 1 , 1 ) (1,1) (1,1) ( x 2 , y 2 ) (x_2,y_2) (x2,y2)的路径的合法方案数就是 C ( x 2 − 1 + y 2 − 1 , x 2 − 1 ) − f [ 1 ] × C ( x 2 − x 1 + y 2 − y 1 , x 2 − x 1 ) C(x_2-1+y_2-1,x_2-1)-f[1]\times C(x_2-x_1+y_2-y_1,x_2-x_1) C(x21+y21,x21)f[1]×C(x2x1+y2y1,x2x1)

那么假如存在 m m m个障碍物分别是 b 1 , b 2 , ⋯   , b m b_1,b_2,\cdots,b_m b1,b2,,bm,对于第 3 3 3个障碍物均满足 x b i < x 3 , y b i < y 3 x_{b_i}<x_3,y_{b_i}<y_3 xbi<x3,ybi<y3,那么从 ( 1 , 1 ) (1,1) (1,1) ( x 3 , y 3 ) (x_3,y_3) (x3,y3)的合法方案数就是 C ( x 3 − 1 + y 3 − 1 , x 3 − 1 ) − ∑ i = 1 m f [ b i ] × C ( x 3 − x b i + y 3 − y b i , x 3 − x b i ) C(x_3-1+y_3-1,x_3-1)-\displaystyle\sum\limits_{i=1}^m f[b_i]\times C(x_3-x_{b_i}+y_3-y_{b_i},x_3-x_{b_i}) C(x31+y31,x31)i=1mf[bi]×C(x3xbi+y3ybi,x3xbi)

最后运算的时候注意对 1 0 9 + 7 10^9+7 109+7取模。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL mod = 1e9 + 7;

LL n, m;
LL o[3005][2], dp[3005];

LL jc[2000050], inv[2000050];

template<class T> T power(T a, LL b) {
	T res = 1;
    for (; b; b >>= 1) {
        if (b % 2) res = (res * a) % mod;
        a = (a * a) % mod;
    }
    return res;
}

void init(LL iN) {
	jc[0] = 1;
	for (LL i = 1; i <= iN + 1; ++i) {
		jc[i] = (jc[i - 1] * i) % mod;
	}
	inv[iN + 1] = power(jc[iN + 1], mod - 2);
	for (LL i = iN; i >= 0; --i) {
		inv[i] = inv[i + 1] * (i + 1) % mod;
	} 
}

LL C(LL N, LL M) {
	return jc[N] * inv[M] % mod * inv[N - M] % mod;
}

LL calc(int dx, int dy) {
	return C(dx + dy, dx);
}

LL vis[3005];

void dfs(int x) {
	if (vis[x]) return;
	vis[x] = 1;
	LL res = 0;
	for (int i = 1; i <= m + 1; ++i) {
		if (i == x) continue;
		int dx = o[x][0] - o[i][0], dy = o[x][1] - o[i][1];
		if (dx < 0 or dy < 0) continue;
		dfs(i);
		res = (res + (dp[i] * calc(dx, dy)) % mod) % mod;
	}
	dp[x] = ((calc(o[x][0] - 1, o[x][1] - 1) - res) % mod + mod) % mod;
}

void main2() {
	cin >> n >> m;
	for (int i = 1; i <= m; ++i) {
		cin >> o[i][0] >> o[i][1];
		vis[i] = 0;
	}
	o[m + 1][0] = o[m + 1][1] = n;
	o[0][0] = o[0][1] = 1;
	dfs(m + 1);
	cout << dp[m + 1];
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	init(2000000);
	LL _;
//	cin >> _;
	_ = 1;
	while (_--) main2();
	return 0;
}

203 - 函数求和

题目链接

由于 f ( i ) f(i) f(i) i i i范围很大,如果我们一个一个 f ( i ) f(i) f(i)去求,一定会超时。所以我们换一种思路。

我们发现, f ( i ) f(i) f(i)的值域很小,远小于定义域,所以我们可以数所有可能的 f ( i ) f(i) f(i)的取值,然后去数有多少个 i i i对应的值是那个数即可。

根据题意,如果能选择第一个数的,一定不会选择第二个及以后的数。首先我们发现, f ( 2 k − 1 ) = 0 f(2^k-1)=0 f(2k1)=0,因为 2 k − 1 2^k-1 2k1的二进制全是 1 1 1,所以不满足 a x & i ≠ a x a_x \& i ≠ a_x ax&i=ax。整体的思想就是,从头到尾依次遍历 a a a序列的每一个数,看 a x a_x ax能被怎么样的数选中。能选中这个数的 i i i,一定是 a x a_x ax的二进制里是 1 1 1的位中,有一些位在 i i i中是 0 0 0

更具体地,现在判断 a a a序列的第 x x x个数 a x a_x ax,是一个 k k k位二进制数,其中有 z z z位是 1 1 1,在当前能选的 i i i的集合中,这 z z z位里有 p p p位是集合没有限制的位,即可以选 0 0 0,可以选 1 1 1。除了这 p p p位以外,集合中没有限制的位还有 m m m个,那么 a x a_x ax为答案带来的贡献就是
x × ( 2 p − 1 ) × 2 m x\times (2^p-1)\times 2^m x×(2p1)×2m
之所以会有的位存在限制,是因为相对较早出现的 a x a_x ax对于这一位是 0 0 0的所有的 i i i都已经被 a x a_x ax占有了,再往后的 a x + 1 , a x + 2 ⋯ a_{x+1},a_{x+2}\cdots ax+1,ax+2,可以选择的数在这一位上都只可能是 1 1 1,这时在这一位上就没有选择权了。每次处理完一个 a x a_x ax,我们能够使用的 i i i就会越来越少,因为会有越来越多的位被占用。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL p = 998244353;

template <class T> T modmul(T MA, T MB) { return ((MA % p) * (MB % p)) % p; }
template <class T> T FPW(T FPWa, T FPWb) {
	T FPWbs = FPWa, FPWres = 1;
	while (FPWb > 0) { 
		if (FPWb & 1) FPWres = modmul(FPWres, FPWbs); 
		FPWbs = modmul(FPWbs, FPWbs); FPWb >>= 1;
	}
	return FPWres;
}

LL n, k, ans;
LL a[150];
LL t[150], hs[150];

void geths(LL x) {
	LL id = 0;
	while (x) {
		hs[id++] = (x & 1ll);
		x >>= 1;
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cin >> n >> k; ans = 0;
	for (LL i = 1; i <= n; ++i) {
		cin >> a[i];
	}
	for (LL i = 0; i < k; ++i) {
		t[i] = -1;
	}
	for (LL i = 1; i <= n; ++i) {
		memset(hs, 0, sizeof(hs));
		geths(a[i]);
		LL cnt = 0, tot = 0;
		for (LL j = 0; j < k; ++j) {
			if (hs[j] == 1) {
				if (t[j] == -1) {
					++cnt; t[j] = 1;
				}
			}
			else if (t[j] == -1) ++tot;
		}
		ans = (ans + (i * FPW(2ll, tot) % p * (FPW(2ll, cnt) - 1) % p))% p;
	}
	cout << ans;
	return 0;
}

204 - XOR Inverse

题目链接
原题:CF1416C
学习了严格鸽的题解

x x x异或,使整个序列的逆序对最小,我们可以贪心地从高位到低位进行考虑:高位01逆序对的数量小与低位01逆序对的数量相比,改变高位的逆序对数量,让高位的逆序对数量变少,可以更大程度上改变整个序列的逆序对情况(可以感性理解)。

建立01trie树,那么每一层就代表一个位。我们为了判断逆序对的数量,我们将trie树上的每一个结点都赋予这个结点所包含的数值的编号(即从这个点往下走,一直走到叶子结点处,查看这个数在原序列的位置,并记录在这一条路径的每一个结点上)。我们令每一个结点记录这个信息的数组是 i d id id

考虑01trie树中的一个结点,会有至多 2 2 2个子结点,一个是 0 0 0,一个是 1 1 1。设两个子结点所包含的 i d id id l 1 , l 2 , ⋯ l_1,l_2,\cdots l1,l2, r 1 , r 2 , ⋯ r_1,r_2,\cdots r1,r2,。由于我们是按照读入顺序加入这两个结点的 i d id id数组,所以内部一定是从小到大排序。我们结合归并排序的思想,对于这一个结点所表示的位,这一个结点所产生的贡献,就是这两个子结点之间的逆序对个数。通过总对数-逆序对个数,就是翻转之后(异或之后)的逆序对个数。

最后,对于每一位,查看翻转与否逆序对哪个小,选择小的那个,将这个逆序对加入答案中。如果要反转,将 x x x这一位上 1 1 1

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;

int tr[10000005][2];
vector<int> id[10000005];
int n, x, cnt = 0;
LL re[33][2];

void calc(int u, int pos) {
	int l = tr[u][0], r = tr[u][1];
	if (l) calc(l, pos - 1);
	if (r) calc(r, pos - 1);
	LL res = 0, cur = 0;
	for (int x: id[l]) {
		while (cur < id[r].size() and x > id[r][cur]) ++cur;
		res += cur;
	}
	re[pos][0] += res;
	re[pos][1] += (LL)id[l].size() * (LL)id[r].size() - res;
}

void main2() {
	cin >> n;
	for (int i = 1; i <= n; ++i) {
		cin >> x;
		int pos = 0;
		for (int j = 30; j >= 0; --j) {
			if ((x & (1 << j)) > 0) {
				if (!tr[pos][1]) tr[pos][1] = ++cnt;
				pos = tr[pos][1];
				id[pos].push_back(i);
			}
			else {
				if (!tr[pos][0]) tr[pos][0] = ++cnt;
				pos = tr[pos][0];
				id[pos].push_back(i);
			}
		}
	}
	calc(0, 30);
	LL ans = 0, res = 0;
	for (int i = 30; i >= 0; --i) {
		res += min(re[i][0], re[i][1]);
		if (re[i][1] < re[i][0]) ans += (1ll << i);
	}
	cout << res << ' ' << ans << '\n';
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	LL _;
//	cin >> _;
	_ = 1;
	while (_--) main2();
	return 0;
}

205 - Closest Equals

题目链接

考虑离线来做这道题。将所有的询问按照 r r r排序。我们考察 r r r不断向右的时候,每向右遇到一个数,如何维护答案。

以样例 1 1 1为例, [ 1 , 1 , 2 , 3 , 2 ] [1,1,2,3,2] [1,1,2,3,2]。我们最初设答案是 [ i n f , i n f , i n f , i n f , i n f ] [inf,inf,inf,inf,inf] [inf,inf,inf,inf,inf]

r = 1 r=1 r=1时, a i = 1 a_i=1 ai=1,前面没有与其相等的数,所以不会更新答案。

r = 2 r=2 r=2时, a i = 1 a_i=1 ai=1,最近一个与其相等的数是 a 1 a_1 a1。那么让答案序列变为 [ 2 − 1 , i n f , i n f , i n f , i n f ] = [ 1 , i n f , i n f , i n f , i n f ] [2-1,inf,inf,inf,inf]=[1,inf,inf,inf,inf] [21,inf,inf,inf,inf]=[1,inf,inf,inf,inf]。这样的话,在以 2 2 2为右端点的查询区间中,只要区间包含 1 1 1,那么选取答案最小值就可以选到最优答案 1 1 1

r = 3 r=3 r=3时, a i = 2 a_i=2 ai=2,前面没有与其相等的数,所以不会更新答案。

r = 4 r=4 r=4时, a i = 3 a_i=3 ai=3,前面没有与其相等的数,所以不会更新答案。

r = 5 r=5 r=5时, a i = 2 a_i=2 ai=2,最近一个与其相等的数是 a 3 a_3 a3。那么让答案序列变为 [ 1 , 2 , 2 , 2 , i n f ] [1,2,2,2,inf] [1,2,2,2,inf]。对每一个位置, 2 = 5 − 3 2=5-3 2=53,是 a 3 a_3 a3 a 5 a_5 a5的距离。我们对答案序列的每一个位置都是更新最小值,如果当前位置的答案已经小于要更新上去的答案,那就不动。

总体上来说,维护这样一个答案序列,当 r r r移到序列第 i i i个数时,每遇到一个前面遇到的一个数,假设这个数与相同的数的最近距离是 x x x,那么更新 [ 1 , i − 1 ] [1,i-1] [1,i1],将这些位置的答案与 x x x比较,选取更小的值。查询答案时,就选取答案序列中 [ l , r ] [l,r] [l,r]中的最小值作为答案。

维护这样的序列相当于区间赋值,维护最小值,用线段树可以轻松解决。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;

struct QUERY {
	int l, r, ans, id;
}q[500005];

int a[500005];
int n, m;

struct Tree {
	int l, r, tag, mn;
}t[2000005];

void pd(int ni) {
	t[ni << 1].tag = min(t[ni << 1].tag, t[ni].tag);
	t[ni << 1 | 1].tag = min(t[ni << 1 | 1].tag, t[ni].tag);
	t[ni << 1].mn = min(t[ni << 1].mn, t[ni].tag);
	t[ni << 1 | 1].mn = min(t[ni << 1 | 1].mn, t[ni].tag);
	t[ni].tag = 1e9; 
}

void pu(int ni) {
	t[ni].mn = min(t[ni << 1].mn, t[ni << 1 | 1].mn);
}

void build_tree(int ni, int l, int r) {
	t[ni].l = l; t[ni].r = r; t[ni].tag = 1e9;
	if (l == r) {
		t[ni].mn = 1e9; 
		return;
	}
	int mid = (l + r) >> 1;
	build_tree(ni << 1, l, mid);
	build_tree(ni << 1 | 1, mid + 1, r);
	pu(ni);
}

void fix(int ni, int l, int r, int x) {
	if (l <= t[ni].l and t[ni].r <= r) {
		t[ni].tag = min(t[ni].tag, x);
		t[ni].mn = min(t[ni].mn, t[ni].tag);
		return;
	}
	int mid = (t[ni].l + t[ni].r) >> 1;
	pd(ni);
	if (l <= mid) fix(ni << 1, l, r, x);
	if (mid < r) fix(ni << 1 | 1, l, r, x);
	pu(ni);
}

int query(int ni, int l, int r) {
	if (l <= t[ni].l and t[ni].r <= r) {
		return t[ni].mn;
	}
	int mid = (t[ni].l + t[ni].r) >> 1;
	pd(ni);
	int ans = 1e9;
	if (l <= mid) ans = min(ans, query(ni << 1, l, r));
	if (mid < r) ans = min(ans, query(ni << 1 | 1, l, r));
	return ans; 
}

map<int, int> mp;

void main2() {
	cin >> n >> m;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
	}
	for (int i = 1; i <= m; ++i) {
		cin >> q[i].l >> q[i].r;
		q[i].ans = 0; q[i].id = i;
	}
	sort(q + 1, q + m + 1, [](const QUERY &A, const QUERY &B) {
		return A.r < B.r;
	});
	int lst = 0;
	build_tree(1, 1, n);
	for (int i = 1; i <= m; ++i) {
		if (q[i].r > lst) {
			for (int j = lst + 1; j <= q[i].r; ++j) {
				if (!mp[a[j]]) mp[a[j]] = j;
				else {
					int x = mp[a[j]];
					fix(1, 1, x, j - x);
					mp[a[j]] = j;
				}
			}
			lst = q[i].r;
		}
		int res = query(1, q[i].l, q[i].r);
		if (res == 1e9) q[i].ans = -1;
		else q[i].ans = res;
	}
	sort(q + 1, q + m + 1, [](const QUERY &A, const QUERY &B) {
		return A.id < B.id;
	});
	for (int i = 1; i <= m; ++i) {
		cout << q[i].ans << '\n';
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	LL _;
//	cin >> _;
	_ = 1;
	while (_--) main2();
	return 0;
}

206 - Damaged Bicycle

题目链接
出自比赛:CCPC2021(哈尔滨站)
原题:CCPC2021(哈尔滨站)G题

这是一道求期望的题目,我们从终点向起点求期望。

对于每一个自行车停车点,只要这里的自行车可以骑,那么直接骑到终点一定是最优的选择。如果这里的自行车是坏的,那么我们有两种选择:一种是直接从这里走到重点,一种是走到下一个没去过的自行车停车点。

我们设从当前的停车点骑到终点的时间是 t 1 t_1 t1,从这里步行出发去往下一个停车点/终点,不考虑过程,最后到终点的期望时间是 t 2 t_2 t2,那么从这个停车点到终点的期望时间是 p i 100 × t 2 + 100 − p i 100 × t 1 \frac{p_i}{100}\times t_2 + \frac{100-p_i}{100}\times t_1 100pi×t2+100100pi×t1

易求 t 1 = d r t_1=\frac{d}{r} t1=rd d d d为当前停车点到终点的最短距离, r r r为骑行速度。

现在考虑 t 2 t_2 t2的求法。当前的状态有以下影响因素:我当前所在的停车点是哪个、从起点我已经走过哪些停车点。由于停车点的数目不超过 18 18 18,所以显然用状态压缩来存储停车点是否去过的信息。对于每一个状态,枚举下一个要走过去的、还没去过的停车点,计算走到这个停车点之后到达终点所需要的时间 T T T:包含两个部分,第一部分是从当前停车点走到那个停车点的步行时间,第二部分是从那个停车点到终点的期望时间。 t 2 t_2 t2就是所有求得的 T T T的最小值。

这样就求出了每一个状态到终点的期望时间。我们设 d p [ x ] [ S ] dp[x][S] dp[x][S]为当前在第 x x x个停车点,状态为 S S S(状态压缩存储),这种情况到终点的期望时间,那么 d p [ x ] [ S ] = p i 100 × t 2 + 100 − p i 100 × t 1 dp[x][S]=\frac{p_i}{100}\times t_2 + \frac{100-p_i}{100}\times t_1 dp[x][S]=100pi×t2+100100pi×t1 t 1 , t 2 t_1,t_2 t1,t2的求法见上述。

接下来考虑距离,题目要求的是最短的距离,所以我们从任意一个点到任意一个点这一段路径一定是走最小值。但是如果我们将每一个点都作为起点求一次最短路径,时间空间都不能接受。根据刚刚的分析,我们发现我们步行也好,骑行也好,起点仅仅出现在了每一个自行车停车点,所以我们只需要对每一个自行车停车点作为起点求解其到所有点的最短路,起点的数量很少,所以在求解最短路时采用Dijkstra,时间复杂度是 O ( 18 m log ⁡ m ) O(18m\log m) O(18mlogm)

然后我们用dfs来递归求解所有状态从终点逆着回到状态所需要的期望时间(求期望从终点逆向求解),最后就可以得到答案。dfs时,为了方便起见,我们在图中设置一个 0 0 0号点作为万能起点, 0 0 0号点的位置和 1 1 1号点重合。把 0 0 0号点当作一个没有自行车的停车点,同样进行最短路时带上这个 0 0 0号点。dfs时如果当前在这个 0 0 0号点,就不能选择骑车到终点,换句话说可以理解为 p 0 = 100 p_0=100 p0=100

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;

LL t, r, n, m, k, en = 0;
LL front[100005];
double dp[22][(1 << 20)];
LL vis[22][(1 << 20)], d[22][100005];
LL by[22], p[22];

inline LL read() {
	LL x = 0, y = 1; char c = getchar(); 
	while (c > '9' || c < '0') { if (c == '-') y = -1; c = getchar(); }
	while (c>='0'&&c<='9') { x=x*10+c-'0';c=getchar(); } return x*y;
}

struct Edge {
	LL v, w, next;
}e[200005];

void addEdge(LL u, LL v, LL w) {
	e[++en] = {v, w, front[u]};
	front[u] = en;
}

struct HeapNode {
	LL u, d;
	bool operator < (const HeapNode& rhd) const {
		return d > rhd.d;
	}
};

void Dijkstra(LL bi) {
	LL s = by[bi];
	for (int i = 1; i <= n; ++i) d[bi][i] = 1e14;
	d[bi][s] = 0; priority_queue<HeapNode> q;
	HeapNode tmp; tmp.u = s; tmp.d = d[bi][s];
	q.push(tmp);
	while (!q.empty()) {
		HeapNode x = q.top(); q.pop();
		int u = x.u; if (d[bi][u] != x.d) continue;
		for (int i = front[u]; i; i = e[i].next) {
			int v = e[i].v, w = e[i].w;
			if (d[bi][v] > d[bi][u] + w) {
				d[bi][v] = d[bi][u] + w; tmp.u = v; tmp.d = d[bi][v];
				q.push(tmp);
			}
		}
	}
}

double dfs(int x, int S) {
	if (vis[x][S]) return dp[x][S];
	vis[x][S] = 1;
	double tr = (double)d[x][n] / (double)r;
	double tw = (double)d[x][n] / (double)t;
	for (int i = 0; i < k; ++i) {
		if ((S & (1 << i)) > 0) continue;
		if (d[x][by[i + 1]] > 1e13) continue;
		tw = min(tw, ((double)d[x][by[i + 1]] / t) + dfs(i + 1, (S | (1 << i))));
	}
	if (x) dp[x][S] = ((double)p[x] / 100) * tw + ((double)(100 - p[x]) / 100) * tr;
	else dp[x][S] = tw;
	return dp[x][S];
}

void main2() {
	t = read(); r = read(); n = read(); m = read();
	for (int i = 1; i <= n; ++i) {
		front[i] = 0;
	}
	for (int i = 1; i <= m; ++i) {
		int u, v, w;
		u = read(); v = read(); w = read();
		addEdge(u, v, w); addEdge(v, u, w);
	}
	k = read();
	by[0] = 1;
	for (int i = 1; i <= k; ++i) {
		by[i] = read(); p[i] = read();
	}
	for (int i = 0; i <= k; ++i) {
		Dijkstra(i);
	}
	if (d[0][n] > 1e13) {
		printf("-1");
		return;
	}
	printf("%.7lf", dfs(0, 0));
}

int main() {
	LL _;
//	cin >> _;
	_ = 1;
	while (_--) main2();
	return 0;
}

207 - 拆方块

题目链接

尝试计算每一列最底部的方块会在第几次撤走。这样想的原因是,这一列最底部的方块一定是这一列最后一批被拿走的。

这一列最底部的方块能否被拿走、第几批被拿走,与这一列的高度和两侧方块的高度有关。若旁边一列需要 x x x次可以全部拿走,这一列的高度如果为 h h h,那么只需要看旁边列的拿走次数+1和这一列的高度的最小值即可。

由于我们一次只能考虑一侧,所以我们采用正反各走一次的方法,先从左到右判断一次,这时比较的是左边一列全部拿走的次数+1、这一列的高度、右边一列的高度+1;再从右到左判断一次,这时比较的就是右边一列全部拿走的次数+1、这一列的高度、左边一列的高度+1。最后每一列全部拿走的次数就是从左到右和从右到左求得的这一列的值的最小值。最后取每一列的最大即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL _; 
int n;
int a[100050], ans[100050], ans2[100050];

void main2() {
	cin >> n;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
		ans[i] = ans2[i] = 0;
	}
	ans[1] = ans[n] = 1;
	ans2[1] = ans2[n] = 1;
	a[n + 1] = 0;
	for (int i = 2; i < n; ++i) {
		ans[i] = min({a[i], ans[i - 1] + 1, a[i + 1] + 1});
	}
	for (int i = n - 1; i >= 2; --i) {
		ans2[i] = min({a[i], ans2[i + 1] + 1, a[i - 1] + 1});
	}
	int mx = 0;
	for (int i = 1; i <= n; ++i) {
		mx = max(min(ans[i], ans2[i]), mx);
	}
	cout << mx << "\n";
}

int main() {
//	freopen("Fin.in", "r", stdin);
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	
//	cin >> _;
	_ = 1; 
	while (_--) main2();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值