ICPC 北美Mid Central 2019 Regional

B - Commemorative Race

  • 题意:给一个\(n\)个点的\(DAG\),求在最长路上,删除一条边后,从某个入点到达出点的最长路最短是多少。
  • 题解:首先可以先求出每个点到达出点的最长距离和次长距离,然后枚举最长路,在最长路上删边。因为起点不固定,所以建一个超级源点,然后从这个超级源点连向每个点一条边。这就变成了,从起点到出点最长路上删一条边使得删后的最长路最小,求删除后的最长路。可以设一个一维数组\(Maxlen\)\(Maxlen_{i}\)表示了从\(i\)到出点的最长路,可以通过每次向下\(dfs\)返回值然后取\(max\)就行。并且当\(dfs2\)模拟沿着最长路走的时候,就只需\(Maxlen[u] = Maxlen[v] + 1\)看看是否成立,如果成立,那么\(v\)\(u\)的下一个的最长路,但是会有多个,如果是个最长路的分叉,那么这里对答案不做贡献,无影响,但是不知道这条分叉的最长路走下去啥情况,不仅要走原来的路,还要走这条最长路的分岔口继续走下去,不过这个分岔口不影响答案。用\(ans\)数组,记录当前答案,设\(dfs2\)沿着最长路中走过的距离为\(len\),那么记录的答案就是之前沿着走过的,加上当前点的除最长路以外的最长的路径长度\(Maxlen2\)的长度,意味着,当前所记录的结果是,沿着最长路,从起点走到这里的路径长度,加上,从这里走的除了最长路以外的最长的路径长度,我们记录完成,然后顺着\(ans\)数组取\(min\)即可.注意,分岔口不能算,\(ans\)初始是\(Maxlen_{0}\),即应该是有多条长度一样并且不交叉的最长路,切记初始的时候图是有个超级源点,最后求的是从起点\(0\)到终点的答案长度,所以我们要求的应该\(ans-1\).
  • 代码:
#include <iostream>
#include <vector>
using namespace std;
const int N = 1e5 + 99;
vector<int>G[N];
int vis[N];
int Max_len[N];
int Max_len2[N]; 
int cnt[N];
int ans[N];
int dfs(int u) {
	if (vis[u] == 1) {
		return Max_len[u];
	}
	vis[u] = 1;
	int tmp = 0;
	for (auto v : G[u]) {
		tmp =  dfs(v) + 1;
		if (tmp > Max_len[u]) {
			Max_len2[u] = Max_len[u];
			Max_len[u] = tmp;
		} else if (tmp > Max_len2[u]){
			Max_len2[u] = tmp;
		}
	}
	return Max_len[u];
}
void dfs2(int u, int len) {
	if (vis[u] == 2) return;
	vis[u] = 2;
	for (auto v : G[u])
	{
		if (Max_len[u] == Max_len[v] + 1) {
			dfs2(v, len + 1);
			cnt[len]++;
			ans[len] = Max_len2[u] + len;
		}
	}
}
int main() {
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; i++)G[0].push_back(i);
	for (int i = 1, u, v; i <= m; i++) {
		cin >> u >> v;
		G[u].push_back(v);
	}
	dfs(0);
	dfs2(0, 0);	
	int Ans = Max_len[0] ;
	for (int i = 1; i <= n; i++)if (cnt[i] == 1 )  {
		Ans = min(Ans, ans[i]);
	}
	cout << Ans - 1 << endl;
}

C - Convoy

  • 题意: 给\(n\)个人,\(k\)辆车,一辆车最多坐\(4\)人,然后每个人去目的地花费一定的时间,要求出最少花费的时间。
  • 题解: 一开始乱想,其实发现过程中无法表示,所以应该是二分时间,\(check\)函数就是,在时间里让前\(k\)快的司机一直拉人,最后计算一下是否可以把所有人都拉走。
  • 代码:
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll N = 200009;
ll a[N];
ll n, k;
bool check(ll mid) {
	ll cnt = 0;
	for (ll i = 1; i <= min(k, n); i++) {
		ll now = mid;
		if (mid >= a[i]) {
			cnt += 5;
			now -= a[i];
		} else break;
		ll cost = 2 * a[i];
		cnt += now / cost * 4;
		if (cnt >= n)return 1;
	}
	return 0;
}
signed main() {
	cin >> n >> k;
	ll l = 0, r = 0x7fffffffffffffff;
	for (ll i = 1; i <= n; i++) {
		cin >> a[i];
	}
	sort(a + 1, a + 1 + n); 
	while (l < r) {
		ll mid = (l + r) >> 1;
		if (check(mid)) {
			r = mid;
		} else {
			l = mid + 1;
		}
	}
	cout << l << endl;
}

