• 牛客NOIP暑期七天营-TG1 赛后题解


    牛客NOIP暑期七天营-提高组1

    比赛链接

    官方题解

    A-最短路

    题解

    有Special Judge,只要构造一下就好了。一种较简单的构造想法:将每个点按照它离源点的距离排序,你可以想象是一条类似链的东西,但是,又不一定是链,当存在大于等于2个的点离1的距离相同,就会形成分叉。其实就是将距离较大的点连在前一个距离较小的点上。

    注意特判细节。

    代码

    #include<bits/stdc++.h>
    using namespace std;
    #define debug(x) cout<<"### "<<x<<endl;
    #define For(a,b,c) for(int a=b;a<=c;++a)
    #define Dor(a,b,c) for(int a=b;a>=c;--a)
    inline int read(){
        int x=0;char c=getchar();
        while(c<'0'||c>'9')c=getchar();
        while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
        return x;
    }
    const int N=50100;
    struct node{
    	int s,id;
    }x[N];
    inline bool cmp(node a,node b){
    	return a.s<b.s;
    }
    int u[N],v[N],d[N];
    int n,lim;
    int main(){
    	n=read(),lim=read();
    	for(int i=1;i<=n;i++)x[i].s=read(),x[i].id=i;
    	sort(x+1,x+n+1,cmp);
    	int lst=0,who=1;
    	for(int i=2;i<=n;i++){
    		u[i]=who,v[i]=x[i].id;
    		d[i]=x[i].s-lst;
    		if(d[i]>lim){puts("-1");return 0;}
    		lst=x[i].s;who=x[i].id;
    	}
    	printf("%d
    ",n-1);
    	for(int i=2;i<=n;i++){
    		printf("%d %d %d
    ",u[i],v[i],d[i]);
    	}
    }
    

    B-最小生成链

    题解

    做法一(60%数据)

    需要证明一个性质:

    命题:最小生成链的最大边权就是最小生成树的最大边权

    证明:见官方题解,有点玄学??

    这样子直接强上(O(N))最小生成树即可,但由于前面需要(O(N^2))预处理出两两边权,故只能跑(n<=1000)的情况。

    比赛的时候还特判了一下点的度以保证链的形态,结果Wa了qwq。

    做法二(100%数据)

    很棒的一个思维题,其实一开始看到这个异或值马上联想到了01Trie,但是感觉用不上就没想了。

    这道题看似是图论,其实我们可以将问题进行转化:

    给定序列(a[1..n]),请你给出该序列的一个排列,使得序列中相邻元素的两两异或值的最大值最小。

    根据链的形态很容易理解上面的这个转化。这样子就变成了一道巧妙的思维题2333。

    从哪下手呢?注意到这样一个特殊情况,当(a[1..n]=0/1)时,如果该序列元素全部为0或全部为1时,那么答案很明显是0;反之如果既有0也有1,那么答案必然是1。理解一下。

    相同的,我们可以将每个数的二进制位独立开来考虑,因为不同的二进制位之间两两没有直接联系。

    现在问题还是没有解决,如何使得最大异或值最小呢?

    再来回想一下二进制数,随便拿两个二进制数((x)_2)((y)_2)(下面的位指的都是二进制位,且位越大指的是该位越高),如果对于x的某一位(i)满足:((bit[])表示二进制位)

    [left{ egin{array}{c} bitx[j]==bity[j] &&(j比i的位更高)\ bitx[j]>bity[j] &&(j在i这一位) end{array} ight. ]

    那么我们就可以判定(x>y),根本不用去看比i低的位。原因的话大家都会推(2^i=2^{i-1}+2^{i-2}...2^0+1)

    有了这个性质的话,我们尝试直接去找一个二进制位(mabit),它满足:这n个数在它这一位既有0又有1,且在满足该条件的情况下这一位最大。为什么要去找这个东西??上面说了,当序列中全是0或全是1时,答案必然为0,而当序列中有0有1时,答案必然为1。我们可以根据这一位为1还是0将这n个数分成两部分(Part0,Part1),不妨令Part0中的数这一位上全是0。

    那么在(part0)内部之间,不论他们怎么排列,相邻两元素的异或值在(mabit)这一位上都不会出现1,两个0还能发生什么呢quq。相同的在(part1)内部之间,相邻元素的异或值在(mabit)这一位上都不出现1。再看看上面那个显而易见的二进制数的性质,就会发现,此时异或值的最大值只可能由一个(part0)、和一个(part1)结合而成。

    由此,得到一种排列方案,(part0)的排在一边,(part1)的排在另一边,异或值的最大值由两者交界处的那两个完成。那现在只需要从(part0)中选出一个,从(part1)中选出一个,把他们放在交界处,然后这个序列的异或最大值就产生了!!

    问题是怎么选,令这个值尽量小??当然可以(O(N^2))的暴力枚举选啦,但是想了这么久还是(O(N^2))就太low了。既然是01的二进制,就直接上(01Trie)就好了,把其中一个part的插入,另一个part中的元素在Trie上进行询问最小值即可。

    综上总的时间复杂度为(O(N) (loga_i)),其中(loga_i)大概为60。

    代码

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1e6+10;
    typedef long long ll;
    
    ll a[N];
    int n,ch[N*60][2],node_cnt;
    inline void insert(ll x){
    	int u=0;
    	for(int i=60;i>=0;i--){
    		int to=(x&(1ll<<i))?1:0;
    		if(!ch[u][to])ch[u][to]=++node_cnt;
    		u=ch[u][to];
    	}
    }
    inline ll ask(ll x){//查询与x进行Xor的最小值 
    	int u=0;ll res=0;
    	for(int i=60;i>=0;i--){
    		int to=(x&(1ll<<i))?1:0;
    		if(ch[u][to])u=ch[u][to];
    		else{
    			res|=1ll<<i;
    			u=ch[u][to^1];
    		}
    	}
    	return res;
    }
    int main(){
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
    	int mabit=-1;//对于a,在mabit这一位上有1有0 
    	for(int i=60;i>=0;i--){
    		ll sta=1ll<<i;bool p1=0,p2=0;
    		for(int j=1;j<=n;j++){
    			if(a[j]&sta)p1=1;
    			else p2=1;
    		}
    		if(p1&&p2){mabit=i;break;}
    	}
    	if(mabit==-1){puts("0");return 0;}
    	
    	for(int i=1;i<=n;i++)if(!(a[i]&(1ll<<mabit)))insert(a[i]);
    
    	//对于这一位为0的,在trie上查询min
    	ll ans=(1ll<<60);
    	for(int i=1;i<=n;i++)if((a[i]&(1ll<<mabit))){
    		ans=min(ans,ask(a[i]));
    	}
    	printf("%lld
    ",ans);
    }
    

    C-最小字典最短路

    题解

    做法一(离线+SPFA+构建最短路图+LCA)

    before:这个做法最多可以拿到85分。

    打比赛的时候觉得它定义的这个最小字典序很蜜汁,感觉处理起来有点麻烦,就打了个打暴力,结果忘了(Dijkstra)不能跑负边!!(Dijkstra)不能跑负边!!(Dijkstra)不能跑负边!!大样例没调出来(之前一直记得是不能跑负环来着??)。成功的爆Wa。赛后换成了(SPFA)拿到了55分。。

    赛时的大暴力就不赘述了。对于85分的这个稍微不那么暴力的算法还是bb几句。

    考虑离线询问,然后再枚举每一个点作为源点时对询问的贡献,枚举(O(N)),内部的统计方法的伪代码如下:

    void calc(int s){
        以s为源点跑一遍spfa()处理出每个点到s的最短路
        构建最短路图——其实是一棵树();
        for(对于每个询问(u,v)){
            ans[i]=max(ans[i],dis[LCA(u,v)]);
        }//至于为什么是LCA离s的距离画个图就知道了
    }
    

    现在解决上面的那个很睿智的问题,怎么搞最小字典序(最好看一下题面,跟平时的定义有点出入),可以直接在存边的时候解决了!!如果用的(vector)存边的话直接(sort(v.begin(),v.end(),cmp))即可,排序依据点的编号从小到大即可。如果用的前向星存边的话需要从大到小排序,然后按此顺序连边即可——因为前向星是倒着存边的。

    综上,在随机情况下,这个方法总的时间复杂度为(O(N*(N+NlogN+Q)))。当然,只是在随机情况下跑会快的嗷嗷叫,但是由于最后几个点卡了SPFA所以。。

    完整代码

    Update

    本题的100%数据做法其实和上面一模一样。

    上面做法的瓶颈在于不随机情况下SPFA可以被卡成(O(NM)),但是Dijkstra又不能跑负边权。

    官方题解中介绍了一种解决方案——(Johnson全源最短路算法)

    可参考这篇博客,大致思路是先跑一遍(SPFA),然后对边重新赋值(Re_weight),使得边权都不为负,然后就可以利用Dijkstra(O(N*NlogN))的时间内得到全源最短路。之后的东西跟上面操作基本一致。实现起来细节还是挺多的,而且上面给的这篇博客貌似没有提到Re_weight后还要重新计算距离,具体实现可以看下面AC代码:

    /*
    下面的100分代码是由上面85分代码优化来的:
    主要是在求最短路时利用Johnson Re-weight+dijkstra代替了上面的SPFA
    还用了手写堆,树剖LCA等卡卡常. 
    */
    #include<bits/stdc++.h>
    #define For(a,b,c) for(register int a=b;a<=c;++a)
    #define Dor(a,b,c) for(register int a=b;a>=c;--a)
    using namespace std;
    const int N=2010,M=6010,INF=1e8+10;
    
    int dep[N],f[N],sz[N],tp[N],son[N];
    int dis[N],h[N];
    int ans[M];
    bool vis[N];
    
    struct node{
        int w,d;
        bool operator<(const node &x)const{return d>x.d;}
    };
    
    struct edge{int to,c;};
    inline bool cmp(edge p,edge q){return p.to<q.to;}
    vector<edge>e[N];
    
    vector<int>g[N];
    queue<int>qq;
    struct Heap{
        int cnt;
        node x[N<<1];
        void push(node a){
            x[++cnt]=a;
            int i=cnt;
            while(x[i>>1]<a&&(i>>1)>0)swap(x[i],x[i>>1]),i>>=1;
        }
        bool empty(){return cnt==0;}
        node top(){return x[1];}
        void pop(){
        	x[1]=x[cnt--];
            int i=1;
            while((i<<1)<=cnt){
                int mx=i;
                if((i<<1)<=cnt&&x[mx]<x[i<<1])mx=i<<1;
                if((i<<1|1)<=cnt&&x[mx]<x[i<<1|1])mx=i<<1|1;
                if(mx==i)break;
                swap(x[i],x[mx]),i=mx;
            }
        }
    }Q;
    int n;
    void spfa(int s){
    	For(i,1,n)h[i]=INF;
        h[s]=0;
        qq.push(s);
        while(!qq.empty()){
            int x=qq.front();
            qq.pop();
            vis[x]=0;
            for(int i=0;i<e[x].size();i++){
                int y=e[x][i].to,c=e[x][i].c;
                if(h[y]>h[x]+c){
                    h[y]=h[x]+c;
                    if(!vis[y])qq.push(y),vis[y]=1;
                }
            }
        }
    }
    void pre(){
    	For(i,1,n)e[0].push_back({i,0});
        spfa(0);
        For(i,1,n)for(int j=0;j<e[i].size();j++)e[i][j].c+=h[i]-h[e[i][j].to];
    }
    void dij(int s){
    	For(i,1,n)dis[i]=INF;
    	dis[s]=0;
    	Q.push((node){s,0});
        while(!Q.empty()){
            node x=Q.top();Q.pop();
            int u=x.w;
            if(dis[u]<x.d)continue;
            for(int i=0;i<e[u].size();i++){
                int y=e[u][i].to,c=e[u][i].c;
                if(dis[y]>x.d+c){
                    dis[y]=x.d+c;
                    Q.push({y,dis[y]});
                }
            }
        }
    }
    void build(int x){
        sz[x]=1,son[x]=0;
        for(int i=0;i<e[x].size();i++){
            int y=e[x][i].to,c=e[x][i].c;
            if(dis[x]+c==dis[y]&&!f[y]){
                g[x].push_back(y);
                f[y]=x;dep[y]=dep[x]+1;
                build(y);
                sz[x]+=sz[y];
                if(sz[son[x]]<sz[y])son[x]=y;
            }
        }
    }
    void go(int x,int top){
        tp[x]=top;if(son[x])go(son[x],top);
        for(int i=0;i<g[x].size();i++){
            int y=g[x][i];
            if(y!=son[x])go(y,y);
        }
    }
    int LCA(int a,int b){
        while(tp[a]!=tp[b]){
            if(dep[tp[a]]<dep[tp[b]])swap(a,b);
            a=f[tp[a]];
        }
        return dep[a]<dep[b]?a:b;
    }
    int qu[M],qv[M];
    int main() {
        memset(ans,-1,sizeof(ans));
        int m,q;
        scanf("%d%d%d",&n,&m,&q);
        For(i,1,m){
            int u,v,c;scanf("%d%d%d",&u,&v,&c);
            e[u].push_back({v,c});
        }
        For(i,1,q)scanf("%d%d",&qu[i],&qv[i]);
        
        pre();
        
    	For(i,1,n)sort(e[i].begin(),e[i].end(),cmp);
        For(i,1,n){
    	    dij(i);
    		For(j,1,n)f[j]=dep[j]=0,g[j].clear();
            dep[i]=1;
            build(i);
            go(i,i);
            For(j,1,q){
                int a=qu[j],b=qv[j];
                if(!dep[a]||!dep[b])continue;
                int lca=LCA(a,b);
                ans[j]=max(ans[j],dis[lca]+h[lca]-h[i]);
            }
        }
        For(i,1,q)printf("%d
    ",ans[i]);
    }
    
  • 相关阅读:
    asp.net mvc上传图片案例
    kafka 常用参数
    play framework 笔记
    调试 kafka manager 源码
    kafka AdminClient 闲时关闭连接
    kafka 心跳和 rebalance
    kafka producer batch 发送消息
    kafka producer 发送消息简介
    zk 的配额
    kafka consumer 指定 offset,进行消息回溯
  • 原文地址:https://www.cnblogs.com/Tieechal/p/11378706.html
Copyright © 2020-2023  润新知