2020-2021 ICPC, NERC, Southern and Volga Russian Regional Contest AGHIJM 题解

A. LaIS

(dp_i) 为到第 i 位的最长的 almost increasing 长度。可以发现,这个 (dp_i) 的转移只有从 (a_j leq a_i) 的地方转移过去,或者是找到一个最靠后的 k 使得 (a_k > a_i),再从 ([1,k-1]) 找到一个 j 满足 (a_j leq a_i)

那么,递推式为

[dp_i = max egin{cases} max{dp_j} + 1, &a_j leq a_i, j in [1, i-1] \ max{dp_j}+2, &a_j leq a_i, j in [1, k-1] end{cases} ]

那么可以用一个单调递减的队列来维护并找出 k。用主席树来维护 (dp) 数组,下标 (a_i) 存值 (dp_i)。时间复杂度 (O(n log n))

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef double db;
typedef long double ld;

#define IL inline
#define fi first
#define se second
#define mk make_pair
#define pb push_back
#define SZ(x) (int)(x).size()
#define ALL(x) (x).begin(), (x).end()
#define dbg1(x) cout << #x << " = " << x << ", "
#define dbg2(x) cout << #x << " = " << x << endl

template<typename Tp> IL void read(Tp &x) {
    x=0; int f=1; char ch=getchar();
    while(!isdigit(ch)) {if(ch == '-') f=-1; ch=getchar();}
    while(isdigit(ch)) { x=x*10+ch-'0'; ch=getchar();}
    x *= f;
}
int buf[42];
template<typename Tp> IL void write(Tp x) {
    int p = 0;
    if(x < 0) { putchar('-'); x=-x;}
    if(x == 0) { putchar('0'); return;}
    while(x) {
        buf[++p] = x % 10;
        x /= 10;
    }
    for(int i=p;i;i--) putchar('0' + buf[i]);
}

const int N = 500000 + 5;

struct PRETree {
    int n, tot;
    int rt[N];
    int maxv[N << 5], ls[N << 5], rs[N << 5];
    void init(int n) {
        this -> n = n;
        fill(rt, rt + n + 1, 0);
        fill(maxv, maxv + (n << 5) + 1, 0);
        fill(ls, ls + (n << 5) + 1, 0);
        fill(rs, rs + (n << 5) + 1, 0);
    }
    int newnode() { return ++tot;}
    void copynode(int u, int v) {
        maxv[u] = maxv[v];
        ls[u] = ls[v];
        rs[u] = rs[v];
    } 
    int upd(int pre, int L, int R, int pos, int v) {
        int o = newnode();
        copynode(o, pre);
        if(L == R) {
            maxv[o] = v;
            return o;
        }
        int M = L+R >> 1;
        if(pos <= M) ls[o] = upd(ls[pre], L, M, pos, v);
        else rs[o] = upd(rs[pre], M+1, R, pos, v);
        maxv[o] = max(maxv[ls[o]], maxv[rs[o]]);
        return o;
    }
    int query(int o, int L, int R, int qL, int qR) {
        if(qL <= L && R <= qR) {
            return maxv[o];
        }
        int M = L+R >> 1;
        int ans = 0;
        if(qL <= M) ans = max(ans, query(ls[o], L, M, qL, qR));
        if(M < qR) ans = max(ans, query(rs[o], M+1, R, qL, qR));
        return ans;
    }
    int query_max(int posR, int aR) {
        if(posR == 0) return 0;
        return query(rt[posR], 1, n, 1, aR);
    }
}lpr;

int n;
int a[N], dp[N];
int m;
int q[N];

