2022杭电多校(1)
2022杭电多校(1)
K
签到
B(枚举、BFS/DFS)
由于数据范围很小,可以枚举存在的墙的状态, 对于每种状态,做一遍 bfs,看是否能搜到终点
由于点的坐标不是整数,边的坐标是整数,可以将坐标全部乘 2,这样判断两个点能否通过时不用考虑边界情况,做两次跨立实验即可
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <queue>
using namespace std;
#define endl "\n"
typedef long long ll;
typedef pair<int, int> PII;
const int N = 16;
int n, m, k;
int sx, sy, ex, ey, state;
bool st[N*2][N*2];
int dx[] = {2, 0, -2, 0};
int dy[] = {0, 2, 0, -2};
struct Wall
{
int x1, y1, x2, y2;
}a[N];
bool check(int x, int y, int tx, int ty)
{
if (tx < 0 || tx > 2 * n || ty < 0 || ty > 2 * m)
return false;
if (st[tx][ty])
return false;
for (int i = 0; i < k; i++)
{
if (!(state >> i & 1))
continue;
int x1 = a[i].x1, y1 = a[i].y1, x2 = a[i].x2, y2 = a[i].y2;
if (x == tx && y1 == y2)
{
if ((x - x1) * (x - x2) < 0 && (y1 - y) * (y1 - ty) < 0)
return false;
}
if (y == ty && x1 == x2)
{
if ((x1 - x) * (x1 - tx) < 0 && (y - y1) * (y - y2) < 0)
return false;
}
}
return true;
}
bool judge()
{
memset(st, false, sizeof st);
queue<PII> q;
q.push({sx, sy});
st[sx][sy] = true;
while(!q.empty())
{
int x = q.front().first, y = q.front().second;
q.pop();
if (x == ex && y == ey)
return true;
for (int i = 0; i < 4; i++)
{
int tx = x + dx[i];
int ty = y + dy[i];
if (check(x, y, tx, ty))
{
st[tx][ty] = true;
q.push({tx, ty});
}
}
}
return false;
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T;
cin >> T;
while(T--)
{
cin >> n >> m >> k;
cin >> sx >> sy >> ex >> ey;
sx = sx * 2 + 1, sy = sy * 2 + 1, ex = ex * 2 + 1, ey = ey * 2 + 1;
for (int i = 0; i < k; i++)
{
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
x1 *= 2, y1 *= 2, x2 *= 2, y2 *= 2;
a[i] = {x1, y1, x2, y2};
}
int ans = 0;
for (state = 0; state < 1 << k; state++)
{
int cnt = __builtin_popcount(state);
if (ans >= cnt)
continue;
if (judge())
ans = cnt;
}
cout << k - ans << endl;
}
return 0;
}
L(博弈论)
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
using namespace std;
#define endl "\n"
typedef long long ll;
typedef pair<int, int> PII;
const int N = 1e6 + 10;
int n;
int a[N];
int main()
{
int T;
scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
for (int i = 0; i <= n; i++)
scanf("%d", a + i);
for (int i = n; i > 0; i--)
a[i-1] += a[i] / 2;
a[0] ? puts("Alice") : puts("Bob");
}
return 0;
}
I(计算几何)
- 对于第一个点,肯定在四条线之一上,枚举这四种线,记为线1
- 对于其余随意一个不在线1上的点,只能在另外三种线上,枚举这三种线,记为线2
- 线1、线2固定后,交点即为激光发射点,检查其余 n - 2 个点能否被照到即可
快读!!!
#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <set>
#include <map>
#define endl '\n'
using namespace std;
typedef std::pair<int, int> PII;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 5e5 + 10;
int n, k;
int _read() {
static int ans, c, p;
for (c = getchar(); c != '-' && (c < '0' || c > '9'); c = getchar());
if (c == '-') p = false, c = getchar(); else p = true;
for (ans = 0; c <= '9' && c >= '0'; c = getchar()) ans = ans * 10 + c - '0';
return p ? ans : -ans;
}
struct Point
{
int x, y;
Point operator+(const Point &a) const {return {x + a.x, y + a.y};}
Point operator-(const Point &a) const {return {x - a.x, y - a.y};}
Point operator-() const {return {-x, -y};}
Point operator*(const Point &a) const {return {x * a.x, y * a.y};}
Point operator*(const double k) const {return {k * x, k * y};}
Point operator/(const double k) const {return {x / k, y / k};}
double operator^(const Point &a) const {return x * a.y - y * a.x;}
}p[N];
typedef Point Vector;
Point get_intersection(Point P, Vector v, Point Q, Vector w)
{
Vector u = P - Q;
double t = (w ^ u) / (v ^ w);
return P + v * t;
}
Vector d[4] = {{0, 1}, {1, 1}, {1, 0}, {1, -1}};
bool check(int x, int y)
{
if (abs(x) == abs(y))
return true;
if (x * y == 0)
return true;
return false;
}
bool judge(Point c)
{
for (int i = 2; i <= n; i++)
{
if (i == k)
continue;
int dx = p[i].x - c.x, dy = p[i].y - c.y;
// cout << dx << " " << dy << endl;
if (!check(dx, dy))
return false;
}
return true;
}
bool solve()
{
for (int i = 0; i < 4; i++)
{
for (k = 2; k <= n; k++)
{
if (((p[k] - p[1]) ^ d[i]) != 0)
break;
}
if (k == n + 1)
return true;
for (int j = 0; j < 4; j++)
{
if (i == j)
continue;
Point c = get_intersection(p[1], d[i], p[k], d[j]);
if (judge(c))
return true;
}
}
return false;
}
int main()
{
int T;
T = _read();
while(T--)
{
n = _read();
for (int i = 1; i <= n; i++)
p[i].x = _read() * 2, p[i].y = _read() * 2;
cout << (solve() ? "YES" : "NO") << endl;
}
return 0;
}
C(bitset优化dp)
-
首先容易想到朴素的dp,\(f[i][j][k]\) 为前 i 个物品,价值为 j ,体积为 k ,能否得到这种状态
f[i][j][k] = f[i-1][j][k] | f[i-1][j^w][k-v];
复杂度为 \(2^{30}\)
-
由于是 bool 型的 dp,可以用 bitset 来优化,用 bitset 存第三维,第 t 位为 1 表示体积为 t 时是可以得到的
-
滚动数组优化掉第一维的空间
复杂度为 \(\frac {2^{30}}{64}=2^{24}\)
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <bitset>
using namespace std;
#define endl "\n"
typedef long long ll;
typedef pair<int, int> PII;
const int N = 1.1e3;
int n, m;
bitset<N> f[N], tmp[N];
int solve()
{
for (int i = 0; i < N; i++)
f[i].reset();
f[0][0] = 1;
for (int i = 1; i <= n; i++)
{
int v, w;
cin >> v >> w;
for (int j = 0; j < 1024; j++)
tmp[j] = f[j] << v;
for (int j = 0; j < 1024; j++)
f[j] |= tmp[j^w];
}
for (int j = 1023; j >= 0; j--)
{
if (f[j][m] == 1)
return j;
}
return -1;
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T;
cin >> T;
while(T--)
{
cin >> n >> m;
cout << solve() << endl;
}
return 0;
}
D (bitset优化,扫描线思想)
-
筛质数
-
求出每两个点的距离,存下这些边,并按距离从小到大的顺序排序
-
从小到大枚举边,如果这个边的长度 w 是质数,则求以 w 为中位数的三元组数,为对答案的贡献
-
可维护一个bool数组, \(f[i][j]=1\) 表示 (i, j) 的距离小于 w
设 w 这条边的两端点是 x,y,设第三个点是 k,要满足 w 在 dist(x, k) 与 dist(y, k) 之间,则需要 \(f[x][k] \;xor\; f[y][k]=1\), 求共有多少个这样的 k
-
这样枚举仍是 \(n^3\) 的复杂度,由于是 bool 类型的数组,可用 bitset 代替来优化,对答案的贡献就是
(f[x] ^ f[y]).count()
计算为这条边的贡献后,要更新 bitset,即 \(f[x][y]=f[y][x]=1\)
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <bitset>
using namespace std;
#define endl "\n"
typedef long long ll;
typedef pair<int, int> PII;
const int N = 2e3 + 10, M = 2e5 + 10;
int n, m;
struct Edge
{
int u, v, w;
bool operator<(const Edge &x) const
{
return w < x.w;
}
}e[N*N];
struct Point
{
int x, y;
}p[N];
bitset<N> f[N];
bool st[M];
int pr[M], cnt;
void get_primes(int n)
{
memset(st, true, sizeof st);
st[1] = false;
for (int i = 2; i <= n; i++)
{
if (st[i])
pr[++cnt] = i;
for (int j = 1; j <= cnt && pr[j] <= n / i; j++)
{
int p = pr[j];
st[i * p] = false;
if (i % p == 0)
break;
}
}
}
int main()
{
int T;
scanf("%d", &T);
get_primes(2e5 + 10);
while(T--)
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
f[i].reset();
for (int i = 1; i <= n; i++)
scanf("%d%d", &p[i].x, &p[i].y);
int tot = 0;
for (int i = 1; i <= n; i++)
for (int j = i + 1; j <= n; j++)
e[++tot] = {i, j, abs(p[i].x - p[j].x) + abs(p[i].y - p[j].y)};
sort(e + 1, e + tot + 1);
ll ans = 0;
for (int i = 1; i <= tot; i++)
{
int x = e[i].u, y = e[i].v, w = e[i].w;
if (st[w])
ans += (f[x] ^ f[y]).count();
f[x][y] = f[y][x] = 1;
}
printf("%lld\n", ans);
}
return 0;
}
H(分层图最短路 + set)
\(dist[i][0/1]\) 表示是否通过特殊边到达第 i 号点的最短距离
分层图最短路即可
但因为走过特殊边后,就可以走向所有点,如果不加优化的话,每个 \(dist[u][1]\) 要更新 n - 1 个点,免费边有 \(n^2\) 个,总复杂度为 \(n^2log m\)
要发现一个性质来优化:走过特殊边后,u 走向非邻点 v 的边的长度是 0,所以用 u 更新 v 时,\(dist[v][0]=dist[u][1]+0\)
若 v 被 u 之后的点 t 更新,则 \(dist[v][0]=dist[t][0/1]+w\), 因为优先队列的性质,并且每次放进去的都比之前再队列里的数大,所以 \(dist[t][0/1]>=dist[u][1]\;且\;w>=0\), 所以如果 u 更新了非邻点 v 后, v 就已经达到了最短距离, 之后再有免费通过的机会就不用考虑 v 了
所以可用 set 记录哪些点还没被上述情况找到最短路,已经找到了就从 set 中删除,因此最多用到更新 \(n\) 次免费边
#include <iostrea
m>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <set>
#include <queue>
using namespace std;
#define endl "\n"
typedef long long ll;
typedef pair<int, int> PII;
int _read() {
static int ans, c, p;
for (c = getchar(); c != '-' && (c < '0' || c > '9'); c = getchar());
if (c == '-') p = false, c = getchar(); else p = true;
for (ans = 0; c <= '9' && c >= '0'; c = getchar()) ans = ans * 10 + c - '0';
return p ? ans : -ans;
}
void _write(int ans) {
static int a[20], n;
if (ans < 0) {
putchar('-');
ans = -ans;
}
if (ans == 0) {
putchar('0');
return;
}
for (n = 0; ans; ans /= 10) a[n++] = ans % 10;
for (n--; n >= 0; n--) putchar(a[n] + '0');
return;
}
const int N = 1e6 + 10;
const ll INF = 1e18;
int n, m, s, k;
struct Edge
{
int v, w, op;
}e[N];
ll dist[N][2];
bool st[N][2], neb[N];//这个点是否更新过其他点;当前点的邻点
struct Node
{
int id, d, op;
bool operator<(const Node &x) const
{
return d > x.d;
}
};
vector<vector<Edge> > G(N);
void add(int u, int v, int w, int op)
{
G[u].push_back({v, w, op});
}
void init()
{
for (int i = 1; i <= n; i++)
G[i].clear();
}
void dijkstra(int x)
{
//还没有确定最短距离的点的集合
set<int> S;
for (int i = 1; i <= n; i++)
{
dist[i][0] = dist[i][1] = INF;
st[i][0] = st[i][1] = false;
if (i != x)
S.insert(i);
neb[i] = false;
}
dist[x][0] = 0;
priority_queue<Node> q;
q.push({x, 0, 0});
while(!q.empty())
{
auto t = q.top();
q.pop();
int u = t.id;
if (t.op == 0)
S.erase(u);
else
{
//找到当前点的邻点
for (auto i : G[u])
neb[i.v] = true;
vector<int> tmp;
for (auto i : S)
{
if (st[i])
continue;
//i还没确定最短距离,因此dist[u][1]就是它的最短距离
dist[i][0] = dist[u][1];
q.push({i, dist[i][0], 0});
tmp.push_back(i);
}
//剪枝,因为是堆优化dijkstra,且当前边为 0,之后不会有点能更新i的最小距离了
for (auto i : tmp)
S.erase(i);
//清空,防止影响下一个点的信息
for (auto i : G[u])
neb[i.v] = false;
}
int sub = (t.op ? k : 0);
if (st[u][t.op])
continue;
st[u][t.op] = true;
for (auto i : G[u])
{
int v = i.v;
if (dist[v][i.op] > dist[u][t.op] + i.w - sub)
{
dist[v][i.op] = dist[u][t.op] + i.w - sub;
q.push({v, dist[v][i.op], i.op});
}
}
}
}
int main()
{
int T = _read();
while(T--)
{
n = _read();
m = _read();
s = _read();
k = _read();
init();
while(m--)
{
int u, v, w, op;
u = _read();
v = _read();
w = _read();
op = _read();
add(u, v, w, op);
}
dijkstra(s);
for (int i = 1; i <= n; i++)
{
if (min(dist[i][0], dist[i][1]) == INF)
printf("-1 ");
else
printf("%lld ", min(dist[i][0], dist[i][1]));
}
puts("");
}
return 0;
}