2018.6.7HGOI模拟赛题解

大概是水逆吧大早上在洛谷上抽到大凶然后今天果然爆零令人心情难受。

第一题——圆排列

  • 讲道理这道题拿到手,第一感觉是dp题(其实正解也是这样子的),但发现好像暴力枚举+贪心也ok(数据真的炒鸡弱)。

  • 一个暴力的算法。对于有序递增数列,他的最大差值是 ana1 a n − a 1 ,那从这里可以开始进行枚举了。

    举例:
    首先:存在差值最小必定为a1,a3,a5…an…a4,a2;
    其次需要有字典序最小的解那么就存在 a1,a2,a4….an…a3;
    从这里可以看出,序列是由单调递增序列和单调递减序列组成,而可以通过暴力枚举可以求出delta/min值(其实也可以从这里看出是双峰dp的)
    然后再根据delta/min求出字典序最小的合法解。

  • 怎么求它的序列呢..此处感谢excitedfrog大佬给出算法。

  • 对于递增序列,首先i指向1,j向后扫描,当j-i>delta时,j-1放到数列末尾,而i指向j-1继续扫描,可以求出合法情况下最优情况
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <cmath>
#include <iomanip>
using namespace std;
void fff(){
    freopen("heightround.in","r",stdin);
    freopen("heightround.out","w",stdout);
}
const int MAXN=200;
int n;
int a[MAXN],b[MAXN],ans[MAXN];
int main(){
    fff();
    int ng;
    scanf("%d",&ng);
    while (ng--){
        memset(ans,0,sizeof(ans));
        memset(b,0,sizeof(b));
        memset(a,0,sizeof(a));
        scanf("%d",&n);
        for (int i=1;i<=n;i++){
            scanf("%d",&a[i]);
        }
        sort(a+1,a+n+1);
        int minn=a[n]-a[1];
        for (int ss=1;ss<n;ss++){
            for (int i=1;i<=ss;i++){
                b[i]=a[i];
            }
            int h=ss+1,t=n;
            bool w=false;
            for (int i=ss+1;i<=n;i++){
                 if(w){
                    b[h++]=a[i];
                 }else{
                    b[t--]=a[i];
                 }
                 w=!w;
            }
            int delta=b[n]-b[1];
            for (int i=2;i<=n;i++){
                delta=max(delta,abs(b[i]-b[i-1]));
            }
            if(delta<minn){
                minn=delta;
            }
        }
        int temp=a[1],size=n,i=2;
        while (i<=n){
            if(a[i]-temp>minn){
                ans[size--]=a[i-1];
                temp=a[i-1];
                a[i-1]=0;
            }
            i++;
        }
        for (i=1;i<=n;i++){
            if(a[i]!=0) printf("%d ",a[i]);
        }
        size++;
        for (;size<=n;size++){
            printf("%d ",ans[size]);
        }
        printf("\n");
    }
}

第二题——彩色

  • 一道矩形面积并的题但是刚开始没有想出来算法就直接放弃了,其实很简单的。
  • 读取所有矩形信息后进行离散化和点去重操作
  • 再从最后一个矩形向前枚举,用map标记离散化后的小分块有没有被操作过,如果没有,那么这一块的面积就加入当前的矩形露出的面积。
  • 最后在对这些矩形排个序取前最大k项就好了。数据也是很水但算法有点点小难度。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <cmath>
