21stUESTC
数矩形
平面上有 \(n\) 个点,这 \(n\) 个点两两不重合。问这 \(n\) 个点可以组成多少个矩形
请注意:矩形的边不必平行于坐标轴。 \(4 ≤ n ≤ 1000\)
保证这些点两两不重合
题解:枚举 + 组合计数
- 对于一个矩阵来说,我们不妨观察它的对角线和中点
- 如果两条对角线之间的长度和中点都相同,说明这两条对角线能够组成一个矩形
- 所以我们不妨\(O(n^2)\)枚举其对角线,利用\(map\)记录每个对角线的长度和中点
- 最后组合计数即可
#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<double, double> pdd;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 1e3 + 10, M = 4e5 + 10;
int n;
pii p[N];
map<pair<pair<double, double>, int>, int> mp;
void solve()
{
cin >> n;
for (int i = 1; i <= n; ++i)
cin >> p[i].first >> p[i].second;
for (int i = 1; i <= n; ++i)
for (int j = 1; j < i; ++j)
{
double x = (p[i].first + p[j].first) / 2.0;
double y = (p[i].second + p[j].second) / 2.0;
int d = (p[i].first - p[j].first) * (p[i].first - p[j].first) +
(p[i].second - p[j].second) * (p[i].second - p[j].second);
mp[mpk(mpk(x, y), d)]++;
}
int ans = 0;
for (auto [x, y] : mp)
ans += y * (y - 1) / 2;
cout << ans << endl;
}
signed main(void)
{
Zeoy;
int T = 1;
// cin >> T;
while (T--)
{
solve();
}
return 0;
}
约瑟夫问题
传统的约瑟夫问题为:\(n\) 个人围成一圈,顺时针依次编号为 \(1, 2, . . . , n\)。从编号为 \(1\) 的人开始顺时针从 1 开始报数,每报到 \(k\) 时这个人就出局,之后他的下一个人又从 \(1\) 开始报数,最后询问剩下的那个人的编号。 而在本题中,每一轮的 \(k\) 都不完全相同。游戏一共进行 \(q\) 轮,你需要输出每一轮出局的人的编号。
\(1 ≤ q < n ≤ 5 × 10^5\)
题解:二分 + 线段树 \(O(nlog^2n)\)
- 对于每一轮的报数\(k\),我们可以让其对剩余人数\(num\)取余后获得当前准备报数的人是第\(p\)个活着的人
- 我们利用线段树维护区间和,所有人初始都是活着的,全为1,如果一个人死了,单点修改其为0
- 我们二分线段树,查询出第一个区间和大于等于\(p\)且是活着的人
#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<double, double> pdd;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 5e5 + 10, M = 4e5 + 10;
int n, q;
struct info
{
int sum;
};
struct node
{
info val;
} seg[N << 2];
info operator+(const info &a, const info &b)
{
info c;
c.sum = a.sum + b.sum;
return c;
}
void up(int id)
{
seg[id].val = seg[lson].val + seg[rson].val;
}
void build(int id, int l, int r)
{
if (l == r)
{
seg[id].val.sum = 1;
return;
}
int mid = l + r >> 1;
build(lson, l, mid);
build(rson, mid + 1, r);
up(id);
}
void change(int id, int l, int r, int x, int val)
{
if (l == r)
{
seg[id].val.sum = val;
return;
}
int mid = l + r >> 1;
if (x <= mid)
change(lson, l, mid, x, val);
else
change(rson, mid + 1, r, x, val);
up(id);
}
info query(int id, int l, int r, int ql, int qr)
{
if (ql <= l && r <= qr)
return seg[id].val;
int mid = l + r >> 1;
if (qr <= mid)
return query(lson, l, mid, ql, qr);
else if (ql > mid)
return query(rson, mid + 1, r, ql, qr);
else
return query(lson, l, mid, ql, qr) + query(rson, mid + 1, r, ql, qr);
}
void solve()
{
cin >> n >> q;
build(1, 1, n);
int pre = 1;
while (q--)
{
int k;
cin >> k;
int num = query(1, 1, n, 1, n).sum;
int p = (pre + k - 1) % num;
if (p == 0)
p += num;
int l = 1, r = n;
while (l <= r)
{
int mid = l + r >> 1;
if (query(1, 1, n, 1, mid).sum < p)
l = mid + 1;
else
r = mid - 1;
}
cout << l << endl;
change(1, 1, n, l, 0);
pre = p;
}
}
signed main(void)
{
Zeoy;
int T = 1;
// cin >> T;
while (T--)
{
solve();
}
return 0;
}
炸弹鸭
假设现在有一局鹅鸭杀游戏,游戏可以抽象的看成在一个 \(n\) 个点 \(m\) 条边的无向图上进行。每条边有边 权 \(w\) 代表两点之间的距离。每个人恰好占据一个节点。
但与原游戏不同,每个人在接到炸弹后只能丢给与自己直接有边相连的并且距离小于等于给定的限制 \(k\) 的人,但炸弹鸭一次可能会塞给你很多个炸弹,且每次你只能把一个炸弹给一个满足条件且此次你还未传递炸弹的人,因此你需要把自己所有的炸弹按照上述条件传递出去,我们定义爆炸被避免当且仅当你手上的炸弹全被传递出去并且存在一个与你有边相连并且距离小于等于给定限制 \(k\) 的人没有被你传递炸弹。
每次游戏炸弹鸭给人炸弹的概率是等可能的,因此你想知道,如果知道炸弹鸭每次给人的炸弹数\(d\)以及给定限制 \(k\) ,有多少位置的人不能够完成传递来避免爆炸。
\(1 ≤ n ≤ 2 × 10^5 , 1 ≤ m, q ≤ 10^6\)
题解:树状数组 \(O((q + m)logn)\)
- 我们考虑离线查询,每次将长度小于等于\(k\)的所有边加入,然后查询有多少个点的度数小于\(d+1\)
- 我们不妨利用树状数组维护某个度数上有多少个点,考虑到一个点的度数不会超过\(2m\),显然可行
- 我们将询问的\(k\)排序,将所有的边排序,然后按照\(k\)从小到达依次加边
- 每次加边的时候一定会使得某两个度数上的点数\(-1\),某两个度数上的点数\(+1\)
- 每次加完边后查询度数\([0,d]\)有多少个点即可
- 时间复杂度:\(O((q + m)logn)\)
#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<double, double> pdd;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 2e5 + 10, M = 1e6 + 10;
int n, m, q;
int c[M << 1];
array<int, 3> edge[M];
int ans[M];
int du[N];
struct node
{
int d;
int k;
int qid;
bool operator<(const node &t) const
{
return k < t.k;
}
} qry[M];
int lowbit(int x)
{
return x & -x;
}
void add(int x, int val)
{
while (x <= 2 * m)
{
c[x] += val;
x += lowbit(x);
}
}
int getsum(int x)
{
int res = 0;
while (x > 0)
{
res += c[x];
x -= lowbit(x);
}
return res;
}
int query(int l, int r)
{
return getsum(r) - getsum(l - 1);
}
void solve()
{
cin >> n >> m >> q;
for (int i = 1, u, v, w; i <= m; ++i)
{
cin >> u >> v >> w;
edge[i] = {w, u, v};
}
for (int i = 1; i <= q; ++i)
{
cin >> qry[i].d >> qry[i].k;
qry[i].qid = i;
}
sort(edge + 1, edge + m + 1);
sort(qry + 1, qry + q + 1);
for (int i = 1; i <= n; ++i)
du[i] = 1; // 为什么du[i]=1,因为lowbit(0) = 0,显然不行
add(1, n);
for (int i = 1, j = 1; i <= q; ++i)
{
int d = qry[i].d, k = qry[i].k;
while (j <= m && edge[j][0] <= k)
{
int u = edge[j][1], v = edge[j][2];
add(du[u], -1);
add(du[v], -1);
du[u]++;
du[v]++;
add(du[u], 1);
add(du[v], 1);
j++;
}
ans[qry[i].qid] = n - query(d + 2, 2 * m);
}
for (int i = 1; i <= q; ++i)
cout << ans[i] << endl;
}
signed main(void)
{
Zeoy;
int T = 1;
// cin >> T;
while (T--)
{
solve();
}
return 0;
}
能量采集
你正在玩一个游戏。游戏中一共有两种能量,分别表示为 \(A\) 和 \(B\)。 游戏地图可以看成是一张 \(n\) 行 \(m\) 列的地图,第 \(i\) 行第 \(j\) 列的格子所含有的能量是 \(A\) 或 \(B\) 的其中一种。
你从 \((1, 1)\) 出发,走到 \((n, m)\)。你只能往下或往右走。
你有一个容量为 \(k\) 的能量容器。这个容器是一个队列,即当收集到的能量总量大于 \(k\) 时,越早收集到的 能量会越先从队列弹出(即消失),直到能量总量等于 \(k\) 为止。
你所属的阵营是 \(A\),即每当该容器内的能量全是种类 \(A\) 并且容器装满了的时候,你所属的阵营的分数会加一(之后容器内的能量不会消失)。
当你站在任意格子上时,不论该格子内的能量是哪一种,你都会收集一单位的能量到容器内。 你想知道:对于所有\((^{n+m-2}_{n-1})\)条可能的路径中,你期望能够给自己阵营加多少分?
\(1 ≤ n, m ≤ 400\)
题解:线性\(DP\) + 数字三角形模型 + 组合计数
加分的期望为:\(\frac{路径队列末尾有连续k个A方案数}{总路径数}\)
总路径数:\((^{n+m-2}_{n-1})\)
状态表示:
\(f[i][j][p]\):代表从\((1,1)\)走到\((i,j)\),且队列末尾有连续\(p\)个\(A\)的方案数
状态属性:数量
状态转移:
\[f[i][j][p] += f[i-1][j][p-1]+f[i][j-1][p-1]\ \ \wedge\ (g[i][j]=='A')
\]
- 状态初始:
\[f[i][j][1] = C_{i+j-2}^{i-1} \ \ \wedge\ \ g[i][j] == 'A' \\
f[i][j][0] = C_{i+j-2}^{i-1} \ \ \wedge\ \ g[i][j] == 'B'
\]
路径队列末尾有连续\(k\)个\(A\)方案数为\(\sum f[i][j][k]\times C_{n-1-(i-1)+m-1-(j-1)}^{n-1-(i-1)}\),因为路径到\((i,j)\)处末尾已经有连续\(k\)个\(A\)了,那么后面的路径可以随便走
那么最后答案为
\[(\sum f[i][j][k]\times C_{n-1-(i-1)+m-1-(j-1)}^{n-1-(i-1)})\times(C_{n+m-2}^{n-1})^{-1}\ \ 在模998244353下
\]
#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
// #define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<double, double> pdd;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 998244353;
const double eps = 1e-9;
const int N = 4e2 + 10, M = 1e3 + 10;
int n, m, k;
char g[N][N];
int f[N][N][N << 1];
long long fac[M], inv[M];
int qpow(int a, int b, int p)
{
int res = 1;
while (b)
{
if (b & 1)
res = 1LL * res * a % p;
b >>= 1;
a = 1LL * a * a % p;
}
return res % p;
}
int C(int a, int b)
{
return (1LL * fac[a] % mod * inv[b] % mod * inv[a - b] % mod) % mod;
}
void solve()
{
cin >> n >> m >> k;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
cin >> g[i][j];
fac[0] = 1;
inv[0] = 1;
for (int i = 1; i <= 1000; ++i)
{
fac[i] = 1LL * fac[i - 1] * i % mod;
inv[i] = 1LL * inv[i - 1] * qpow(i, mod - 2, mod) % mod;
}
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
if (g[i][j] == 'A')
f[i][j][1] = C(i + j - 2, i - 1);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
for (int p = 1; p <= k; ++p)
if (g[i][j] == 'A')
f[i][j][p] = (f[i][j][p] % mod + f[i - 1][j][p - 1] % mod + f[i][j - 1][p - 1] % mod) % mod;
long long ans = 0;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j)
{
long long t = (1LL * f[i][j][k] % mod * C((n - 1) - (i - 1) + (m - 1) - (j - 1), (n - 1) - (i - 1)) % mod) % mod;
ans = (ans % mod + t % mod) % mod;
}
cout << (ans % mod * fac[n - 1] % mod * fac[m - 1] % mod * inv[n + m - 2] % mod) % mod << endl;
}
signed main(void)
{
Zeoy;
int T = 1;
// cin >> T;
while (T--)
{
solve();
}
return 0;
}
随机推荐
- VMware安装CentOS7及远程登录详细教程
写在前面 主要使用软件: VMware Workstation Pro17 Navicat Premium17 Xshell7 Xftp7 1.在虚拟机安装CentOS7 访问阿里云镜像站 ,选择标记 ...
- JavaScript – Fetch
前言 上一篇 JavaScript – XMLHttpRequest 有提到 XMLHttpRequest 正在被 Fetch 取代,这篇就继续介绍 Fetch 吧. 参考 阮一峰 – Fetch A ...
- Figma 学习笔记 – Scroll and Position Fixed
Scroll Scroll 属于 prototype 的一部分. 当一个 Frame 的内容超出 Frame 的高度或宽度时, Frame 就具备了 scroll 的能力. 通过 uncheck cl ...
- Figma 学习笔记 – Constraints 约束
用途 Constraints 用于 responsive design, 子元素和父元素建立约束关系后, 当父元素 dimension 变换的时候, 子元素会做出相应的变化 (移动位置或 resize ...
- 彻底理解 IP 地址,子网掩码,子网划分
原文地址:https://oldme.net/article/55彻底理解 IP 地址,子网掩码,子网划分 什么是 IP 协议 在回答什么是 IP 协议前,我们先需要回答另外一个问题:什么是网络?从普 ...
- Java反射取值赋值
项目需求:需要对获取的数据每个字段值校验合法性,故想到用 反射 实现 /** * 字段值校验 * * @param r 需要校验的实体类 * @param properties 自定义需要校验的属性 ...
- 八字测算引流seo程序php网页版黄历/排盘/抽签/星座/生肖/解梦整站程序分享
2演示站: https://s31.yczfcn.com/ 2源码说明: 1.手机端和PC端共两套模板,手机端访问时候自动跳转至手机端模板和域名. 2.本程序包含文章系统,结合自身的免费测算功能,适合 ...
- ⼯作⾥中的token是怎么管理的?
我们公司的token管理都是通过vuex配合本地存储来做的,使⽤vuex是因为token数据⽐较特殊,在很多 模块中都可能会⽤到,vuex⽅便管理,配合本地存储⽐如localstorage,是因为vu ...
- 云原生周刊:Kubernetes v1.27 发布 | 2023.4.17
开源项目推荐 Palaemon Palaemon 是一个开源开发工具,用于监控 Kubernetes 集群的健康状况和资源指标并分析内存不足 (OOMKill) 错误. Gitkube Gitkube ...
- 两个时间段比较的六种情况,以及交集、并集、补集简要sql语句示例
〇.两时间段比较的全部情况 总共有如下图中的六种情况: 下文将根据这六种情况进一步操作. 注意,图中说的动态和固定两时间段,就是两个普通时间段,不区分主次,仅用作帮助理解. 一.判断两个时间段是否有交 ...