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;
}
posted @ 2023-05-24 18:14  Zeoy_kkk  阅读(6)  评论(0编辑  收藏  举报