F - Dragon Ball I

  • 题意:给出\(n\)个点, \(m\)条无向边, \(1 <= m,n<= 200 000\),然后给出\(7\)个位置,求从\(1\)出发依次全部经过这\(7\)个位置的最短路径。
  • 题解:dij感觉都能想到,并且不能从\(1\)点贪心的去走,这个也好想,并且还得知道要得有\(O(A^{7}_{7})\)的常数,dij的复杂度是\(O(nlogn)\),所以乘起来不大行,想到用数组分别记录从这7个点开始的dij的\(dis\)数组,并且排列数用next_pumutation很好用。
  • 代码:
#include <iostream>
#include <cstring>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;

typedef long long ll;
const ll N = 200009;
const ll inf = 0x3f3f3f3f3f3f3f3f;
struct edge {
	ll v, w;
};
vector<edge>G[N];
struct Node {
	ll pos,dis;
	bool operator<(Node rhs)const {
		return rhs.dis  < dis;
	}
};
ll n, m;
priority_queue<Node>pq;
ll d[10][N];
bool vis[N];
void dij(ll s, ll rk) {
	while (!pq.empty()) pq.pop();
	for (int i = 1; i <= n; i++) {
		d[rk][i] = inf;
	}
	memset(vis, 0, sizeof vis);
	d[rk][s] = 0;
	pq.push({s, d[rk][s]});
	while (!pq.empty()) {
		auto now = pq.top();
		pq.pop();
		ll u = now.pos;
		if (vis[u])continue;
		vis[u] = 1;
		for (auto t : G[u]) {
			if (d[rk][t.v] > d[rk][u] + t.w) {
				d[rk][t.v] = d[rk][u] + t.w;
				pq.push({t.v, d[rk][t.v]});
			}
		}
	}
}
ll a[9]{};
ll ans = 0x7fffffffffffffff;
void dfs(ll len,ll now, ll pre) {
	if (len == 7) {
		ans = min(ans, now);
		return;
	}
	for (ll i = 1; i <= 7; i++) {
		if (vis[i] == 1) {
			continue;
		}
		vis[i] = 1;
		dfs(len + 1, now + d[pre][a[i]], i);
		vis[i] = 0;
	}
}
signed main() {
	ios::sync_with_stdio(0);
	cin >> n >> m;
	for (ll i = 1; i <= m; i++) {
		ll u, v, w;
		cin >> u >> v >> w;
		G[u].push_back({v, w});
		G[v].push_back({u, w});
	}
	a[0] = 1;
	for (ll i = 1; i <= 7; i++) cin >> a[i];
	for (ll i = 0; i <= 7; i++) dij(a[i], i);
	int b[] = {0, 1, 2, 3, 4, 5, 6, 7};
	do {
		ll now = 0;
		for (int i = 1; i <= 7; i++) {
			now += d[b[i-1]][a[b[i]]];
			
		}
		ans = min(ans, now);
	} while (next_permutation(b + 1, b + 1 + 7));
	cout << ans << endl;
} 

