2019-08-07 纪中NOIP模拟赛B组

T1 [JZOJ1385] 直角三角形

题目描述

  二维平面坐标系中有N个位置不同的点。

  从N个点选择3个点,问有多少选法使得这3个点形成直角三角形。

数据范围

  $3 \leq N \leq 1500$

分析

  再一次考场上正解写挂(怎么AC的一堆都是暴力卡常吸氧??)

  我们可以先枚举直角顶点,并求出其他点与该点连成的直线的斜率,再按斜率将点排序

  众所周知,两条相互垂直的直线斜率之积为 $-1$,所以两条垂直直线斜率一定异号(竖直和水平的直线特殊考虑)

  然后把斜率大于 $0$ 的点从大到小加入队列,再从大到小枚举斜率小于等于 $0$ 的点

  如果队首斜率与该点斜率之积小于 $-1$,那么它与后面将枚举的点也一定无法形成直角三角形,所以将其出队

  如果队首斜率与该点斜率之积大于 $-1$,那么该点与队列后面的点也一定无法形成直角三角形,所以直接判断下一个点

  这样时间复杂度就降至 $O(n^2 \, log \, n)$

  注意精度,$eps$ 尽量设小一点

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
#define ll long long
#define inf 2000000001.0
#define eps 1e-15
#define N 1505

int n, ans;

struct Point {
    int x, y;
    double k;
} p[N], q[N];

bool cmp(Point a, Point b) {
    return a.k > b.k;
}

double getk(Point a, Point b) {
    if (a.x == b.x) return inf;
    if (a.y == b.y) return 0;
    return (double)(b.y - a.y) / (b.x - a.x);
}

double Abs(double a) {
    if (a < 0) return -a;
    return a;
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d%d", &p[i].x, &p[i].y);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j < i; j++) q[j] = p[j];
        for (int j = i; j < n; j++) q[j] = p[j + 1];
        for (int j = 1; j < n; j++) q[j].k = getk(p[i], q[j]);
        sort(q + 1, q + n, cmp);
        queue<int> qu;
        int now = 0, last;
        while (q[++now].k > 0 && now < n) qu.push(now);
        for (; now < n; now++) {
            if (Abs(q[now].k - q[now - 1].k) < eps) {
                ans += last; continue;
            }
            last = 0;
            while (!qu.empty()) {
                int temp = qu.front();
                if (q[now].k == 0) {
                    if (q[temp].k == inf) qu.pop(), last++;
                    else break;
                }
                else {
                    double mul = q[now].k * q[temp].k;
                    if (Abs(mul + 1) < eps) qu.pop(), last++;
                    else if (mul + 1 < 0) qu.pop();
                    else break;
                }
            }
            ans += last;
        }
    }
    printf("%d", ans);
    
    return 0;
}
View Code

 T2 [JZOJ1386] 排序

题目描述

  你收到一项对数组进行排序的任务,数组中是1到N个一个排列。你突然想出以下一种特别的排序方法,分为以下N个阶段:

  • 阶段1,把数字1通过每次交换相邻两个数移到位置1;

  • 阶段2,用同样的方法把N移到位置N;

  • 阶段3,把数字2移到位置2处;

  • 阶段4,把数字N-1移到位置N-1处;

  • 依此类推。换句话说,如果当前阶段为奇数,则把最小的未操作的数移到正确位置上,如果阶段为偶数,则把最大的未操作的数移到正确位置上。

  写一个程序,给出初始的排列情况,计算每一阶段交换的次数。

数据范围

  对于 $70 \%$ 的数据,$1 \leq N \leq 100$

  对于 $100 \%$ 的数据,$1 \leq N \leq 10^5$

分析

  刚看到这题,就想到了昨天讲到的拉格朗日计数

  如果一个数要向前移,那么移动次数就是前面还没有操作过的数字个数,向后移同理

  所以我们可以可以先把所有位置的值都设为 $1$,对于每次操作都只需要查询这个数所在位置前/后的区间和,然后把这个位置的值改为 $0$

  用树状数组/线段树维护就可以了

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define N 100005

int n, p;
int pos[N], t[N];

int lowbit(int x) {
    return x & -x;
}

void update(int x, int q) {
    while (x <= n) t[x] += q, x += lowbit(x);
}

int query(int l, int r) {
    int ans = 0; l--;
    while (r) ans += t[r], r -= lowbit(r);
    while (l) ans -= t[l], l -= lowbit(l);
    return ans;
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &p);
        pos[p] = i;
        update(i, 1);
    }
    for (int i = 1; i <= n / 2; i++) {
        printf("%d\n", query(1, pos[i] - 1));
        update(pos[i], -1);
        printf("%d\n", query(pos[n + 1 - i] + 1, n));
        update(pos[n + 1 - i], -1);
    }
    if (n % 2) printf("0\n");
    
    return 0;
}
View Code

