本期题目依旧不是很难。
C题数学,D题DP,E题宽搜,F题是折半搜索模板题。
题意里有三种操作,我们记为o1,o2,o3
首先需要了解,如果奇偶性质相同,一定可以在两步内通过对角线移动到达另一个点。即 ( x 1 + y 1 ) % 2 = ( x 2 + y 2 ) % 2 (x1+y1) \% 2 =(x2+y2) \% 2 (x1+y1)%2=(x2+y2)%2
然后显然,最大需要3步到达,因为可以通过o3操作改变奇偶性。需要判断的是能否在2步内抵达。
0步和1步比较简单。考虑2步的情况。
前两种操作为对角线移动,一开始也许会考虑先进行两种对角线移动,然后进行o3操作,但是程序不太好写。实际上三种操作是可交换的,因此可以先进行第三种操作枚举,然后判断对角线能否到。
最后居然没有通过case after contest,原因是可以进行两次o3操作。
# -*- coding: utf-8 -*-
# @time : 2023/6/2 13:30
# @file : atcoder.py
# @software : PyCharm
import bisect
import copy
import sys
from itertools import permutations
from sortedcontainers import SortedList
from collections import defaultdict, Counter, deque
from functools import lru_cache, cmp_to_key
import heapq
import math
sys.setrecursionlimit(100010)
def m0(x0, y0, x1, y1):
return x0 + y0 == x1 + y1
def m1(x0, y0, x1, y1):
return x0 - y0 == x1 - y1
def m2(x0, y0, x1, y1):
return abs(x0 - x1) + abs(y0 - y1) <= 3
def main():
items = sys.version.split()
fp = open("in.txt") if items[0] == "3.10.6" else sys.stdin
x0, y0 = map(int, fp.readline().split())
x1, y1 = map(int, fp.readline().split())
if x0 == x1 and y0 == y1:
print(0)
return
if m0(x0, y0, x1, y1) or m1(x0, y0, x1, y1) or m2(x0, y0, x1, y1):
print(1)
return
if (x0 + y0) % 2 == (x1 + y1) % 2:
print(2)
return
for x in range(x0 - 3, x0 + 4):
for y in range(y0 - 3, y0 + 4):
if m2(x, y, x0, y0):
if m0(x, y, x1, y1) or m1(x, y, x1, y1) or m2(x, y, x1, y1):
print(2)
return
print(3)
if __name__ == "__main__":
main()
记忆化搜索还是非常好推的。
设 f ( i , j , k ) f(i,j,k) f(i,j,k)是三种coin有i,j,k个的情况下完成操作的次数期望
f ( i , j , k ) = f ( i + 1 , j , k ) ∗ i / s u m + f ( i , j + 1 , k ) ∗ j / s u m + f ( i , j , k + 1 ) / s u m + 1 f(i,j,k)=f(i+1,j,k)*i/sum+f(i,j+1,k)*j/sum+f(i,j,k+1)/sum+1 f(i,j,k)=f(i+1,j,k)∗i/sum+f(i,j+1,k)∗j/sum+f(i,j,k+1)/sum+1
边界是当任意一种coin达到100个
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define LT(x) (x * 2)
#define RT(x) (x * 2 + 1)
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
typedef vector<int> vi;
double f[101][101][101];
double get(int a, int b, int c) {
if (a == 100 || b == 100 || c == 100)
return 0;
if (f[a][b][c])
return f[a][b][c];
int s = a + b + c;
double ret = double(a) / s * get(a + 1, b, c) + double(b) / s * get(a, b + 1, c) +
double(c) / s * get(a, b, c + 1) + 1;
f[a][b][c] = ret;
return ret;
}
int main() {
//freopen("in.txt", "r", stdin);
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
double ans = get(a, b, c);
printf("%.8f\n", ans);
return 0;
}
如果没有字母,那么就是一道简单的bfs题。
加入字母后,可以把相同的字母当成一个点。在传送的时候记录一下哪个字母已经发生传送,这样下一次遇到该字母时就不需要做传送操作。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define LT(x) (x * 2)
#define RT(x) (x * 2 + 1)
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
typedef vector<int> vi;
int n, m;
char s[2003][2003];
map<char, vector<pii>> tel;
pii start, target;
queue<pii> qu;
bool vis[2003][2003];
int step[2003][2003];
bool alpha[256];
int main() {
//freopen("in.txt", "r", stdin);
scanf("%d%d", &n, &m);
for (int i = 0; i < n; ++i)
{
scanf("%s", s[i]);
for (int j = 0; j < m; ++j) {
if (s[i][j] == 'S')
start = { i, j };
else if (s[i][j] == 'G')
target = { i, j };
else if (s[i][j] >= 'a' && s[i][j] <= 'z')
tel[s[i][j]].push_back({ i, j });
}
}
memset(step, -1, sizeof(step));
int dir[4][2] = { {0, 1}, {1, 0}, {0, -1}, {-1, 0} };
vis[start.first][start.second] = 1;
step[start.first][start.second] = 0;
qu.push(start);
while (!qu.empty()) {
pii cur = qu.front();
qu.pop();
int r = cur.first, c = cur.second;
//printf("%d %d %d\n", r, c, step[r][c]);
if (s[r][c] == 'G')
break;
if (s[r][c] >= 'a' && s[r][c] <= 'z') {
if (!alpha[s[r][c]]) {
for (pii& nxt : tel[s[r][c]]) {
int nr = nxt.first, nc = nxt.second;
if (vis[nr][nc] == 0) {
vis[nr][nc] = 1;
step[nr][nc] = step[r][c] + 1;
qu.push({ nr, nc });
}
}
alpha[s[r][c]] = 1;
}
}
for (int i = 0; i < 4; ++i) {
int nr = r + dir[i][0], nc = c + dir[i][1];
if (nr >= 0 && nr < n && nc >= 0 && nc < m && s[nr][nc] != '#' && vis[nr][nc] == 0) {
vis[nr][nc] = 1;
step[nr][nc] = step[r][c] + 1;
qu.push({ nr, nc });
}
}
}
printf("%d", step[target.first][target.second]);
return 0;
}
n = 20 n=20 n=20的话,暴力mask求解。
T < 1 0 6 T<10^6 T<106的话,简单的背包求解。
遗憾的是都不满足。
这个题的正统做法是折半搜索。思路是分两组mask求可能的和。
以其中一组遍历,二分求另一组可能的数。
另外有强行剪枝的方法,但是这里的空白太小了写不下。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define LT(x) (x * 2)
#define RT(x) (x * 2 + 1)
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
typedef vector<int> vi;
int n;
LL T;
LL a[50];
set<LL> s0, s1;
int main() {
//freopen("in.txt", "r", stdin);
scanf("%d%lld", &n, &T);
for (int i = 0; i < n; ++i) {
scanf("%lld", &a[i]);
}
vector<LL> v0(a, a + n / 2);
vector<LL> v1(a + n / 2, a + n);
int m = v0.size();
for (int i = 0; i < (1 << m); ++i) {
LL t = 0;
for (int j = 0; j < m; ++j) {
if ((i >> j) & 1) t += v0[j];
}
s0.insert(t);
}
m = v1.size();
for (int i = 0; i < (1 << m); ++i) {
LL t = 0;
for (int j = 0; j < m; ++j) {
if ((i >> j) & 1) t += v1[j];
}
s1.insert(t);
}
vector<LL> l0(s0.begin(), s0.end());
vector<LL> l1(s1.begin(), s1.end());
sort(l0.begin(), l0.end());
sort(l1.begin(), l1.end());
LL ans = 0;
for (auto x : l0) {
if (x == T)
{
ans = T;
break;
}
if (x > T)
break;
int pos = lower_bound(l1.begin(), l1.end(), T - x) - l1.begin();
if (pos - 1 >= 0) {
ans = max(ans, x + l1[pos - 1]);
}
if (pos >= 0 && pos < l1.size() && x + l1[pos] <= T) {
ans = max(ans, x + l1[pos]);
}
}
printf("%lld\n", ans);
return 0;
}