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$

分析

  这大概是这些天来最神仙的题了

  虽然说思路理解了以后感觉不算是很难但我肯定是想不到的,但是要码出来并不简单,何况是在考场上

  思考后可以发现,这个图是由一些环和桥组成的(相连的环之间一定有桥)

  然后我们可以用 $Tarjan$ 判断出每条边是在环上还是在桥上

  对于一个环/桥,我们说它悬挂在点 $x$ 上,当且仅当 $x$ 是该环/桥上 $dfn$ 值最小的点

  设 $f[x][0]$ 表示点 $x$ 的子图中始于 $x$ 终于 $x$ 的最长路径长度,$f[x][1]$ 表示点 $x$ 的子图中始于任意节点终于 $x$ 的最长路径长度

  显然 $f[x][0]$ 就等于悬挂在点 $x$ 上的所有环的长度之和加上环上所有点 $y$ 的 $f[y][0]$ 之和

  而对于 $f[x][1]$,有两种情况可能达到最优(在两者中取较大值)

  1.先走完悬挂在 $x$ 上的所有环(包括环上所有的点上悬挂的环),即 $f[x][0]$;当有桥悬挂在点 $x$ 上时,就再加上这些桥中走下去得到的路径最长的一条路径长度

  2.先走完悬挂在 $x$ 上的所有其他环,然后走到剩下的一个环上的某个结点处进入其子图,由于从 $x$ 在环上走到该结点有顺时针和逆时针两个方向,所以要取两条路中的较长者

  这样就可以处理出每个结点上的 $f$ 值,答案就是 $f[1][1]$

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

int n, m, tot, num, top, cirnum;
int to[N << 2], nxt[N << 2], head[N];
int dfn[N], low[N], stack[N], f[N][2];
vector<int> c[N], cir[N], bri[N];

void add(int u, int v) {
    to[++tot] = v; nxt[tot] = head[u]; head[u] = tot;
}

void tarjan(int x, int fa) {
    dfn[x] = low[x] = ++num;
    stack[++top] = x;
    for (int i = head[x]; i; i = nxt[i]) {
        if (to[i] == fa) continue;
        if (dfn[to[i]]) low[x] = min(low[x], dfn[to[i]]);
        else {
            tarjan(to[i], x);
            if (low[to[i]] < dfn[x]) low[x] = min(low[x], low[to[i]]);
            else if (low[to[i]] == dfn[x]) {
                cir[x].push_back(++cirnum);
                do c[cirnum].push_back(stack[top]);
                while (to[i] != stack[top--]);
            }
            else bri[x].push_back(stack[top--]);
        }
    }
}

int dp(int x, int way) {
    int &sum = f[x][way];
    if (sum >= 0) return sum;
    sum = 0; int path = 0;
    for (int i = 0; i < bri[x].size(); i++)
        path = max(path, dp(bri[x][i], 1) + 1);
    for (int i = 0; i < cir[x].size(); i++) {
        vector<int> &now = c[cir[x][i]];
        int cirlen = now.size() + 1, all = now.size() - 1;
        int len1 = 1, len2 = 1, maxlen = 0;
        for (int j = 0; j < now.size(); j++) {
            maxlen = max(maxlen, len1 + dp(now[j], 1));
            len1 += dp(now[j], 0) + 1;
            maxlen = max(maxlen, len2 + dp(now[all - j], 1));
            len2 += dp(now[all - j], 0) + 1;
            cirlen += dp(now[j], 0);
        }
        sum += cirlen;
        path = max(path, maxlen - cirlen);
    }
    if (way) sum += path;
    return sum;
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        add(u, v); add(v, u);
    }
    tarjan(1, 0);
    memset(f, -1, sizeof f);
    printf("%d\n", dp(1, 1));
    
    return 0;
}
View Code

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^{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
posted @ 2019-08-07 22:53  Pedesis  阅读(207)  评论(0编辑  收藏  举报