2021昆明区域赛

Simone and graph coloring

题意:给定一个排列,要求给逆序对连边,形成一张图,图的相邻顶点之间颜色不能相同,问最少的颜色种类以及每个点的颜色。
做法:对于每一个数,他要连接上在他前面出现过的比他大的数。如果他前面出现过1 2 3 4 这种颜色,那他的颜色必定是5。可以递归的想,他前面出现过的最大颜色是4,那么对于涂了颜色4的,肯定是出现过1 2 3 的颜色的…,所以线段树查询区间最大值,从而确定当前的颜色。
因为多测数据有1e6组,用endl会狂TLE,所以以后再也不要用endl了。

#include<bits/stdc++.h>
using namespace std;
// #define int long long
const int N = 1e6 + 5, mod = 998244353;
stack<int> q;
int a[N];
bool f[N];
struct node{
    int l, r;
    int ma;
    #define l(x) tree[x].l
    #define r(x) tree[x].r
} tree[N * 4];
void build(int p, int l, int r)
{
    tree[p].l = l, tree[p].r = r;
    if (l >= r) {
        tree[p].ma = 0;
        return;
    }
    int mid = (l + r) >> 1;
    int chl = p * 2, chr = p * 2 + 1;
    build(chl, l, mid);
    build(chr, mid + 1, r);
    tree[p].ma = max(tree[chl].ma, tree[chr].ma);
}
void up(int p, int l, int r, int x)
{   
    if (l <= l(p) && r >= r(p)) {
        tree[p].ma = x;
        return;
    }
    int mid = (l(p) + r(p)) >> 1;
    int chl = p * 2, chr = p * 2 + 1;
    if (l <= mid) up(chl, l, r, x);
    if (mid < r) up(chr, l, r, x);
    tree[p].ma = max(tree[chl].ma, tree[chr].ma);
}
int que(int p, int l, int r)
{
    if (l <= l(p) && r >= r(p)) {
        return tree[p].ma;
    }
    int mid = (l(p) + r(p)) >> 1;
    int chl = p * 2, chr = p * 2 + 1;
    int ret = 0;
    if (l <= mid) ret = max(ret,que(chl, l, r));
    if (mid < r) ret = max(ret,que(chr, l, r));
    return ret;
}
signed main ()
{
    int tt;
    cin >> tt;
    while(tt--){
        int n;
        scanf("%d", &n);
        build(1, 1, n);
        for (int i = 1; i <= n; i++)
        {
            int x;
            scanf("%d", &x);
            int k = que(1, x, n);
            a[i] = k + 1;
            up(1, x, x, k + 1);
            f[k + 1] = 1;
        }
        int cnt = 0;
        for (int i = 1; i <= n + 1; i++) {
            if (!f[i]) {
                cnt = i - 1;
                break;
            }
        }
        printf("%d\n", max(1, cnt));
        for (int i = 1; i <= n; i++) printf("%d ", a[i]);
        puts("");
        for (int i = 1; i <= n; i++) {
            a[i] = 0;
            f[i] = 0;
        }
    }
    return 0;
}

Mr. Main and Windmills

题意:给定起始一条线段与n个点,m次询问,每次回答第h个点与其他n-1个点两两相连,得到与线段的一些交点,输出第k个交点。
做法:直接套用几何板子即可。
坑点:这条线段是矢量,有方向!!所以第k个交点要分清楚是左到右还是右到左。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 5, mod = 998244353;
#define eps 1e-8
#define pi  3.141592653589793

using namespace std;

//二维点类
struct Point
{
    double x,y;
    bool operator < (const Point &k) const {
        return x < k.x;
    }
    bool operator <= (const Point &k) const {
        return (x < k.x) || (fabs(x - k.x) <= 1e-5);
    }
    Point(double a=0,double b=0){x=a;y=b;}
};

typedef Point Vector;

//二维直线类,一般方程ax+by+c=0
struct Line
{
    double a,b,c,angle;
    Point p1,p2;
    Line(Point s,Point e)
    {
        a=s.y-e.y;
        b=e.x-s.x;
        c=s.x*e.y-e.x*s.y;
        angle=atan2(e.y-s.y,e.x-s.x);
        p1=s;p2=e;
    }
    Line(){}
};

//二维线段类
struct Segment
{
    Point s,e;
    Segment(Point a,Point b){s=a;e=b;}
    Segment(double x1,double y1,double x2,double y2)
    {
        s=Point(x1,y1);
        e=Point(x2,y2);
    }
    Segment(){}
};