#include <iomanip>
#define INF 0xffffff
using namespace std;
void fff(){
    freopen("color.in","r",stdin);
    freopen("color.out","w",stdout);
}
const int MAXN=51;
struct node{
    int x1,x2,y1,y2;
    int id;
    int s;
    bool operator < (const node x) const{
        if(s==x.s) return id>x.id;
        return s<x.s;
    }
}a[MAXN];
int n,k,x_max,x_min;
vector <int> x,y;
int ans[MAXN];
bool map[1000][1000];
int main(){
    fff();
    scanf("%d%d",&n,&k);
    for (int i=0;i<n;i++){
        cin>>a[i].x1>>a[i].y1>>a[i].x2>>a[i].y2;
        a[i].id=i;
        x.push_back(a[i].x1);
        x.push_back(a[i].x2);
        y.push_back(a[i].y1);
        y.push_back(a[i].y2);
    }
    sort(x.begin(),x.end());
    sort(y.begin(),y.end());
    unique(x.begin(),x.end());
    unique(y.begin(),y.end());
    int tx=x.size(),ty=y.size();
    for (int s=n-1;s>=0;s--){
        int tempx1=0,tempx2=0,tempy1=0,tempy2=0;
        for (int i=tx-1;i>=0;i--){
            if(x[i]==a[s].x1){
                tempx1=i;
            }
            if(x[i]==a[s].x2){
                tempx2=i;
            }
        }
        for (int i=ty-1;i>=0;i--){
            if(y[i]==a[s].y1){
                tempy1=i;
            }
            if(y[i]==a[s].y2){
                tempy2=i;
            }
        }
        for (int i=tempx1;i<tempx2;i++){
            for (int j=tempy1;j<tempy2;j++){
                if(!map[i][j]){
                    map[i][j]=true;
                    a[s].s+=(x[i+1]-x[i])*(y[i+1]-y[i]);
                }
            }
        }
    }
    sort(a,a+n);
    int t=k;
    for (int i=n-t;i<n;i++) ans[k--]=a[i].id;
    sort(ans+1,ans+t+1);
    for (int i=1;i<=t;i++) printf("%d ",ans[i]);
}

第三题——联络

  • 这是一道看数据和逻辑就让你怕的题。
  • 从逻辑上来看,两个互不相识的人肯定是在同一个集合,而相互认识的人不一定在不同的集合(可能A不认识C,B不认识C,那A和B就在一起了!)
先讲60分的算法。
  • 反正就是读图嘛,建立to[i]数组表示i所指向的数,再添上0和n+1两个点(其实不存在的),进行排序,对于(to[i],to[i+1])区间内的所有元素都是不认识的,那就联通(并查集),最后再看下有多少不同的联通块就好了。
  • 然而这数据实在是太大了,那就需要换个高端点的算法。
100分算法
  • 基于上面算法,反正都是要联通的,那能不能减少枚举的点数呢。那么可以找出边最少的点K,找出和它没有相连的点合并到一个集合(还是并查集实现,O(M)的时间扫一遍所有边)。这样就可以把很多点缩成一个点。而对于缩点内部的点再枚举判断进行并查集,最后还是和上面一样扫一遍判断集合哥舒就好啦。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <cmath>
#include <iomanip>
#include <set>
using namespace std;
int n,m;
const int MAXN=100010;
int father[MAXN],rnk[MAXN];
int num;
bool visited[MAXN];
int degree[MAXN],pool[MAXN],map[3010][3010],p[MAXN],hash[MAXN];
int find(int x){
    if(x!=father[x]) return find(father[x]);
    return father[x];
}
vector <int> to[MAXN];
void merge(int x,int y){
    x=find(x),y=find(y);
    if(x==y) return;
    if (rnk[x]<rnk[y])
        father[x] = father[y];
    else
    {
        father[y] = father[x];
        if (rnk[x] == rnk[y])
            rnk[x]++;
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) father[i]=i;
    for (int i=1;i<=m;i++){
        int f,t;
        scanf("%d%d",&f,&t);
        to[f].push_back(t);
        to[t].push_back(f);
        degree[t]++;
        degree[f]++;
    }
    int k=1;
    for (int i=2;i<=n;i++){
        if(degree[i]<degree[k]) k=i;
    }
    int t=to[k].size();
    int cnt=1;
    for (int i=0;i<t;i++){
        visited[to[k][i]]=true;
    }
    for (int i=1;i<=n;i++){
        if(!visited[i]){
            num++;
            pool[i]=1;
        }else pool[i]=++cnt;
    }
    for (int i=1;i<=n;i++){
        t=to[i].size();
        for (int j=0;j<t;j++){
            if(pool[i]!=pool[to[i][j]])
                map[pool[i]][pool[to[i][j]]]++;
        }
    }
    for (int i=1;i<=cnt;i++){
        for (int j=1;j<=cnt;j++){
            if(i!=j){
                if(j==1&&map[i][j]<num) merge(j,i);
                if(j!=1&&!map[i][j]) merge(i,j);
            }
        }
    }
    for (int i=1;i<=cnt;i++) p[find(i)]++;
    p[1]+=num-1;
    int ans=0;
    for (int i=1;i<=cnt;i++){
        if(p[i]) hash[++ans]=p[i];
    }
    sort(hash+1,hash+ans+1);
    printf("%d\n",ans);
    for (int i=1;i<=ans;i++){
        printf("%d ",hash[i]);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值