int main() {
#ifdef LOCAL
    freopen("A.in", "r", stdin);
#endif
    int T; read(T);
    while(T--) {
        read(n);
        lpr.init(n);
        m = 0;
        for(int i=1;i<=n;i++) read(a[i]);
        for(int i=1;i<=n;i++) {
            dp[i] = lpr.query_max(i-1, a[i]) + 1;
            if(m >= 1) {
                int pos = lower_bound(q+1, q+m+1, i, [&](int x, int y) { return a[x] > a[y];}) - q - 1;
                if(pos > 0) {
                    dp[i] = max(dp[i], lpr.query_max(q[pos]-1, a[i]) + 2);
                    q[m = pos + 1] = i;
                }
                else q[m = 1] = i;
            }
            else {
                q[++m] = i;
            }
            lpr.rt[i] = lpr.upd(lpr.rt[i-1], 1, n, a[i], dp[i]);
        }
        int ans = 0;
        // puts("dp[i] = ");
        // for(int i=1;i<=n;i++)  {
        //     dbg1(i); dbg2(dp[i]);
        // }

        for(int i=1;i<=n;i++) ans = max(ans, dp[i]);
        printf("%d
", ans);
    }
    return 0;
}

G. Hobbits

非常简单的计算几何,模拟的时候为啥没读到?

维护一个最高的点, 然后判断线段是不是在眼睛到最高点这个向量左侧即可。记得判一下平行。

eps 开太小了导致本机能过的数据交上去 WA 了。

#include <bits/stdc++.h>
using namespace std;

typedef double db;

#define IL inline
#define fi first
#define se second
#define mk make_pair
#define pb push_back
#define SZ(x) (int)(x).size()
#define ALL(x) (x).begin(), (x).end()
#define dbg1(x) cout << #x << " = " << x << ", "
#define dbg2(x) cout << #x << " = " << x << endl

const db eps = 1e-8;
int dcmp(db x) {
    if(fabs(x) < eps) return 0; else return x < 0 ? -1 : 1;
}

struct P {
    db x, y;
    P(db x=0.0, db y=0.0):x(x), y(y) {}
};

typedef P V;
IL V operator + (const V& a, const V& b) { return V(a.x + b.x, a.y + b.y);}
IL V operator - (const V& a, const V& b) { return V(a.x - b.x, a.y - b.y);}
IL V operator * (const V& a, db p) { return V(a.x * p, a.y * p);}
IL V operator / (const V& a, db p) { return V(a.x / p, a.y / p);}

IL bool operator < (const P& a, const P& b) { 
    return a.x < b.x || (a.x == b.x && a.y < b.y);
}
IL bool operator == (const P& a, const P& b) {
    return dcmp(a.x-b.x) == 0 && dcmp(a.y-b.y) == 0;
}
IL db Dot(const V& a, const V& b) { return a.x * b.x + a.y * b.y;}
IL db Length(const V& a) { return sqrt(Dot(a, a));}
IL db Angle(const V& a, const V& b) { return acos(Dot(a, b) / Length(a) / Length(b));}
IL db Cross(const V& a, const V& b) { return a.x*b.y - a.y*b.x;}

IL P GetLineIntersection(P p, V v, P q, V w) {
    V u = p-q;
    db t = Cross(w, u) / Cross(v, w);
    return p + v * t;
}

IL bool OnLeft(P a, P b, P p) {
    return dcmp(Cross(b - a, p - a)) > 0;
}
IL bool OnSegment(P p, P a1, P a2) {
    return dcmp(Cross(a1-p, a2-p)) == 0 && dcmp(Dot(a1-p, a2-p)) < 0;
}


const int N = 200000 + 5;

int n, H;
P p[N];


int main() {
#ifdef LOCAL
    freopen("G.in", "r", stdin);
#endif
    scanf("%d%d", &n, &H);
    for(int i=1;i<=n;i++) {
        int xx, yy; scanf("%d%d", &xx ,&yy);
        p[i] = P(xx, yy);
    }
    p[0] = P(p[n].x, p[n].y + H);
    P high = p[n];
    db ans = 0.0;
    for(int i=n;i>=2;i--) {
        if(!OnLeft(p[0], high, p[i])) high = p[i];
        V v = p[i-1] - p[i];
        if(dcmp(Cross(v, high - p[0])) == 0) { // parallel
            if(OnLeft(p[0], high, p[i])) continue;
            else ans += Length(v);
            continue;
        }

        // not parallel
        int b = 2;
        P a = GetLineIntersection(p[0], high - p[0], p[i], p[i-1] - p[i]);
        if(OnSegment(a, p[i-1], p[i])) {
            ans += Length(a - p[i-1]);
        }
        else {
            if(dcmp(Cross(high - p[0], p[i] - p[0])) >= 0 && dcmp(Cross(high - p[0], p[i-1] - p[0])) >= 0) continue;
            else ans += Length(p[i] - p[i-1]);
        }
    }
    printf("%.10lf
", ans);
    return 0;
}

H. K and Medians

结论是满足以下两个条件就是 yes。

  • ((k-1) | (n-m))
  • 删后的序列中满足一个条件,存在某一个数在删后的序列中且它前面删了 (frac{k-1}{2}) 个数,后面删了 (frac{k-1}{2}) 个数。

以上。

#include <bits/stdc++.h>
using namespace std;

const int N = 200000 + 5;

int n, k, m;
int a[N];

bool check() {
    for(int i=1;i<=m;i++) {
        int delL = a[i] - i;
        int delR = (n - a[i]) - (m - i);
        if(delL >= (k-1) / 2 && delR >= (k-1) / 2) return true;
    }
    return false;
}

int main() {
    int T; scanf("%d", &T);
    while(T--) {
        scanf("%d%d%d", &n, &k, &m);
        for(int i=1;i<=m;i++) scanf("%d", a+i);
        if((n-m) % (k-1) != 0) { puts("NO"); continue;}
        puts(check() ? "YES" : "NO");
    }
    return 0;
}

I. Plane Tiling

题意:给你两个向量 (vec{v_1} = (dx_1, dy_1), vec{v_2} = (dx_2, dy_2)) 请恰好选 (n) 个不同点 (p_i = (x_i, y_i)),使得平面上的所有点都能由下式表示出来。(P = p_i + a cdot vec{v_1} + b cdot vec{v_2})

做法:先把从 ((0, 0)) 出发的向量 (vec{v_1}, vec{v_2}) 构成的平行四边形画出来。接着扩展这个平行四边形,铺满整个平面(可以自行脑补长边贴着长边,短边贴着短边)。然后可以发现整个平面很多点在平行四边形的顶点上。答案其实就等于单个平行四边形内不在平行四边形顶点的点的个数。尝试计算它,可以发现

不在平行四边形顶点上的点的个数 = 平行四边形内部的点的个数 + (平行四边形边上的点的个数 - 4) / 2(注:这里 /2 是因为计算答案的时候只能计算左边和上边。右边和下边会和平面上别的平行四边形算重。减去的是顶点)

引用 pick 定理,可以发现 答案 = 平行四边形面积 - 1 + 1 = 平行四边形面积。(注:这里 +1 是考虑 ((0,0))

所以第一步先判断 (S = |dx_1 cdot dy_2 - dx_2 cdot dy_1|) 是否等于 (n)。如果不等于则无解,反之有解。

法一:接下来可以把平行四边形里面的所有点都输出出来,即平行四边形内部的点和位于上边、左边的点并去掉顶点。记得输出 $(0, 0) $。

法二:再试试分割 (dx_1, dx_2, dy_1, dy_2)。设 (d_x = gcd (dx_1, dx_2), d_y = gcd(dy_1, dy_2), m = n / (d_x cdot d_y)),那么最后的答案 (n = m cdot d_x cdot d_y)

先取 (x in [0, d_x), y in [0, d_y)) 中的所有点,很显然它们每一个都无法相互表示出来。接下来考虑如何用 (m) 去扩展这些点。这一步可以把选出来的点矩阵沿着 (x) 轴正向每次平移 (d_x) 格共平移 (m-1) 次。这个即为答案。

证明:由于 (a cdot dy_1 + b cdot dy_2) 无法构造出一个 (y) 使得 (0 < y < d_y),那么我们考虑在 (a cdot dy_1 + b cdot dy_2=0) 的时候,是否会使得 (x) 重选或者漏选。由上式可得 (a = k cdot dy_2 / d_y, b = -k cdot dy_1 / d_y, k in Z)。带入 (x) 相关式子得

[x = x_i + k cdot dy_2 cdot dx_1 / d_y - k cdot dy_1 cdot dx_2 / d_y = x_i + k cdot S / d_y ]

(S / d_y = n / d_y = m cdot d_x),确实是平移距离。

综上所述,证毕。

最终答案即为 ((i cdot d_x + r_x, r_y), i in [0,m), r_x in [0, d_x), r_y in [0, d_y))

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

#define IL inline

long long n;
long long dx1, dx2, dy1, dy2;

ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a % b);}
IL ll absl(ll x) { return x < 0 ? -x : x;}

int main() {
#ifdef LOCAL
    freopen("I.in", "r", stdin);
#endif
    scanf("%lld", &n);
    scanf("%lld%lld", &dx1, &dy1);
    scanf("%lld%lld", &dx2, &dy2);
    long long d = absl(dx1 * dy2 - dx2 * dy1);
    if(d != n) return puts("NO"), 0;
    ll dx = absl(gcd(dx1, dx2)), dy = absl(gcd(dy1, dy2));
    ll m = n / dx / dy;
    puts("YES");
    for(int rx=0;rx<dx;rx++) {
        for(int ry=0;ry<dy;ry++) {
            for(int i=0;i<m;i++) {
                printf("%lld %lld
", i*dx+rx, 1ll * ry);
            }
        }
    }
    return 0;
}

J. Road Reform

先做一次最小生成树,得到最小生成树 T。设做完后最大边权为 (v)

  1. (v geq k),那么由于最小生成树又是最小瓶颈树,那么 (ans = sum_{e in T} (v_e - k)[v_e > k])
  2. (v < k),那么从非最小生成树边与最小生成树最大边权 (v) 中找到离 k 最近的边。

不知道模拟的时候自己在干什么,可能在梦游?

M. Similar Sets

题意:求出二分图中是否存在四元环。若存在,输出四元环在左边集合中的两个点的下标。左边集合大小 (n leq 10^5),右边集合大小 (m leq 2 cdot 10^5),最坏情况下可能会有 (2 cdot 10^5) 条边。

做法:第一步离散化。再考虑分治。设左边集合中度数大于等于 (D) 的点为大点,其余点为小点。枚举每一个左边集合中的点 (i),对大点做一种操作,对小点做另一种操作。

  1. 若该点 (i) 度数为 (A) 大于等于 (D),我们看这个点与其他左边集合中其他的点的关系。再设一个 (w_x) 数组,若右边集合中下标为 (x) 的点与点 (i) 相连,这个 (w_x = 1),反之 (w_x = 0)。这样每次枚举到一个大点,把这个点对应的 (w_x) 数组弄出来,然后对左边集合中剩余其他点判断是否有两个 (w_x)(1)。如果题目想要数据一直不匹配到四元环的话,单次的时间复杂度为 (O(A + m) = O(m))。由于如果找到了四元环可以直接跳出,所以如果数据一直找不到四元环的话,左边集合中的每两个大点,最多只会连接重复的一个右边集合中的点。那么最多只会有 (O(frac{m}{D})) 个大点。那么这一步总体的最坏时间复杂度应为 (O(frac{m}{D} cdot m) = O(frac{m^2}{D}))
  2. 若该点 (i) 度数为 (A) 小于 (D),是个小点,由于上一步看过了所有大点和其他所有点的关系,这里看这个点与其他小点的关系即可。接下来我们把点 (i) 所连接的所有右边集合中的点组成的点对 ((x,y), x<y) 推入(v[x]) 这个 vector 中。然后枚举完所有小点得到整个 vector 后,再看哪一个 vector 中某个数出现了两次。由于 (sum_{i=1}^n A_i = m) ,时间复杂度应为 (O(sum A_i^2[A_i < D])),根据导数等高中知识,让越多的 (A_i) 取到 (D) 这个时间复杂度越大,所以最坏时间复杂度为 (O(D^2 frac{m}{D}) = O(mD))

最终时间复杂度为 (O(frac{m^2}{D} + mD)),在 (D = sqrt{m}) 时取到较优秀的时间复杂度为 (O(m sqrt m))

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef double db;
typedef long double ld;
typedef pair<int, int> pii;

#define IL inline
#define fi first
#define se second
#define mk make_pair
#define pb push_back
#define SZ(x) (int)(x).size()
#define ALL(x) (x).begin(), (x).end()
#define dbg1(x) cout << #x << " = " << x << ", "
#define dbg2(x) cout << #x << " = " << x << endl

template<typename Tp> IL void read(Tp &x) {
    x=0; int f=1; char ch=getchar();
    while(!isdigit(ch)) {if(ch == '-') f=-1; ch=getchar();}
    while(isdigit(ch)) { x=x*10+ch-'0'; ch=getchar();}
    x *= f;
}
int buf[42];
template<typename Tp> IL void write(Tp x) {
    int p = 0;
    if(x < 0) { putchar('-'); x=-x;}
    if(x == 0) { putchar('0'); return;}
    while(x) {
        buf[++p] = x % 10;
        x /= 10;
    }
    for(int i=p;i;i--) putchar('0' + buf[i]);
}

const int N = 100000 + 5;
const int M = N << 1;

int n, m, mm;
int tmp[M];
int pre[M];
int w[M];
vector<int> a[N];

vector<pair<int, int> > mp[M];

void solve() {
    mm = 0;
    read(n);
    for(int i=1;i<=n;i++) {
        a[i].clear();
        int ki; read(ki);
        for(int j=1;j<=ki;j++) {
            int x; read(x);
            a[i].push_back(x);
            tmp[++mm] = x;
        }
        sort(a[i].begin(), a[i].end());
    }

    sort(tmp+1, tmp+1+mm);
    m = unique(tmp+1, tmp+1+mm) - tmp - 1;
    for(int i=1;i<=n;i++) {
        for(int j=0;j<a[i].size();j++) {
            a[i][j] = lower_bound(tmp+1, tmp+1+m, a[i][j]) - tmp;
        }
    }

    int sz = sqrt(mm + 0.5);

    // big -> others
    bool ok = false;
    for(int i=1;i<=n;i++) {
        if(a[i].size() < sz) continue;
        for(int j=0;j<a[i].size();j++) w[a[i][j]] = 1;
        for(int j=1;j<=n;j++) if(i != j) {

            if(j < i && a[j].size() >= sz) continue;
            int cnt = 0;
            for(int k=0;k<a[j].size();k++) {
                if(w[a[j][k]]) cnt++;
                if(cnt >= 2) { printf("%d %d
", i, j); ok = true; break;}
            }
            if(ok) break;
        }
        for(int j=0;j<a[i].size();j++) w[a[i][j]] = 0;
        if(ok) return;
    }

    // small -> small
    for(int i=1;i<=n;i++) {
        if(a[i].size() >= sz) continue;
        for(int j=0;j<a[i].size();j++) {
            for(int k=j+1;k<a[i].size();k++) {
                mp[a[i][j]].push_back(mk(a[i][k], i));
            }
        }
    }

    for(int x=1;x<=m;x++) {
        for(auto pp : mp[x]) {
            int y = pp.first, p = pp.second;
            if(pre[y]) {
                printf("%d %d
", p, pre[y]);
                ok = true;
                break;
            }
            pre[y] = p;
        }
        for(auto pp : mp[x]) {
            int y = pp.first, p = pp.second;
            pre[y] = 0;
        }
        if(ok) break;
    }

    for(int i=1;i<=m;i++) mp[i].clear();
    if(ok) return;
    puts("-1");
    return;
}

int main() {
#ifdef LOCAL
    freopen("M.in", "r", stdin);
#endif
    int T; read(T);
    while(T--) solve();
    return 0;
}
原文地址:https://www.cnblogs.com/bringlu/p/14911400.html