J - Sum and Product

  • 题意:给\(n < 2 \times 10^{5}\)的数量的数,数的大小范围在\(int32\)内,求一共有多少个连续序列,使得序列数字之和与数字之积相等。
  • 题解:最暴力的办法就是\(n ^ {2}\)暴力跑,但是复杂度在那里必然超时。于是分析得\(1\)很特殊,先设乘积为\(prd\), 和为\(sum\),\(1\)是唯一一种能延缓乘积增加,并且使\(sum\)增加的方法。还可发现,除了\(1\)就算是最小的\(2\),当连续的\(2\)用不了多长就会爆,更何况更大的数,所以如果是处理连续的大于\(1\)的数的话,最差最差是\(O(n\log n)\)处理,发现很客观,那么最烦的\(1\)咋整?发现,如果遇到\(1\),可以分析此时\(sum\)\(prd\)的性质,发现\(prd\)是不会变化的,但是\(sum\)就不一样,它一定是线性\(+1\)的数量,那么很显然,如果是一段连续的\(1\),设连续的\(1\)的数量为\(cnt1\)要确定是否存在答案,就看是否当前的和加上\(1\)的数量是否大于乘积的同时,当前和要小于乘积,这样一定在\(1\)的区间里面对答案造成贡献,即\(sum + cnt1 >= prd\)并且 \(sum < prd\),这样两个\(for\)循环嵌套就可以算完,最差的时间复杂度是\(O(2· n ·\log n )\),就是那种一\(2\)隔着一个\(1\).
  • 代码:
#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;
const int N = 2e6 + 99;
typedef long long ll;
int a[N];
ll endOne[N];
ll usum[N];
signed main() {
	ll n;
	ios::sync_with_stdio(0);
	cin >> n;
	for (ll i = 1; i <= n; i++) {
		cin >> a[i];	
	}
	for (ll i = n; i >= 1; i--) {
		usum[i] += usum[i+1] + a[i];
		if (a[i] == 1) {
			endOne[i] = max(i, endOne[i+1]);
		}
		else endOne[i] = -1;
	}
	ll ans = 0;
	for (ll i = 1;i <= n; i++) {
		ll prd = a[i];
		ll sum = a[i];
		for (ll j = i + 1; j <= n; j++) {
			if (a[j] == 1) {
				ll cnt1 = endOne[j] - j + 1;
				j = endOne[j];
				if (sum < prd && sum + cnt1 >= prd) {
					ans++;
				} 
				sum += cnt1;
				continue;
			}
			prd *= a[j];
			sum += a[j];
			if (sum == prd) {
				ans++;	
			}
			if (usum[i + 1] + sum < prd)break;
		}
	}
	cout << ans << endl;
}

H - Farming Mars

  • 题意:给\(n < 10000\)的数量的小数,精确到\(6\)位,给\(m <= n\)个询问,询问\(l ~ r\)区间内相同数的数量的最大值是否不小于 \(\lfloor \frac{(r - l + 1)}{2} + 1 \rfloor\)
  • 题解:看到\(10000\)就想想\(n_{2}\)的暴力,毕竟少了个\(0\),还是经验不足啊,不敢写。给区间大致排个序然后双指针跑记录数量就行。
  • 代码:
#include <iostream>
#include <unordered_map>
#include <algorithm>
#include <queue>
#include <map>
using namespace std;
int cnt[15000000];
const int N = 10009;
struct node {
	int l, r, id;
}q[N];
bool cmp(node a, node b) {
	if (a.l == b.l)return a.r < b.r;
	return a.l < b.l;
}
int a[N];
int ans[N];
signed main() {
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		double x;
		cin >> x;		
		x *= 1000000;
		a[i] = x;
	}
	for (int i = 1; i <= m; i++) {
		int l, r;
		cin >> l >> r;
		q[i].l = l, q[i].r = r;
		q[i].id = i;
	}
	sort(q + 1, q + 1 + m, cmp);
	int l = 1, r = 1;
	cnt[a[l]]++;
	for (int i = 1; i <= m; i++) {
		int L = q[i].l;
		int R = q[i].r;
		int id = q[i].id;
		int Max = -1;
		int cmp = (R - L + 1)/ 2 + 1;
		while ( l < L) {
			Max = max(--cnt[a[l++]],Max); 
		}while (l > L) {
			Max = max(++cnt[a[--l]],Max); 
		}while (r > R) {
			Max = max(--cnt[a[r--]],Max); 
		} while ( r < R) {
			Max = max(++cnt[a[++r]],Max); 
		}
		Max = -1;
		for (int i = L; i <= R; i++) {
			Max = max(Max, cnt[a[i]]);
		}
		if (Max >=  cmp) {
			ans[id] = 1;
		}
	}
	for (int i =1;i  <= m; i++) {
		if (ans[i])cout << "usable\n";
		else cout << "unusable\n";
	}
}
posted @ 2021-01-27 18:20  u_yan  阅读(265)  评论(0编辑  收藏  举报