[ABC271Ex] General General 题解
[ABC271Ex] General General Solution
更好的阅读体验戳此进入
题面
给定终点 $ (A, B) $,存在向量集合 $ S = { (1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1), (1, -1) } $,每次给定一个长度为 $ 8 $ 的 $ 01 $ 串表示对应的向量是否可以被使用且无限次使用,你需要用可以使用的向量从起点 $ (0, 0) $ 到达终点 $ (A, B) $,最小化使用的向量的个数,输出最小值,无解输出 -1
。
Solution
首先这道题应该很明显是分类讨论,但是找到的性质的多少也就决定了式子最终的复杂程度。
首先这八个向量分别对应了右、右上、上、$ \cdots $、右下,也就是逆时针顺序的八向移动,这里我们称右、上、左、下为四向向量,右上、左上、左下、右下为八向向量。
首先我们想到,若终点可以被一个向量表示,那么一定可以只用这个向量表示(废话)。
其次一个显而易见的性质,显然两个相邻的四向向量可以表示出其夹着的八向向量(但贡献会多 $ 1 $)两个八向向量可以表示出其夹着的四向向量(贡献不变),而这个性质普遍地讲可以认为就是平面向量基本定理,也就是平面任意向量都可以用两个不共线向量的 $ a\vec{x} + b\vec{y} $ 表示,但是这里显然我们需要要求 $ a, b $ 非负且为整数,对于非负可以转化为向量只能表示出其夹角内的所有向量,整数则需要具体讨论一下了。
所以不难想到,如果一个向量可以被任意两个向量表示,那么就变成了一个简单的解方程的问题,而若解出来的解全都不合法(有负数或者不为整数),就说明这个向量无法被仅用两个向量表示。
而这里我们可以思考一下,对于某个象限内的终点,若我们可以用其对应的两个四向向量,那么是一定可以表示出来的,所以无法表示就说明我们一定不是有对应的两个四向向量的情况。
而如果是选择的一个四向向量和一个八向向量,那么我们再选一个四向向量上去是没意义的,因为都可以更优地被之前的一个四向向量和一个八向向量表示,所以我们可以考虑一下再选一个八向向量的情况,这个时候我们发现,若这个四向向量用了达到两次,那么就可以在不增加贡献的情况下用另外两个八向向量表示,所以对于这个情况我们可以转化为使用一个四向向量后再用另外两个八向向量解方程。
而对于初始选择两个八向向量在选择一个四向向量的情况与上一种情况本质相同。
同时我们继续思考就会发现再没有更多情况了。
于是我们现在梳理一下共有哪些可能:
- 用一个向量表示。
- 用两个向量表示(应该是两个不共线的向量,但是范围较小这里无需剪枝,可以强行枚举)。
- 用一个四向向量且只使用一次,和任意两个相邻的八向向量表示。
令种类数 $ d = 8 $,复杂度大概是 $ O(d^2T) $ 的,可以通过。
Tips:上文很多部分的再选一个等语言可能已经默认了额外选择的是相邻的或夹着的,因为其它部分情况的错误性过于显然,故不赘述了。
Code
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW void* Edge::operator new(size_t){static Edge* P = ed; return P++;}
#define ROPNEW_NODE void* Node::operator new(size_t){static Node* P = nd; return P++;}
using namespace std;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define EPS (1e-10)
#define INFLL (0x3f3f3f3f3f3f3f3fll)
template < typename T = int >
inline T read(void);
ll A, B;
ll ans(INFLL);
ll dx[10] = {0, 1, 1, 0, -1, -1, -1, 0, 1};
ll dy[10] = {0, 0, 1, 1, 1, 0, -1, -1, -1};
bitset < 10 > exists;
bool isInteger(ld v){
return fabs(ld(ll(v)) - v) < EPS;
}
void Check(int i, int j, int base = 0){
if(dx[i] * dy[j] - dx[j] * dy[i] == 0)return;
ld v1 = (ld)(B * dx[i] - A * dy[i]) / (dx[i] * dy[j] - dx[j] * dy[i]);
if(v1 <= -EPS || !isInteger(v1))return;
ld v2 = (ld)(B * dx[j] - A * dy[j]) / (dx[j] * dy[i] - dx[i] * dy[j]);
if(v2 <= -EPS || !isInteger(v2))return;
ans = min(ans, ll(v1) + ll(v2) + base);
}
int main(){
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
int T = read();
while(T--){
A = read(), B = read();
for(int i = 1; i <= 8; ++i){
char c = getchar(); while(!isdigit(c))c = getchar();
exists[i] = c == '1';
}ans = INFLL;
if(!A && !B){printf("0\n"); continue;}
for(int i = 1; i <= 8; ++i)
if(exists[i]){
if(A * dx[i] < 0 || B * dy[i] < 0)continue;
if((!dx[i] && (A || !isInteger((ld)B / dy[i]))) || (!dy[i] && (B || !isInteger((ld)A / dx[i]))))continue;
if(!dx[i])ans = min(ans, B / dy[i]);
if(!dy[i])ans = min(ans, A / dx[i]);
ld v1 = (ld)A / dx[i], v2 = (ld)B / dy[i];
if(!isInteger(v1) || !isInteger(v2) || (ll)v1 != (ll)v2)continue;
ans = min(ans, (ll)v1);
}
for(int i = 1; i <= 8; ++i)for(int j = i + 1; j <= 8; ++j)
if(exists[i] && exists[j])Check(i, j);
for(int i = 2; i <= 8; i += 2){
if(!exists[i] || !exists[i == 2 ? 8 : i - 2])continue;
for(int j = 1; j <= 8; j += 2){
if(!exists[j])continue;
A -= dx[j], B -= dy[j];
Check(i == 2 ? 8 : i - 2, i, 1);
A += dx[j], B += dy[j];
}
}
printf("%lld\n", ans == INFLL ? -1ll : ans);
}
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
UPD
update-2023_02_09 初稿