牛客NOIP暑期七天营-TG1 赛后题解
牛客NOIP暑期七天营-提高组1
A-最短路
题目描述 link
题解
有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",n-1);
for(int i=2;i<=n;i++){
printf("%d %d %d\n",u[i],v[i],d[i]);
}
}
B-最小生成链
题目描述 link
题解
做法一(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[]\)表示二进制位)
\begin{array}{c}
bitx[j]==bity[j] &&(j比i的位更高)\\
bitx[j]>bity[j] &&(j在i这一位)
\end{array}
\right.
\]
那么我们就可以判定\(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\n",ans);
}
C-最小字典最短路
题目描述 link
题解
做法一(离线+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\n",ans[i]);
}
牛客NOIP暑期七天营-TG1 赛后题解的更多相关文章
- 牛客NOIP暑期七天营-TG3 赛后题解
目录 牛客NOIP暑期七天营-提高组3 A-破碎的矩阵 题目描述 link 题解 代码 B-点与面 题目描述 link 题解 代码 C-信息传递 题目描述 link 题解 牛客NOIP暑期七天营-提高 ...
- 牛客NOIP暑期七天营-提高组1
牛客NOIP暑期七天营-提高组1 链接 A 边权可为0就排序建一条链子. 但是边权不为0 除了第一个有0的不行. x连向上一个比他小的数. 期间判断有无解. #include <bits/std ...
- 牛客NOIP暑期七天营-普及组2D
链接:https://ac.nowcoder.com/acm/contest/926/D来源:牛客网 在一维坐标系中,给定 n条有颜色的线段,第 i条线段的左右端点分别为 li和 ri,此外它的颜 ...
- 牛客NOIP暑期七天营-提高组6
目录 A-积木大赛 题目描述 link 题解 代码 B-破碎的序列 题目描述 link 题解 C-分班问题 题目描述 link 题解 比赛链接 官方题解 A-积木大赛 题目描述 link 题解 标签: ...
- 牛客NOIP暑期七天营-提高组5+普及组5
————提高组———— 第一题:deco的abs 题目链接:https://ac.nowcoder.com/acm/contest/934/A 因为每个数都可以加任意次 d ,所以可以推出 0 < ...
- 牛客NOIP暑期七天营-提高组3
第一题:破碎的矩阵 题目链接:https://ac.nowcoder.com/acm/contest/932/A 刚看到这题的时候感觉特别熟悉...诶,这不就是codeforces某场比赛的某某 ...
- 牛客NOIP暑期七天营-提高组2
第一题:ACGT 题目链接:https://ac.nowcoder.com/acm/contest/931/A trie树.hash.map遍历 ①.trie树上的节点多记一个rest值表示还有多少 ...
- 牛客NOIP暑期七天营-提高组6C:分班问题 (组合数)
题意:A班有N个人,B班有M个人,现在要组成一个新的班级C班,为了公平,从AB班各抽相同人数的人. 现在求所有方案中,人数之和是多少. 思路:即求Σ k*C(N,k)*C(M,k); 先忽略这个 ...
- 牛客NOIP暑期七天营-提高组5
A:deco的abs. 水题,先%,然后相邻两个数min()一下差值. #include<bits/stdc++.h> #define ll long long using namespa ...
随机推荐
- HTML 5 基础
HTML 参考手册 HTML 5 视频 controls 属性供添加播放.暂停和音量控件. <video src="movie.ogg" width="320&qu ...
- JMeter 返回Json数据提取方法
JMeter中,对response返回JSON格式的数据进行处理,格式如下: { "code":2000, "message":"success&qu ...
- 0908CSP-S模拟测试赛后总结
我早就料到昨天会考两场2333 话说老师终于给模拟赛改名了啊. 距离NOIP祭日还有60天hhh. 以上是废话. %%%DeepinC无敌神 -rank1 zkt神.kx神.动动神 -rank2 有钱 ...
- ul列表元素在float:right后li元素倒转
发现对li元素进行float:right后,虽然成功右浮动,但是的元素是倒转的 解决方案: 对ul进行右浮动,然后对li左浮动 结果
- Python第二课-输入输出
name = input() 输入的字符串已经赋值给变量name print() 输出内容 print(,) print中,连接字符串相当于空格
- C# EF去除重复列DistinctBy
在网上看了LinQ有DistinctBy方法,实际在用的时候并没有找到,后来参照了该网站才发现写的是拓展方法 https://blog.csdn.net/c1113072394/article/det ...
- Ubuntu环境下安装Scala以及安装IntelliJ Scala插件(Plugin)
一.Scala介绍 1.结合Spark处理大数据 这是Scala的一个主要应用,而且Spark也是那Scala写的. 2.Java的脚本语言版 可以直接写Scala的脚本,也可以在.sh直接使用Sc ...
- 确认(confirm 消息对话框)语法:confirm(str); 消息对话框通常用于允许用户做选择的动作,如:“你对吗?”等。弹出对话框(包括一个确定按钮和一个取消按钮)
确认(confirm 消息对话框) confirm 消息对话框通常用于允许用户做选择的动作,如:"你对吗?"等.弹出对话框(包括一个确定按钮和一个取消按钮). 语法: confir ...
- spring自定义bean工厂模式解耦
在resources下创建bean.properties accountService=cn.flypig666.service.impl.AccountServiceImpl accountDao= ...
- NOIP2018游记 & 退役记
NOIP2018游记 & 退役记 我是一名来自湖北武汉华中师大一附中的高二\(OIer\),学习\(OI\)一年,今年去参加\(NOIP\),然后退役.这是一篇\(NOIP2018\)的游记, ...