• 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;
    }
    
  • 相关阅读:
    iTerm2 颜色配置
    IOS_问题: Xcode8 安装KSImageName插件, 编代码就崩了
    IOS_设置启动图片若干问题
    Android 多国语言
    Reveal 配置与使用
    自定义代码块
    Android 动画
    SQL
    dialog
    2016-1-18UIlabel学习,正则表达式
  • 原文地址:https://www.cnblogs.com/bringlu/p/14911400.html
Copyright © 2020-2023  润新知