USACO21DEC B&S

前言

无语。。。
状态贼差,每一题就要交四五次。
Bronze T1 4 次,T2 9 次。
Sliver T2,T3 都是十多次,T1 甚至就没做出来。
(结果最后发现是忘了排序。。。)

Bronze

A:Lonely Photo

题目链接:luogu P7993

给你一个 01 串,然后问你有多少个长度大于等于三的子串满足 0 的个数或 1 的个数为 1。

就直接枚举每个 1 和 0 的位置搞它可以贡献的范围。

#include<cstdio>
#include<iostream>
#define ll long long

using namespace std;

int n, a[500005], nxt[500005][2];
int lst[2];
char c;
ll ans;

ll clac(int L, int R) {
	if (R - L + 1 < 3) return 0;
	return 1ll * (R - L + 1 - 3 + 1) * (R - L + 1 - 3 + 1 + 1) / 2;
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		c = getchar(); while (c != 'G' && c != 'H') c = getchar();
		if (c == 'G') a[i] = 1;
	}
	
	lst[0] = lst[1] = n + 1; nxt[n + 1][0] = nxt[n + 1][1] = n + 1;
	for (int i = n; i >= 1; i--) {
		nxt[i][0] = lst[0]; nxt[i][1] = lst[1];
		lst[a[i]] = i;
	}
	
	ans = clac(1, n);
	for (int i = 1; i <= n; i++) {
		int L;
		if (a[i]) {
			L = max(nxt[i][1], nxt[nxt[i][0]][0]);
		}
		else {
			L = max(nxt[i][0], nxt[nxt[i][1]][1]);
		}
		if (L - i + 1 < 3) L = i + 2 - 1;
		ans -= max(0, n - L + 1);
		
		int R = nxt[i][a[i] ^ 1] - 1;
		if (R - i + 1 >= 3) ans -= R - i + 1 - 3 + 1;
	}
	
	printf("%lld", ans);
	
	return 0;
}

B:Air Cownditioning

题目链接:luogu P7994

给你一个数列,然后你每次可以选一个区间加 1 或减 1,然后问你最少要多少次操作把所有数变成 0。

就会发现加减抵消和直接只加只减的效果一样,那就直接暴力推过去搞即可。

#include<cstdio>
#define ll long long

using namespace std;

int n, a[100001], x, b[100001];
ll ans;

void slove(int *f) {
	int now = 0;
	for (int i = 1; i <= n; i++) {
		if (f[i] < now) now = f[i];
		if (f[i] > now) ans += f[i] - now, now = f[i];
	}
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &x); a[i] -= x;
	}
	
	for (int i = 1; i <= n; i++)
		if (a[i] < 0) {
			b[i] = -a[i];
			a[i] = 0;
		}
	
	slove(a); slove(b);
	
	printf("%lld", ans);
	
	return 0;
}

C:Walking Home

题目链接:luogu P7995

给你一个矩阵,你要从左上角走到右下角,然后有一些地方不可以走,你只能向右或向下走,而且你走的方向的改变次数不能超过 K K K,然后问你有多少种走的方案。

不难看出是不会往回走,那就直接 dfs / bfs 即可。

#include<queue>
#include<cstdio>
#include<cstring>
#define ll long long

using namespace std;

int T, n, k, dx[2] = {0, 1}, dy[2] = {1, 0};
ll f[51][51][4][2], ans, gogo[51][51][4][2];
bool in[51][51];
char c;
queue <int> q;

bool ck(int x, int y) {
	if (x < 1 || x > n) return 0;
	if (y < 1 || y > n) return 0;
	if (!in[x][y]) return 0;
	return 1;
}