T3 [JZOJ1388] 自行车赛

题目描述

  翠亨村举行一场自行车赛,翠亨村有N个路口(编号1到N),另有M条双向边连接起来。下面有几个定义:

  • 路径:由一系列边组成,满足后一条边的起点为前一条边的终点;

  • 简单路径:每个路口最多经过一次的路径;

  • 环:起点和终点在同一个路口的简单路径。

  保证每对路口之间至少有一条路径相连,两个路口之间最多只有一条边直接相连,每条边最多只会出现在一个环中。

  你的任务是找出最长的满足以下两个条件的路径:

  • 起点可以在任意路口,但终点必须在1号路口;

  • 路径可能多次经过同一个路口,但每条边最多只会经过一次。

数据范围

  $2 \leq N \leq 10^4$,$1 \leq M \leq 2N-2$

分析

  咕咕咕

T4 [JZOJ6275] 小L的数列

题目描述

数据范围

  对于 $30 \%$ 的数据,$n \leq 10^4$

  对于另外 $20 \%$ 的数据,$b_i=1$,$n \leq 10^6$

  对于另外 $20 \%$ 的数据,$f_1...f_{k-1}=1$

  对于另外 $20 \%$ 的数据,$k \leq 30$

  对于 $100 \%$ 的数据,$1 \leq 200$,$1 \leq n \leq 4 \times 10^7$,$1 \leq b_i,f_i \leq 998244352$

分析

  我们以 $k=4$ 为例来说明

  因为 $f[1]=f[1]^1f[2]^0f[3]^0f[4]^0$,$f[2]=f[1]^0f[2]^1f[3]^0f[4]^0$,

     $f[3]=f[1]^0f[2]^0f[3]^1f[4]^0$,$f[4]=f[1]^0f[2]^0f[3]^0f[4]^1$

  可得初始矩阵 $$\begin{pmatrix}1&0&0&0\\0&1&0&0\\0&0&1&0\\0&0&0&1\end{pmatrix}$$

  又因为 $f[5]=f[1]^{b[4]}f[2]^{b[3]}f[3]^{b[2]}f[4]^{b[1]}$

  所以转移得到的下一个矩阵为 $$\begin{pmatrix}0&0&0&b[4]\\1&0&0&b[3]\\0&1&0&b[2]\\0&0&1&b[1]\end{pmatrix}$$

  由于初始矩阵相当于矩阵中的实数 $1$,所以第二个矩阵就是转移矩阵

  然后根据矩阵的结合律,可以对转移矩阵快速幂,最后求出矩阵最后一列 $f$ 的幂之积得到答案

  注意在矩阵乘法中,若模数为 $p$($p$ 为质数),则应该对 $p-1$ 取模

  这是根据费马小定理 $a^{\varphi(p)}\equiv 1 \, (mod \; p)$

  即 $a^{p-1}\equiv 1 \, (mod \; p)$

  所以每 $p-1$ 次幂模 $p$ 都为 $1$,对答案不会产生影响

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define N 205

int n, K;
ll b[N], f[N], ans = 1;
const int p = 998244353;

struct Mat {
    ll m[N][N];
    Mat(){memset(m, 0, sizeof m);}
} a;

Mat mul(Mat x, Mat y) {
    Mat z;
    for (int i = 1; i <= K; i++)
    for (int j = 1; j <= K; j++)
    for (int k = 1; k <= K; k++)
        z.m[i][j] = (z.m[i][j] + x.m[i][k] * y.m[k][j] % (p - 1)) % (p - 1);
    return z;
}

Mat mpow(Mat x, int k) {
    Mat res;
    for (int i = 1; i <= K; i++) res.m[i][i] = 1;
    while (k) {
        if (k & 1) res = mul(res, x);
        x = mul(x, x);
        k >>= 1;
    }
    return res;
}

ll qpow(ll x, ll k) {
    ll res = 1;
    while (k) {
        if (k & 1) res = (res * x) % p;
        x = (x * x) % p;
        k >>= 1;
    }
    return res;
}

int main() {
    scanf("%d%d", &n, &K);
    for (int i = 1; i <= K; i++) scanf("%lld", b + i);
    for (int i = 1; i <= K; i++) scanf("%lld", f + i);
    if (n <= K) {printf("%lld\n", f[n]); return 0;}
    for (int i = 2; i <= K; i++) a.m[i][i - 1] = 1;
    for (int i = 1; i <= K; i++) a.m[i][K] = b[K + 1 - i];
    a = mpow(a, n - K);
    for (int i = 1; i <= K; i++) ans = ans * qpow(f[i], a.m[i][K]) % p;
    printf("%lld\n", ans);
    
    return 0;
}
View Code

转载于:https://www.cnblogs.com/Pedesis/p/11316367.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值