NOIP2013提高组Day1

NOIP2013Day1

一、转圈游戏
按照题意每个人往后m个,那就是(10^k*m+x)%n,然后用快速幂就可以了

二、火柴排队
看起来很像鬼脚图,但是有点不一样,鬼脚图的上一层是固定的,但这里两层都可以动
注意到交换对于交换上面还是交换下面效果都是一样的,那么直交换下面就可以了
这样和鬼脚图就一模一样了,排序一下,重新定义一下就行了

三、货车运输
我一档档写了过来
对于30分,和七夕是一样的,只要枚举每条边作为最小的那个限重,然后判连通,不断更新即可
对于60分,修改一下上面的,对于每一个询问,二分限重来求所需的限重。限重满足线形,越小越容易实现
对于100分,是最大生成树+倍增+LCA。注意到要使每一条路径都最长,那么短的路径就可以舍去了
用倍增和LCA求两点在树上的路径上的最小权值即可
注意这可能是森林而不是一个完整的树。我把每棵树都标上了颜色来区分。如果两点的颜色不同
那么他们所在的子树也不同,就无法连通,直接输出-1即可

Code:

#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
int n,m,q;
struct node{int x,y,z;}G[50005];
bool cmp(node x,node y){return x.z>y.z;}
struct node1{int to,v;};
vector<node1>edge[10005];
struct Tree1{//造出最大生成树
    int fa[10005];
    int Find(int x){return x==fa[x]?x:fa[x]=Find(fa[x]);}
    void solve(){
        for(int i=1;i<=n;i++)fa[i]=i;
        sort(G+1,G+1+m,cmp);
        for(int i=1;i<=m;i++){
            int x=Find(G[i].x),y=Find(G[i].y);
            if(x==y)continue;
            edge[G[i].x].push_back((node1){G[i].y,G[i].z});
            edge[G[i].y].push_back((node1){G[i].x,G[i].z});
            fa[x]=y;
        }
    }
}Tree;
int fa[17][10005],dep[10005],dis[17][10005],co[10005],col;
struct Init1{//预处理倍增的东西
    bool Q[10005];
    void f(int x,int fa1){
        dep[x]=dep[fa1]+1;fa[0][x]=fa1;Q[x]=1;co[x]=col;
        for(int i=0;i<(int)edge[x].size();i++){
            int y=edge[x][i].to,v=edge[x][i].v;
            if(fa1==y)continue;
            dis[0][y]=v;
            f(y,x);
        }
    }
    void Init(){//倍增
        for(int j=1;j<17;j++)
            for(int i=1;i<=n;i++){
                fa[j][i]=fa[j-1][fa[j-1][i]];
                dis[j][i]=min(dis[j-1][i],dis[j-1][fa[j-1][i]]);
            }
    }
    void solve(){
        col=1;
        for(int i=1;i<=n;i++)if(Q[i]==0)f(i,0),col++;
        Init();
    }
}Init;
int LCA(int x,int y){//LCA
    if(dep[x]>dep[y])swap(x,y);
    int step=dep[y]-dep[x];
    for(int i=0;i<17;i++)
        if(step&(1<<i))y=fa[i][y];
    if(x==y)return x;
    for(int i=16;i>=0;i--)
        if(fa[i][x]!=fa[i][y])
            x=fa[i][x],y=fa[i][y];
    return fa[0][x];
}
int work(int x,int y){//跳着向上
    int step=dep[x]-dep[y],mn=1e9;
    for(int i=16;i>=0;i--)
        if(step&(1<<i))
            mn=min(mn,dis[i][x]),x=fa[i][x];
    return mn;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)scanf("%d%d%d",&G[i].x,&G[i].y,&G[i].z);
    Tree.solve();//建树
    Init.solve();//倍增
    scanf("%d",&q);
    for(int i=1;i<=q;i++){//每个点的询问
        int x,y;
        scanf("%d%d",&x,&y);
        if(co[x]!=co[y])puts("-1");
        else{
            int lca=LCA(x,y);
            int L=work(x,lca),R=work(y,lca);
            printf("%d\n",min(L,R));
        }
    }
    return 0;
}

一次最简单的考试。一半的人AK了。所以也不想写了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值