牛客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]);
}
原文地址:https://www.cnblogs.com/Tieechal/p/11378706.html