T1 序列
一道超级细节的模拟题,需要注意很多细节。我分了三类,写了100+行,而hzy大佬才写了50+行。其实很简单。
直接贴代码吧:
#include<cstdio>
#include<algorithm>
#define maxn 100005
using namespace std;
int n;
int a[maxn],b[maxn],ans[maxn];
int tonga[maxn],tongb[maxn];
int pos[maxn];
int cnta,cntb,vala,valb,lesa,lesb,tot;
int max(int x,int y)
{
return x>y?x:y;
}
int min(int x,int y)
{
return x<y?x:y;
}
int main()
{
//freopen("seq.in","r",stdin);
//freopen("seq.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%d",&a[i]);
if(!tonga[a[i]])
cnta++;
tonga[a[i]]++;//打标记
}
for(int i=1;i<=n;++i)
{
scanf("%d",&b[i]);
if(!tongb[b[i]])
cntb++;
tongb[b[i]]++;
}
if(cnta!=n-1||cntb!=n-1)
{
printf("Impossible");
return 0;
}
for(int i=1;i<=n;++i)
{
if(a[i]!=b[i])
pos[++tot]=i;//记录不同数的坐标
else
ans[i]=a[i];
if(tonga[i]==2)vala=i;
if(tongb[i]==2)valb=i;//记录a,b重复的数
if(tonga[i]==0)lesa=i;
if(tongb[i]==0)lesb=i;//标记a,b缺少的数
}
if(tot>=3)//不同的数的个数不能超过3
{
printf("Impossible");
return 0;
}
if(tot==2)
{
if(lesa!=lesb)
{
ans[pos[1]]=min(lesa,lesb);
ans[pos[2]]=max(lesa,lesb);
if((ans[pos[1]]!=b[pos[1]]&&ans[pos[2]]!=b[pos[2]])||(ans[pos[1]]!=a[pos[1]]&&ans[pos[2]]!=a[pos[2]]))
{
swap(ans[pos[1]],ans[pos[2]]);
}
}
else
{
printf("Impossible");
return 0;
}
}
if(tot==1)
ans[pos[1]]=lesa;
if(tot==0)
{
if(vala<lesa)
{
for(int i=n;i>=1;--i)
{
if(ans[i]==vala)
{
ans[i]=lesa;
break;
}
}
}
else
{
for(int i=1;i<=n;++i)
{
if(ans[i]==vala)
{
ans[i]=lesa;
break;
}
}
}
}
for(int i=1;i<=n;++i)
printf("%d\n",ans[i]);
return 0;
}
T2 工作
由于打卡机到公司的距离是不变的,我们只需要考虑人到打卡机的距离。把人和打卡机单独拎出来,画在数轴上:
因为影响答案的是上班时间的最大值,所以将人和打卡机连接起来,若不出现交点一定是更优的,如图:
上图一定比下图更优。
因为答案满足二分性,所以我们可以二分答案。先把人和机器的坐标排序,再二分一个答案,在check时贪心的枚举人,从1开始枚举机器,计算人—>机器—>公司的距离,若大于枚举的答案,则枚举下一个机器;反之,枚举下一个人。当枚举的机器编号大于m时跳出循环。若此时枚举到的人刚好等于n+1(即所有人都有机器时),返回1,反之返回0.
注:求距离时一定要用公式。其他细节见代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define maxn 100005
using namespace std;
ll n,m,pos;
ll peo[maxn],mac[maxn];
ll check(ll x)
{
ll i,cnt=1;
for(i=1;i<=n;++i)
{
ll ret=abs(peo[i]-mac[cnt])+abs(pos-mac[cnt]);
while(ret>x&&cnt<=m)
{
cnt++;
ret=abs(peo[i]-mac[cnt])+abs(pos-mac[cnt]);
}
if(cnt>m)break;
cnt++;
}
return (i==n+1);
}
int main()
{
//freopen("work.in","r",stdin);
//freopen("work.out","w",stdout);
scanf("%lld%lld%lld",&n,&m,&pos);
for(ll i=1;i<=n;++i)
scanf("%lld",&peo[i]);
for(ll i=1;i<=m;++i)
scanf("%lld",&mac[i]);
sort(peo+1,peo+1+n);
sort(mac+1,mac+1+m);
ll l=0,r=2e9+1e8;
while(l<=r)
{
ll mid=(l+r)>>1;
if(check(mid))r=mid-1;
else l=mid+1;
}//二分答案
printf("%lld",l);
return 0;
}
/*
2 2 10
9 11
15 7
*/
T3 最小生成树
名字叫最小生成树,但算法与最小生成树关系不大。先求出一颗最小生成树,给树边打上标记。再枚举边,对于非树边,若其连接的节点是(u,v)那么该边的答案就是链(u,v)上的最大边权-1,用树链剖分处理即可;对于树边,该边的答案是包含它的非树边的最小值-1,画个图理解下(黑色是树边,绿色是非树边,黑数字是边的权值,红数字是答案):
不信请自行验证。
所以我们可以只枚举非树边,先用树链剖分更新非树边的答案,再找到非树边连接的两点(u,v)的lca点p,从u到p更新树边的答案,再从v到p更新树边的答案。注意,因为我们以事先对边权排过序了,所以更新答案的时候不用取最小值。
细节:因为给的是边权,所以要边权下放。更重要的是,求两点间最大值时不能考虑lca的值,向上更新树边答案时也不能更新lca以及其父节点边的值(在这里我查错了一个多小时)。
更多细节见代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 100005
#define lid (k<<1)
#define rid (k<<1|1)
using namespace std;
struct node
{
int u,v,val,flag,num;
}po[maxn];
struct tree
{
int v,val,next,num;
}tr[maxn<<1];
struct poo
{
int l,r,mx;
}pool[maxn*4];
int n,m,cnt;
int tot,head[maxn];
int fa[maxn],val[maxn];
int idc,size[maxn],dep[maxn],f[maxn],son[maxn];
int top[maxn],in[maxn],out[maxn],seq[maxn];//树链剖分数组
int flag,ans[maxn];
bool cmp(node x,node y)
{
return x.val<y.val;
}
int find(int x)
{
if(fa[x]!=x)return fa[x]=find(fa[x]);
return x;
}
void unionn(int x,int y)
{
x=find(x);y=find(y);
fa[x]=y;
}
int judge(int x,int y)
{
x=find(x);y=find(y);
return (x==y)?1:0;
}
void add(int x,int y,int z,int nu)
{
tot++;
tr[tot].v=y;
tr[tot].val=z;
tr[tot].next=head[x];
tr[tot].num=nu;
head[x]=tot;
}
void dfs1(int x)
{
size[x]=1;
for(int t=head[x];t;t=tr[t].next)
{
int y=tr[t].v,z=tr[t].val;
if(y==f[x])continue;
dep[y]=dep[x]+1;
f[y]=x;
val[y]=z;
dfs1(y);
size[x]+=size[y];
if(size[y]>size[son[x]])son[x]=y;
}
}
void dfs2(int x,int tp)
{
in[x]=++idc;
seq[idc]=x;
top[x]=tp;
if(son[x])
dfs2(son[x],tp);
for(int t=head[x];t;t=tr[t].next)
{
int y=tr[t].v;
if(y==son[x]||y==f[x])continue;
dfs2(y,y);
}
out[x]=idc;
}
void build(int k,int l,int r)
{
pool[k].l=l;
pool[k].r=r;
if(l==r)
{
pool[k].mx=val[seq[l]];
return;
}
int mid=(l+r)>>1;
build(lid,l,mid);
build(rid,mid+1,r);
pool[k].mx=max(pool[lid].mx,pool[rid].mx);
}//树链剖分
int query(int k,int l,int r)
{
if(pool[k].l==pool[k].r)
return pool[k].mx;
int ret=0,mid=(pool[k].l+pool[k].r)>>1;
if(l<=mid)
ret=max(ret,query(lid,l,r));
if(r>mid)
ret=max(ret,query(rid,l,r));
return ret;
}
int lca(int x,int y)
{
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]])swap(x,y);
x=f[top[x]];
}
return dep[x]<dep[y]?x:y;
}//跳lca
int add(int x,int y)
{
int ret=0;
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]])swap(x,y);
ret=max(ret,query(1,in[top[x]],in[x]));
x=f[top[x]];
}
if(dep[x]<dep[y])swap(x,y);
ret=max(ret,query(1,in[y]+1,in[x]));
return ret;
}
void up(int x,int p,int val)
{
if(x==p)
{
flag=0;
return;
}
for(int t=head[x];t;t=tr[t].next)
{
int y=tr[t].v,nu=tr[t].num;
if(y!=f[x])continue;
if(!ans[nu])
ans[nu]=val-1;
up(y,p,val);
if(!flag)return;
}
}//向上更新树边答案
int main()
{
//freopen("tree.in","r",stdin);
//freopen("tree.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
fa[i]=i;
for(int i=1;i<=m;++i)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
po[i].u=x;
po[i].v=y;
po[i].val=z;
po[i].num=i;
}
sort(po+1,po+1+m,cmp);
for(int i=1;i<=m;++i)
{
int x=po[i].u,y=po[i].v,z=po[i].val,nw=po[i].num;
if(judge(x,y))continue;
unionn(x,y);
po[i].flag=1;//标记树边
cnt++;
add(x,y,z,nw);
add(y,x,z,nw);
if(cnt==n-1)break;
}//求最小生成树
f[1]=0;
dep[1]=1;
dfs1(1);
dfs2(1,1);
build(1,1,n);
for(int i=1;i<=m;++i)
{
int x=po[i].u,y=po[i].v,z=po[i].val;
if(!po[i].flag)
{
int ret=add(x,y);
ans[po[i].num]=ret-1;
int p=lca(x,y);//寻找lca
flag=1;
up(x,p,z);
flag=1;
up(y,p,z);
}//树边
}
for(int i=1;i<=m;++i)
printf("%d\n",ans[i]==0?-1:ans[i]);
return 0;
}
题目在这里(但一般人不能进去):题面