【模拟赛】纪中提高A组 19.8.23 测试

Posted on 2019-08-23 21:59  opethrax  阅读(...)  评论(... 编辑 收藏

Task.1 矩阵乘法

题目大意:给你一个 \(N\times N\) 的矩阵,\(Q\) 次询问每次询问一个子矩形的第 \(K\) 小数。

数据范围:\(1\leq N\leq 500,Q\leq 60000\)

树套树(主席树)、整体二分、分块。大概那么搞就能过去...但是我还不是 ds 大师。

\(Source:\) 聪明人才能看到的BZOJ 2738 Luogu P1527

代码:

Task.2 Tree

题目大意:给定一张 \(n\) 个点 \(m\) 条带权边(权值\(c\))的连通图,保证存在最小生成树。求最小标准差生成树。

数据范围:\(1\leq N\leq 100,N-1\leq M\leq 2000,c_i\leq 100\)

求最小标准差生成树...就是对于生成树的 \(n-1\) 条边要最小化这个东西:\(\sqrt{\frac{\sum(c_i-\overline{c})^2}{n-1}}\)

本质上就是最小化 \(\sum (c_i-\overline c)^2\)。接下来的操作就比较套路了。

\(f(x)=\sum (c_i-x)^2\),可以看出这个式子是把 \(x\) 当平均数对某些 \(c_i\) 求一个类似方差的东西,当 \(x=\overline c\)\(f(x)\) 等于上式。

一种比较初步的思路就是枚举一个 \(x\) 去求可能的 \(c\) 有哪些,然后对确定下来的这些 \(c\) 求标准差。我们把边按照 \((c_i-x)^2\) 排序后求“最小”的生成树就能确定 \(\overline c\) 最接近 \(x\) 时选哪些 \(c\)能最小化 \(f(x)\),不停的增加 \(x\) 同时更新答案即可。

\(Source:\) 聪明人才能看到的BZOJ 3754

代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
using namespace std;

template<class T>void read(T &x){
    x=0; char c=getchar();
    while(c<'0'||'9'<c)c=getchar();
    while('0'<=c&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
}
typedef double db;
const int N=105,M=2005;

int n,m;
int fa[N];
struct edge{int x,y,c;}e[M];
int q[N]; db ave,ans=1e9;

db sqr(db x){return x*x;}
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
bool cmp(edge e1,edge e2){return sqr(e1.c-ave)<sqr(e2.c-ave);}
void kruskal(){
    int x,y,fx,fy,cnt=0;
    db tmp=0,sum=0;
    sort(e+1,e+m+1,cmp);
    for(int i=1;i<=n;i++)fa[i]=i;
    for(int i=1;i<=m&&cnt<n;i++){
        x=e[i].x; y=e[i].y;
        if((fx=find(x))==(fy=find(y)))continue;
        fa[fx]=fy; tmp+=e[i].c; q[++cnt]=e[i].c;
    }
    if(cnt!=n-1)return ;
    tmp/=(n-1);
    for(int i=1;i<n;i++)sum+=sqr(q[i]-tmp);
    ans=min(ans,sum);
    return ;
}

int main(){
//  freopen("in","r",stdin);
    read(n); read(m);
    for(int i=1;i<=m;i++){
        read(e[i].x); read(e[i].y); read(e[i].c);
    }
    for(ave=0.25;ave<=100;ave+=0.25)kruskal();
    printf("%.4lf\n",sqrt(ans/(n-1)));
    return 0;
}

Task.3 Points and Segments

题目大意:在数轴上有 \(n\) 条线段\([l,r]\),现在要把每条线段染成红色或蓝色。求一个满足数轴上所有点被两种颜色的线段覆盖次数 \(|c_{red}-c_{blue}|\leq 1\)的一种合法的染色方案。

数据范围:\(1\leq N\leq 10^5,0\leq l_i,r_i\leq 10^9\)

吓死我了我还以为要求方案数

可以想到把染两种颜色分别看做 \(+1\ -1\),然后就是求区间加减之后结果每个位置绝对值不超过 \(1\)

然后还有一种想法是离散后建图,连接 \(l_i,r_i\) ,然后让每个位置被尽可能相等的两种颜色的边覆盖。

如果把两种思路结合一下,把建的边看成有权值的。分配权值的方法就是给边定向。就是要平衡一个点往左往右的边数。从一个点出发向一个方向走过去,再回来...若能平衡这个次数,我们就一定能找到一种定向方案。

回路?什么回路?我们发现这个东西有点像欧拉回路。

若建好图图中没有奇点的话,欧拉回路一定存在,那么我们就可以构造出一种合法方案。

若存在奇点。那么奇点的个数一定是偶数个(想想为什么)。我们从左到右枚举每一个奇点,和他之后最近的一个奇点连边把他们俩都变成偶点,再往后跳,这样就能变成上面的那种情况,再求解欧拉回路就可以了。至于奇点之间连的那些边删掉也不会使答案变得不合法(想想为什么)。

代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<vector>
using namespace std;

template<class T>void read(T &x){
    x=0; char c=getchar();
    while(c<'0'||'9'<c)c=getchar();
    while('0'<=c&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
}

const int N=200050;

int n;
struct seg{int l,r;}a[N];
int t[N],cnt;
int d[N],ans[N];
int head[N],tot=1,pos[N];
struct edge{int to,next;}e[N<<1];
int vis[N],ve[N];

void add(int x,int y){e[++tot]=(edge){y,head[x]}; head[x]=tot;}
void dfs(int x,int dep=1){
    vis[x]=1;
    for(int i=head[x],y;i;i=e[i].next){
        y=e[i].to; if(ve[i>>1])continue;
        ve[i>>1]=1; ans[pos[i>>1]]=x<y;
        dfs(y);
    }
}

int main(){
//  freopen("in","r",stdin);
    read(n);
    int x,y;
    for(int i=1;i<=n;i++){
        read(x); read(y); ++y;
        a[i]=(seg){x,y};
        t[++cnt]=x; t[++cnt]=y;
    }
    sort(t+1,t+cnt+1);
    cnt=unique(t+1,t+cnt+1)-t-1;
    for(int i=1;i<=n;i++){
        x=lower_bound(t+1,t+cnt+1,a[i].l)-t;
        y=lower_bound(t+1,t+cnt+1,a[i].r)-t;
        ++d[x]; ++d[y];
        add(x,y); add(y,x);
        pos[tot>>1]=i;
    }
    for(x=1,y=-1;x<=cnt;x++)if(d[x]&1){
        if(y==-1)y=x;
        else {add(x,y); add(y,x); y=-1;}
    }
    for(x=1;x<=cnt;x++)
        if(!vis[x])dfs(x);
    for(int i=1;i<=n;i++)printf("%d ",ans[i]);
    return 0;
}