//向量的加减及数乘
Vector operator + (Point a,Point b)
{
    return Vector(a.x+b.x,a.y+b.y);
}

Vector operator - (Point a,Point b)
{
    return Vector(a.x-b.x,a.y-b.y);
}

Vector operator * (Point a,double k)
{
    return Vector(a.x*k,a.y*k);
}

Vector operator / (Point a,double k)
{
    return Vector(a.x/k,a.y/k);
}

//求向量的模(长度)
double len(Vector a)
{
    return sqrt(a.x*a.x+a.y*a.y);
}

//得到sp-op和ep-op的叉积
//>0时ep在opsp的逆时针方向,<0时顺时针,=0时共线
double Cross(Point &sp, Point &ep, Point &op)
{
    return (sp.x-op.x)*(ep.y-op.y)-(ep.x-op.x)*(sp.y-op.y);
}

//两向量求叉积,求三角形面积需要除以2
double Cross(Vector a,Vector b)
{
    return a.x*b.y-b.x*a.y;
}

//两向量求点积
double Dot(Vector a,Vector b)
{
    return a.x*b.x+a.y*b.y;
}

//求最大最小值
double max(double a,double b)
{
    if (a<b) return b; else return a;
}

double min(double a,double b)
{
    if (a>b) return b; else return a;
}

//采用eps的精度判断大/小于零
int epssgn(double x)
{
    if (fabs(x)<eps) return 0;
    else return x<0?-1:1;
}

//求两点之间的直线距离
double dis(Point a,Point b)
{
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}

//判断两直线是否平行
int Parallel(Line l1,Line l2)
{
    return (fabs(l1.a*l2.b-l2.a*l1.b)<eps);
}

//判断两直线是否相等
int LineEqual(Line l1,Line l2)
{
    if (!Parallel(l1,l2)) return 0;
    else return (fabs(l1.a*l2.c-l2.a*l1.c)<eps && fabs(l1.b*l2.c-l2.b*l1.c)<eps);
}

//求点C到线段AB的距离 
double PointToSegDist(Point A,Point B,Point C)
{
    if (dis(A,B)<eps) return dis(B,C);
    if (epssgn(Dot(B-A,C-A))<0) return dis(A,C);
    if (epssgn(Dot(A-B,C-B))<0) return dis(B,C);
    return fabs(Cross(B-A,C-A))/dis(B,A);
}

//求线段两端AB到另一线段CD的距离
double TwoSegMinDist(Point A,Point B,Point C,Point D)
{
    return min(min(PointToSegDist(A,B,C),PointToSegDist(A,B,D)),
               min(PointToSegDist(C,D,A),PointToSegDist(C,D,B)));
}

Point SymPoint(Point p,Line l) //求二维平面上点p关于直线p1p2的对称点
{
    Point result;
    double a=l.p2.x-l.p1.x;
    double b=l.p2.y-l.p1.y;
    double t=((p.x-l.p1.x)*a+(p.y-l.p1.y)*b)/(a*a+b*b);
    result.x=2*l.p1.x+2*a*t-p.x;
    result.y=2*l.p1.y+2*b*t-p.y;
    return result;
}

//判断线段s1e1与s2e2是否相交(含端点)
//不含端点的话将下面的<=改成<
int IsSegmentIntersect(Point s1, Point e1, Point s2, Point e2)
{
    if( min(s1.x,e1.x)<= max(s2.x,e2.x) &&
        min(s1.y,e1.y)<= max(s2.y,e2.y) &&
        min(s2.x,e2.x)<= max(s1.x,e1.x) &&
        min(s2.y,e2.y)<= max(s1.y,e1.y) &&
        Cross(s2,e2,s1)*Cross(s2,e2,e1)<=0 &&
        Cross(s1,e1,s2)*Cross(s1,e1,e2)<=0)
    return 1;
    return 0;
}

//知道直线上两点p1p2,判断直线与线段se是否相交,含顶点
int IsLineIntersectSegment(Point p1,Point p2,Point s,Point e)
{
    if (Cross(p1,p2,s)*Cross(p1,p2,e)>eps) return 0;
    else return 1;
}

int IsLineIntersectSegment(Line l1,Point s,Point e)
{
    if (Cross(l1.p1,l1.p2,s)*Cross(l1.p1,l1.p2,e)>eps) return 0;
    else return 1;
}