int main() {
	scanf("%d", &T);
	while (T--) {
		scanf("%d %d", &n, &k);
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= n; j++) {
				c = getchar(); while (c != '.' && c != 'H') c = getchar();
				if (c == '.') in[i][j] = 1; else in[i][j] = 0;
			}
		memset(f, 0, sizeof(f)); memset(gogo, 0, sizeof(gogo));
		
		if (!in[1][1] || !in[n][n]) {
			printf("0\n"); continue;
		}
		
		ans = 0;
		while (!q.empty()) q.pop();
		for (int i = 0; i < 2; i++) {
			if (!ck(1 + dx[i], 1 + dy[i])) continue;
			f[1 + dx[i]][1 + dy[i]][0][i] = 1;
			q.push(1 + dx[i]), q.push(1 + dy[i]), q.push(0), q.push(i);
		}
		while (!q.empty()) {
			int x = q.front(); q.pop(); int y = q.front(); q.pop();
			int kk = q.front(); q.pop(); int p = q.front(); q.pop();
			if (gogo[x][y][kk][p]) continue;
			gogo[x][y][kk][p] = 1;
			if (x == n && y == n) {
				ans += f[x][y][kk][p];
				continue;
			}
			if (ck(x + dx[p], y + dy[p])) {
				f[x + dx[p]][y + dy[p]][kk][p] += f[x][y][kk][p];
				q.push(x + dx[p]); q.push(y + dy[p]); q.push(kk); q.push(p);
			}
			if (kk == k) continue;
			kk++; p ^= 1;
			if (ck(x + dx[p], y + dy[p])) {
				f[x + dx[p]][y + dy[p]][kk][p] += f[x][y][kk - 1][p ^ 1];
				q.push(x + dx[p]); q.push(y + dy[p]); q.push(kk); q.push(p);
			}
		}
		
		printf("%lld\n", ans);
	}
	
	return 0;
}

Sliver

A:Closest Cow Wins

题目链接:luogu P7990

有一个数轴,给你 K K K 个得分点的位置和分数,然后别人在 M M M 个整数点放了一些据点。
然后你可以放 N N N 个据点,可以放在小数位置。然后判断一个得分点被谁占领是看它距离谁的据点近,如果距离就是别人占领。

考虑到我们可以按敌人的据点把数轴分成若干个部分,这些位置顶多放两个(把据点里面的点都包围住),那两个就是全部的贡献,一个的话我们可以通过滑动窗口单调队列得到最大的贡献。
那我们可以把放第一个的求出来,放第二个的就是全部的贡献减第一个的贡献。
然后用个堆搞前 n n n 个即可。
(记得 f f f 要排序!!!!!!)

#include<queue>
#include<cstdio>
#include<iostream>
#include<algorithm>
#define ll long long

using namespace std;

struct node {
	ll p, t;
}a[800001];
ll k, m, n, tot, ww, sum;
ll f[800001], tott;
ll all[800001], answer;
priority_queue <pair<ll, ll> > q;

bool cmp(node x, node y) {
	return x.p < y.p;
}

int main() {
	scanf("%lld %lld %lld", &k, &m, &n);
	for (ll i = 1; i <= k; i++) {
		scanf("%lld %lld", &a[i].p, &a[i].t);
	}
	sort(a + 1, a + k + 1, cmp);
	for (ll i = 1; i <= m; i++) scanf("%lld", &f[i]);
	sort(f + 1, f + m + 1);
	f[0] = -1e9; f[m + 1] = 2e9;
	
	ll l = 1;
	for (ll i = 1; i <= m + 1; i++) {
		ll r = l - 1; while (r + 1 <= k && a[r + 1].p <= f[i]) r++;
		if (l > r) continue;
		ll L = l;
		sum = 0; tot = 0; ww = 0;
		for (ll R = l; R <= r; R++) {
			tot += a[R].t; sum += a[R].t;
			while (L <= R && (a[R].p - a[L].p) * 2 >= f[i] - f[i - 1]) sum -= a[L].t, L++;
			ww = max(ww, sum);
		}
		q.push(make_pair(ww, ++tott)); all[tott] = tot;
		l = r + 1;
		if (l > k) break;
	}
	
	answer = 0;
	for (ll i = 1; i <= n; i++) {
		if (q.empty()) break;
		pair <ll, ll> tmp = q.top(); q.pop();
		answer += tmp.first;
		if (tmp.second) q.push(make_pair(all[tmp.second] - tmp.first, 0));
	}
	printf("%lld", answer);
	
	return 0;
}

B:Connecting Two Barns

题目链接:luogu P7991

有个无向图有 n n n 个点, m m m 条边,然后你可以加至多 2 2 2 条边,连接 i , j i,j i,j 的边的费用是 ( i − j ) 2 (i-j)^2 (ij)2
然后问你使得 1 , n 1,n 1,n 连通的最小费用。

