题目链接
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 k−1 个传送跳过。
#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
1∼5 这段区间会被算
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} 4ai−pai%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,y→anx,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];
}