2024 1.30 模拟考解析

文章详细解析了四道USACO竞赛中的编程题目,涉及贪心算法、优先队列、单调栈的应用,以及博弈论中的策略分析。第一题利用贪心策略找到牛年之间的最大间隔,第二题通过单调栈计算数组操作的分数,第三题讨论了质数影响的博弈问题,第四题是关于最短路径的求解。
摘要由CSDN通过智能技术生成

题目链接

T1:P7412 [USACO21FEB] Year of the Cow S

T2:P8094 [USACO22JAN] Cow Frisbee S

T3:P8901 [USACO22DEC] Circular Barn S

T4:P1849 [USACO12MAR] Tractor S

分析

T1

由于只能传送到牛年,所以求出所有年后面一个牛年,去重,在算出每个牛年之间的时间差距,贪心取距离最大的 k − 1 k-1 k1 个传送跳过。

#include <bits/stdc++.h>
#define debug puts("Y")
#define inf 0x3f3f3f3f
#define int long long

using namespace std;
using PII = pair <int, int>;

const int N = 5 * 1e5 + 5;
int n, k, a[N], t[N];
set <int> s;
vector <int> v;
priority_queue <int> q;//维护大根堆

signed main() {
	cin >> n >> k;
	for (int i = 1; i <= n; i ++) {
		cin >> a[i];
		t[i] = (a[i] / 12) + 1;
	}
	sort(t + 1, t + n + 1);
	int siz = unique (t + 1, t + n + 1) - (t + 1);//去重
	for (int i = 1; i <= siz; i ++) {
		if (t[i] - t[i - 1] - 1) {
			q.push(t[i] - t[i - 1] - 1);//-1方便后面计算答案
		}
	}
	int ans = siz * 12;//每个段至少要12年
	for (; !q.empty() && k > 1;) {//将距离最大的k-1段路跳过
		k --;
		q.pop();
	}
	for (; !q.empty(); q.pop()) {//计算答案
		ans += q.top() * 12;
	}
	cout << ans;
  return 0;
}

T2

考试时挂的真冤,数组开小导致 100 100 100 → \to 27 27 27 分,Rank 14 → 14\to 14 Rank 35 35 35

维护两个单调栈,分别求出每个数左边第一个比他大的和右边第一个比他大的,两个相减再加一即可,如果这个数左边没有比他大的,或右边没有比他大的,那么直接跳过,还有一种特殊情况:

5 1 1 1 5

这种情况 1 ∼ 5 1\sim5 15 这段区间会被算 3 3 3 次,所以二维 map 判重即可。

# include <bits/stdc++.h>
# define int long long

using namespace std;
using PII = pair <int, int>;

const int N = 300005;
int n, h[N], L[N], R[N];

map <int, map <int, int> > mp;

void get_L() {
  stack <int> stk;
  for (int i = n; i >= 1; i --) {
    for (; !stk.empty() && h[i] > h[stk.top()];) {
      L[stk.top()] = i;
      stk.pop();
    }
    stk.push(i);
  }
} 
void get_R() {
  stack <int> stk;
  for (int i = 1; i <= n; i ++) {
    for (; !stk.empty() && h[i] > h[stk.top()];) {
      R[stk.top()] = i;
      stk.pop();
    }
    stk.push(i);
  }
  while (!stk.empty()) {
    R[stk.top()] = n + 1;
    stk.pop();
  }
} 

signed main () {
  cin >> n;
  for (int i = 1; i <= n; i ++) {
    cin >> h[i];
  }
  get_L(), get_R();
  int cnt = (n - 1) * 2;//相邻两个点之间没有其他数可以互传
  for (int i = 1; i < n; i ++) {
    mp[i][i + 1] = true;
  } 
  for (int i = 1; i <= n; i ++) {
    if (mp[L[i]][R[i]] || L[i] == 0 || R[i] == n + 1) {
      continue;
    }
    mp[L[i]][R[i]] = true;
    cnt += R[i] - L[i] + 1;
  }
  cout << cnt;
  return 0;
}

估计还有更简单的写法……

T3

一道质量很高的博弈论题。

发现,对于一个数,如果其除 4 4 4 余数为 0 0 0,那么后手必赢,否则先手必赢,不难证明,一个除 4 4 4 余数为 0 0 0 的数不能减去一个质数或 1 1 1,来到达一个后手必赢的状态。

那么对于一个在当前房间必赢的人,他肯定希望局数尽可能的少,让当前房间他赢的局数比他在后面房间输的局数少,对于一个在当前房间必输的人,他肯定希望局数尽可能的多,让他在后面房间赢的局数比他在当前房间输的局数少。