你考虑到如果是不连边就是 1 , n 1,n 1,n 在同一个连通块。如果是连一条边就是 1 , n 1,n 1,n 分别所在的连通块连接。
然后如果是两条边你就枚举中间中转的连通块搞。
然后两个连通块连边的最小费用的话你可以分别看 1 , n 1,n 1,n 所在连通块到别的连通块的。
然后你可以把 1 / n 1/n 1/n 的连通块的点都放进去一个数组里面排好序,然后你就直接 lower_bound 找到一个点附近的两个连接点,这样来算。

#include<queue>
#include<cstdio>
#include<vector>
#include<iostream>
#include<algorithm>
#define ll long long

using namespace std;

ll T, n, m, x, y;
ll fa[500005];
ll ans, LL, RR;
ll L[500005], R[500005];
vector <ll> son[500001];

ll find(ll now) {
	if (fa[now] == now) return now;
	return fa[now] = find(fa[now]);
}

int main() {
	scanf("%lld", &T);
	while (T--) {
		scanf("%lld %lld", &n, &m);
		for (ll i = 1; i <= n; i++) {
			fa[i] = i;
		}
		for (ll i = 1; i <= m; i++) {
			scanf("%lld %lld", &x, &y);
			ll X = find(x), Y = find(y);
			if (X == Y) continue; fa[X] = Y;
		}
		
		if (find(1) == find(n)) {
			printf("0\n"); continue;
		}
		
		for (ll i = 1; i <= n; i++) {
			if (find(i) == find(1)) L[++LL] = i;
			if (find(i) == find(n)) R[++RR] = i;
			son[find(i)].push_back(i);
		}
		
		ans = 1e15;
		for (ll i = 1; i <= n; i++) {
			if (find(i) != i) continue;
			ll ln = 1e9, rn = 1e9;
			for (ll j = 0; j < son[i].size(); j++) {
				ll l = lower_bound(L + 1, L + LL + 1, son[i][j]) - L;
				if (l != 1) ln = min(ln, son[i][j] - L[l - 1]);
				if (l != LL + 1) ln = min(ln, L[l] - son[i][j]);
				ll r = lower_bound(R + 1, R + RR + 1, son[i][j]) - R;
				if (r != 1) rn = min(rn, son[i][j] - R[r - 1]);
				if (r != RR + 1) rn = min(rn, R[r] - son[i][j]);
			}
			ans = min(ans, ln * ln + rn * rn);
		} 
		
		printf("%lld\n", ans);
		
		for (ll i = LL; i >= 1; i--) L[i] = 0;
		for (ll i = RR; i >= 1; i--) R[i] = 0;
		LL = RR = 0;
		for (int i = 1; i <= n; i++) son[i].clear();
	}
	
	return 0;
} 

C:Convoluted Intervals

题目链接:luogu P7992

给你一些二元组 ( a i , b i ) (a_i,b_i) (ai,bi),然后这些数都在 0 ∼ M 0\sim M 0M,然后要你对于每个 0 ∼ 2 M 0\sim 2M 02M 的数 k k k 求有多少个数对 ( i , j ) (i,j) (i,j) 满足 a i + a j ⩽ k ⩽ b i + b j a_i+a_j\leqslant k\leqslant b_i+b_j ai+ajkbi+bj

你考虑 a a a b b b 分开处理,它就相当于一个前缀和,用 b b b 的答案减去 a − 1 a-1 a1 的答案。
然后如果计算的话,我们发现 M M M 很小,考虑直接枚举点对的数值,然后进行统计,然后前缀和一下就有结果了。

#include<cstdio>
#define ll long long

using namespace std;

int n, m, a[200001], b[200001];
ll ans[10005], numa[5001], numb[5001];

int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) {
		scanf("%d %d", &a[i], &b[i]);
		numa[a[i]]++; numb[b[i]]++;
	}
	
	for (int i = 0; i <= m; i++)
		for (int j = 0; j <= m; j++) {
			ans[i + j] += numb[i] * numb[j];
			if (i || j) ans[i + j - 1] -= numa[i] * numa[j];
		}
	for (int i = 2 * m - 1; i >= 0; i--) ans[i] += ans[i + 1];
	for (int i = 0; i <= 2 * m; i++) {
		printf("%lld\n", ans[i]);
	}
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值