//求两条直线l1和l2的交点
Point GetIntersect(Line l1, Line l2) 
{
    Point res;
    res.x=(l1.b*l2.c-l2.b*l1.c)/(l1.a*l2.b-l2.a*l1.b);
    res.y=(l1.c*l2.a-l2.c*l1.a)/(l1.a*l2.b-l2.a*l1.b);
    return res;
}
Point arr[N], ans[N];
    Point ss, tt;
bool cmp(Point &x, Point &y)
{
    return dis(x, ss) < dis(y, ss);
}

signed main ()
{
    int n, m;
    scanf("%lld%lld", &n, &m);
    cin >> ss.x >> ss.y >> tt.x >> tt.y;
    Line L = Line(ss, tt);
    for (int i = 1; i <= n; i++)
    {
        scanf("%lf%lf", &arr[i].x, &arr[i].y);
    }
    while(m--)
    {
        int h, k;
        scanf("%lld%lld", &h, &k);
        int cnt = 0;
        for (int i = 1; i <= n; i++) {
            if (i == h) continue;
            Line t = Line(arr[i], arr[h]);
            if (!IsLineIntersectSegment(arr[i], arr[h], ss, tt)) continue;
            ans[++cnt] = GetIntersect(L, t);
        }
        sort(ans + 1, ans + 1 + cnt, cmp);
        if (k > cnt) puts("-1");
        else {
            printf("%.10f %.10f\n", ans[k].x, ans[k].y);
        }
    }
    return 0;
}

Parallel Sort

题意:给定一个排列,在一次操作中,可以选出k对下标,这些下标两两不同,对于每一对,交换他们指向的数。要求操作次数最少。
做法:以前做过类似的交换排列的题,所以想到 i 与 pi连边来做。但是这题烦就烦在要求次数最少。对于一个长度为n的环,可以进行n-1断边,也就是交换。那么一个环怎么操作次数最少呢?就是说有很多边可以同一次操作的时候断开。
草稿纸画了好久。。终于找到一个方法,两次操作就能达到目的。。
具体方法是,p1p2…pk-1pk,交换p1与pk-1,那么pk-1与pk形成环,与前面的断开,这时候前面的就可以p2交换pk-2,得到p1与pk-2形成的环…

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 5, mod = 998244353;
int a[N], b[N], pos[N];
vector<int> v[N], ans[N], v2[N];
bool vis[N];
int cnt;
void dfs1(int k)
{
    vis[k] = 1;
    b[++cnt] = k;
    for (auto i : v[k]) {
        if (vis[i]) continue;
        dfs1(i);
    }
}
void dfs2(int k)
{
    vis[k] = 1;
    b[++cnt] = k;
    for (auto i : v2[k]) {
        if (vis[i]) continue;
        dfs2(i);
    }
}
signed main ()
{
    // freopen("data.in","r",stdin);
	// freopen("data.out","w",stdout);
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        scanf("%lld", &a[i]);
        pos[a[i]] = i;
    }
    for (int i = 1; i <= n; i++) {
        int x = i, y = a[i];
        if (x == y) continue;
        x = pos[x], y = pos[y];
        v[x].push_back(y);
        v[y].push_back(x);
    }
    for (int i = 1; i <= n; i++) {
        if (!vis[i]) {
            cnt = 0;
            dfs1(i);
            if (cnt == 2) {
                ans[1].push_back(b[1]);
                ans[1].push_back(b[2]);
            // cout << "?";
                continue;
            }
            if (cnt <= 1) continue;
            int l = 1, r = cnt;
            ans[1].push_back(b[1]);
            ans[1].push_back(b[cnt - 1]);
            v2[b[cnt - 1]].push_back(b[cnt]);
            v2[b[cnt]].push_back(b[cnt - 1]);
            r -= 2;
            while(l < r)
            {
                if (l + 1 < r){
                    ans[1].push_back(b[l + 1]);
                    ans[1].push_back(b[r]);
                }
                if (l != r){
                    v2[b[l]].push_back(b[r]);
                    v2[b[r]].push_back(b[l]);
                }
                l++;
                r--;
            }
        }
    }
    memset(vis, 0, sizeof(vis));
    for (int i = 1; i <= n; i++) {
        if (!vis[i]) {
            cnt = 0;
            dfs2(i);
            if (cnt == 2) {
                ans[2].push_back(b[1]);
                ans[2].push_back(b[2]);
                continue;
            }
        }
    }
    int num = 0;
    if (ans[1].size()) num = 1;
    if (ans[2].size()) num = 2;
    cout << num << "\n";
    for (int i = 1; i <= num; i++) {
        cout << ans[i].size() / 2 << " ";
        for (auto j : ans[i]) cout << j << " ";
        puts("");
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值