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

A. LaIS#

dpi 为到第 i 位的最长的 almost increasing 长度。可以发现,这个 dpi 的转移只有从 ajai 的地方转移过去,或者是找到一个最靠后的 k 使得 ak>ai,再从 [1,k1] 找到一个 j 满足 ajai

那么,递推式为

dpi=max{max{dpj}+1,ajai,j[1,i1]max{dpj}+2,ajai,j[1,k1]

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

#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\n", 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\n", ans);
    return 0;
}

H. K and Medians#

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

  • (k1)|(nm)
  • 删后的序列中满足一个条件,存在某一个数在删后的序列中且它前面删了 k12 个数,后面删了 k12 个数。

以上。

#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#

题意:给你两个向量 v1=(dx1,dy1),v2=(dx2,dy2) 请恰好选 n 个不同点 pi=(xi,yi),使得平面上的所有点都能由下式表示出来。P=pi+av1+bv2

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

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

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

所以第一步先判断 S=|dx1dy2dx2dy1| 是否等于 n。如果不等于则无解,反之有解。

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

法二:再试试分割 dx1,dx2,dy1,dy2。设 dx=gcd(dx1,dx2),dy=gcd(dy1,dy2),m=n/(dxdy),那么最后的答案 n=mdxdy

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

证明:由于 ady1+bdy2 无法构造出一个 y 使得 0<y<dy,那么我们考虑在 ady1+bdy2=0 的时候,是否会使得 x 重选或者漏选。由上式可得 a=kdy2/dy,b=kdy1/dy,kZ。带入 x 相关式子得

x=xi+kdy2dx1/dykdy1dx2/dy=xi+kS/dy

S/dy=n/dy=mdx,确实是平移距离。

综上所述,证毕。

最终答案即为 (idx+rx,ry),i[0,m),rx[0,dx),ry[0,dy)

#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\n", i*dx+rx, 1ll * ry);
            }
        }
    }
    return 0;
}

J. Road Reform#

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

  1. vk,那么由于最小生成树又是最小瓶颈树,那么 ans=eT(vek)[ve>k]
  2. v<k,那么从非最小生成树边与最小生成树最大边权 v 中找到离 k 最近的边。

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

M. Similar Sets#

题意:求出二分图中是否存在四元环。若存在,输出四元环在左边集合中的两个点的下标。左边集合大小 n105,右边集合大小 m2105,最坏情况下可能会有 2105 条边。

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

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

最终时间复杂度为 O(m2D+mD),在 D=m 时取到较优秀的时间复杂度为 O(mm)

#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\n", 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\n", 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;
}
posted @   bringlu  阅读(136)  评论(0编辑  收藏  举报
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端
点击右上角即可分享
微信分享提示
主题色彩