接下来分类讨论:

  • 如果 a i a_i ai 1 1 1 那么局数就为 1 1 1
  • 如果 a i % 4 = 0 a_i \%4=0 ai%4=0,那么局数为 a i 4 \large\frac{a_i}{4} 4ai + 1 +1 +1,每次两人拿的总和都为 4 4 4
  • 否则,先手肯定希望拿一个尽可能大的质数,维护 p i p_i pi 表示余数为 i i i 的最大质数,那么局数为 a i − p a i % 4 4 \Large\frac{a_i-p_{a_i\%4}}{4} 4aipai%4 + 1 +1 +1

维护 m i n n 1 minn1 minn1 m i n n 2 minn2 minn2 表示先手和后手输的最小局数,判断即可,注意如果相等,要判断谁输的房间编号小。

赛时没想到局数这一点,喜提 30 30 30 分。。。

#include <bits/stdc++.h>
#define debug puts("Y")
#define inf 0x3f3f3f3f

using namespace std;
using PII = pair <int, int>;

const int N = 5 * 1e6 + 5;

int T, n, a[N], v[N], p[N];
bool vis[N];

void init () {
	memset (vis, 1, sizeof vis);
	v[1] = p[1] = 1;
	for (int i = 2; i <= N - 5; i ++) {//因为在循环里维护局数所以这里得循环完
		if (vis[i]) {
			p[i % 4] = i;
			for (int j = i << 1; j <= N - 5; j += i) {
				vis[j] = 0;
			}
		}
		if (i % 4 == 0) {
			v[i] = i / 4 + 1;
		} else {
			v[i] = (i - p[i % 4]) / 4 + 1;
		}
	}
}

int main() {
	init();
	for (cin >> T; T; T --) {
		cin >> n;
		for (int i = 1; i <= n; i ++) {
			cin >> a[i];
		}
		int minn1 = 1e9, minn2 = 1e9;
		int room1, room2;
		for (int i = 1; i <= n; i ++) {
			if (a[i] % 4 != 0) {
				if (v[a[i]] < minn2) {
					minn2 = v[a[i]];
					room2 = i;
				}
			} else {
				if (v[a[i]] < minn1) {
					minn1 = v[a[i]];
					room1 = i;
				}
			}
		}
		if (minn1 < minn2) {
			cout << "Farmer Nhoj\n";
		} else if (minn1 > minn2){
			cout << "Farmer John\n";
		} else {
			if (room1 < room2) {
				cout << "Farmer Nhoj\n";
			} else {
				cout << "Farmer John\n";
			}
		}
	}
  return 0;
}

T4

不知道为什么是第 4 4 4 题……

考虑建边,把点权转化到边权上,对于一条 a x , y → a n x , n y a_{x,y}\to a_{nx,ny} ax,yanx,ny 的边,如果 a n x , n y a_{nx,ny} anx,ny 有稻草堆就将边权赋值为 1 1 1 否则为 0 0 0,在跑一遍最短路或双端队列广搜都可以,注意由于是坐标系所以判断越界时, ( x , 0 ) , ( 0 , x ) , ( x , 1001 ) , ( 1001 , x ) (x,0),(0,x),(x,1001),(1001,x) (x,0),(0,x),(x,1001),(1001,x) 这四种情况都是合法的,在往深走没有意义。

由于不卡 spfa 所以跑 spfa 就行。

# include <bits/stdc++.h>
#define int long long

using namespace std;
using PII = pair <int, int>;

const int N = 1e3 + 5, kD[][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
int n, sx, sy, a[N][N], dis[N][N], vis[N][N];

void spfa() {
	memset(dis, 0x3f, sizeof dis);
	queue <pair <int, int> > q;
	q.push({sx, sy}), dis[sx][sy] = a[sx][sy];
	for (; !q.empty(); q.pop()) {
		PII x = q.front();
		vis[x.first][x.second] = 0;
		for (int i = 0; i < 4; i ++) {
			int nx = x.first + kD[i][0], ny = x.second + kD[i][1];
			if (nx < 0 || nx > 1e3 + 1 || ny < 0 || ny > 1e3 + 1) {
				continue;
			}
			if (dis[x.first][x.second] + (a[nx][ny] == 1) < dis[nx][ny]) {
				dis[nx][ny] = dis[x.first][x.second] + (a[nx][ny] == 1);
				if (!vis[nx][ny]) {
					vis[nx][ny] = 1;
					q.push({nx, ny});
				}
			}
		}
	}
}

signed main() {
	cin >> n >> sx >> sy;
	for (int i = 1; i <= n; i ++) {
		int x, y;
		cin >> x >> y;
		a[x][y] = 1;
	}
	spfa();
	cout << dis[0][0];
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值