2012D2T2
教室
Task
有n个正数,m次操作,每次将[ li, ri ]区间所有值-ci,求操作后第一次出现负数的操作编号。
n,m<=1e6,ci<=1e9
Solution
方法一:
发现结果是单调的,每个数不可能变大,只可能随操作次数增多越来越小。
如果二分操作次数,就把问题转化成一个判定问题:这个区间是否会出现负数?
对区间加减可以利用线段树来维护区间最小值判断是否有负数。
更优的方法是刷漆法,每次只要对左端点+c,右端点+1的位置-c,再利用前缀和,就可以得到这个区间的值。
这种方法适合于多次操作一次询问的类型,但不适用询问操作同时。
方法二:by Leefir大神
复杂度O(n+m)!!
具体的操作:
刷漆法得到最终的点值,从前往后找到负数,再把区间从后往前加回来。
贪心为什么是正确的?
如果位于i的值是负数,那么通过区间从后往前加,可以得到第一个让该点变为负数的操作序号。
如果位于j( j > i )再遇到一个负数,此时a[i]为非负数。说明还需要还原更多的间才能让a[j]变为正数。换言之,j先比i变成负数。
拓展问题:求每个数第一次变成负数的操作?
每个点都可以二分操作,可以同时二分,即整体二分,BIT维护区间差分和。
2013D1T3
货车运输
Task
n个点,m条无向边,q个询问,输出(a,b)路经上最大的最小值,如果没有路径,输出-1。
n<1e4,m<5e4,q<3e4
Solution
70分
求a,b路径上最大的最小值问题:
用并查集处理,边权从大到小排序,最后一条边让a,b连通的就是路径上的最大的最小值。
100分
方法一:树上两点路径间最小值:
由图想到关键词“树”。图删去边,就变成了树。
如果按边权从大到小排序加入,当前边的端点a,b已经相连,那么当前边一定不是a,b路径上最大的最小值,因此可以删去。
最后的操作结果就是一片森林。
问题转化成了求树上两点间的边权最小值。
我们曾经做过的是求a,b两点间路径值,类似的,边权最小值也是利用倍增来实现的。
方法二:整体二分
“最大的最小值”一种边界状态,满足二分的000111的样子。
因此对于每个询问二分一个权值下标,问题转化了:判定只用>=v的边连接,是否可以联通a,b两点。
那么可不可以让二分跳出来,进行整体二分呢?
借鉴区间第k值的二分做法,对于每个询问,都处理出答案的区间。按照从小到达排序,
依次加入边。这样的好处是:只O(m)加了一次边,可以回答所有的询问。
这种整体二分的方法很普遍,可以回答多个询问确定最值的情况。
2012D2T1
同余方程
Task
求x的同于方程ax-by=1(mod b)的最小正整数解。
Solution
没错!这就是一道裸的数学题,用扩展欧几里得算法求解。
运用的性质:gcd(a,b)=gcd(b,a%b)
特别的gcd(a,0)=a
如果ax+by=gcd(a,b)=1
那么与bx1+(a%b)y1=gcd(b,a%b)=1的解之间有什么关系呢?
a%b=a-a/b*b,其中a/b是向下取整
bx1+ay1-a/b*b*y1=1
整理为:ay1+b(x1-a/b*y1)=1
可以令x=y1
y=x1-a/b*y1
特别的,当b=0时,只有当gcd(a,0)=0;因此x=1
2012D2T3
疫情控制
Task
以1为根n个节点的树,有m个点可以任意移动,为保证最后每个叶子到根的路径上都有至少一个点,求所有移动的点最大值的最小值。
Solution
树上的贪心。
每个点越往上走,可以覆盖的叶子节点越多。但是应该往上走多少呢?
发现单调性:移动的最大值越大,越可能满足条件,符合二分01边界性质。该题可以转化成判定类型的问题。
如果二分一个最大值,每个点在不超过最大值的情况下,应尽可能地往上走。
将所有的点分成两类:能走到根,不能走到根。
所有不能走到根的,可以覆盖一些叶子。但如果某个根儿子的子树不能覆盖到所有的叶子,就需要能走到根的,且在剩余步数内能走到该儿子的点取帮助这棵子树,该点只可能往下走一段到儿子,不可能从儿子再往下走。
利用dfs得到需要帮助的儿子。剩下的就是需要帮助的点A和给予帮助的点B和匹配问题了。
(赛时前面都对,卡在这个地方,只得了20分)
赛时的思路是贪心去选剩余步数较小的点去满足这个需要帮助的儿子。
但是如果遇到从儿子子树出去的点该怎么办呢?
LIN452小姐的处理方法是:
A和B分别按需要的距离,剩余的步数从大到小排序!
记录从子树内出去的剩余步数最小的节点序号C。
对每一个A中的点,如果C存在且未被使用,那么一定使用C,否则就用当前B中剩余步数最大的点。
如果需要帮助距离较大的无法满足,那么一定达不到题目要求。
/*
树[建边]-遍历-预处理-询问[lca,倍增]
*/
const int M=1e4+5,S=14;//2^14=16384
int head[M],dep[M],belong[M],fa[S][M],val[S][M];
bool vis[M];
int n,m,ecnt;
struct edge{
int t,v,nxt;
bool operator<(const edge &t)const{
return nxt>t.nxt;
}
}e[M<<1],E[50005];//53w
inline int get(int x){
if(x!=belong[x])belong[x]=get(belong[x]);
return belong[x];
}
inline bool Combine(int a,int b){
a=get(belong[a]);
b=get(belong[b]);
if(a==b)return 0;//同一个集合
belong[a]=b;//并在一起
return 1;
}
inline void addedge(int f,int t,int v){
e[++ecnt]=(edge){t,v,head[f]};
head[f]=ecnt;
}
inline void input(){
int i,j,k,a,b,c;
rd(n);rd(m);
rep(i,1,m){
rd(a);rd(b);rd(c);
E[i]=(edge){a,b,c};
}
sort(E+1,E+1+m);
rep(i,1,n)belong[i]=i;
rep(i,1,m){
a=E[i].t,b=E[i].v,c=E[i].nxt;
if(Combine(a,b)){
addedge(a,b,c);
addedge(b,a,c);
}
}
/*for(int x=1;x<=n;x++){
printf("flag:%d\n",x);
for(i=head[x];i;i=e[i].nxt){
printf("%d ",e[i].t);
}puts("");
}*/
}
inline void build(int f,int x,int d,int v){//建树
dep[x]=d;
val[0][x]=v;
fa[0][x]=f;
vis[x]=1;
for(int i=head[x];i;i=e[i].nxt)
if(e[i].t!=f)build(x,e[i].t,d+1,e[i].v);
}
inline void init(){
int i,j,k;
rep(i,1,S-1)
rep(j,1,n){
fa[i][j]=fa[i-1][fa[i-1][j]];
val[i][j]=min(val[i-1][j],val[i-1][fa[i-1][j]]);
}
}
inline int LCA(int x,int y){
int i,d;
if(dep[x]<dep[y])swap(x,y);
d=dep[x]-dep[y];
rep(i,0,S-1)
if(d&(1<<i))x=fa[i][x];//相同的高度
if(x==y)return x;
per(i,S-1,0)
if(fa[i][x]!=fa[i][y])x=fa[i][x],y=fa[i][y];
return fa[0][x];
}
inline int solve(int x,int d){
int i,j,k,mi=1e9;
rep(i,0,S-1)
if(d&(1<<i)){
mi=min(mi,val[i][x]);
x=fa[i][x];
}
return mi;
}
inline void query(){
int i,j,k,a,b,lca,la,lb;
rd(m);
while(m--){
rd(a);rd(b);
if(get(belong[a])!=get(belong[b])){puts("-1");continue;}//没有路
lca=LCA(a,b);
la=solve(a,dep[a]-dep[lca]);
lb=solve(b,dep[b]-dep[lca]);
sc(min(la,lb));
}
}
int main(){
int i;
input();
rep(i,1,n)if(!vis[i])build(0,i,0,0);
init();
query();
return 0;
}
const int M=10002;
int n,m,q;
int fa[M],ans[M*3];
struct edge{
int a,b,c;
bool operator<(const edge &t)const{
return c>t.c;//边权从大到小排序
}
}E[M*5];
struct node{
int l,r,x,y,id;
bool operator<(const node &t)const{
int a=l+r>>1;
int b=t.l+t.r>>1;
return a<b;
}
}A[M*3];
inline void input(){
int i,j,k;
rd(n);rd(m);
rep(i,1,m){
rd(E[i].a);rd(E[i].b);rd(E[i].c);
}
sort(E+1,E+1+m);
memset(ans,-1,sizeof(ans));
rd(q);
rep(i,1,q){
rd(A[i].x);rd(A[i].y);
A[i].l=1;A[i].r=m;A[i].id=i;
}
}
inline int getfa(int x){
if(x!=fa[x])fa[x]=getfa(fa[x]);
return fa[x];
}
inline void Combine(int a,int b){
a=getfa(fa[a]);
b=getfa(fa[b]);
if(a!=b)fa[a]=b;
}
inline void solve(){//整体二分
int i,j,k,cas=16,mid,x;
while(cas--){
rep(i,1,n)fa[i]=i;//初始不连通
sort(A+1,A+1+q);
x=1;
rep(i,1,q){//q个询问
mid=A[i].l+A[i].r>>1;
while(x<=mid&&x<=m){Combine(E[x].a,E[x].b);x++;}
if(getfa(fa[A[i].x])==getfa(fa[A[i].y])){//可以联通
ans[A[i].id]=E[mid].c;
A[i].r=mid-1;
}else A[i].l=mid+1;
}
}
}const int M=10002;
int n,m,q;
int fa[M],ans[M*3];
struct edge{
int a,b,c;
bool operator<(const edge &t)const{
return c>t.c;//边权从大到小排序
}
}E[M*5];
struct node{
int l,r,x,y,id;
bool operator<(const node &t)const{
int a=l+r>>1;
int b=t.l+t.r>>1;
return a<b;
}
}A[M*3];
inline void input(){
int i,j,k;
rd(n);rd(m);
rep(i,1,m){
rd(E[i].a);rd(E[i].b);rd(E[i].c);
}
sort(E+1,E+1+m);
memset(ans,-1,sizeof(ans));
rd(q);
rep(i,1,q){
rd(A[i].x);rd(A[i].y);
A[i].l=1;A[i].r=m;A[i].id=i;
}
}
inline int getfa(int x){
if(x!=fa[x])fa[x]=getfa(fa[x]);
return fa[x];
}
inline void Combine(int a,int b){
a=getfa(fa[a]);
b=getfa(fa[b]);
if(a!=b)fa[a]=b;
}
inline void solve(){//整体二分
int i,j,k,cas=16,mid,x;
while(cas--){
rep(i,1,n)fa[i]=i;//初始不连通
sort(A+1,A+1+q);
x=1;
rep(i,1,q){//q个询问
mid=A[i].l+A[i].r>>1;
while(x<=mid&&x<=m){Combine(E[x].a,E[x].b);x++;}
if(getfa(fa[A[i].x])==getfa(fa[A[i].y])){//可以联通
ans[A[i].id]=E[mid].c;
A[i].r=mid-1;
}else A[i].l=mid+1;
}
}
}
inline void print(){
int i;
rep(i,1,q)sc(ans[i]);
}
int main(){
input();
solve();
print();
return 0;
}
inline void print(){
int i;
rep(i,1,q)sc(ans[i]);
}
int main(){
input();
solve();
print();
return 0;
}