2017-10-27离线赛小结

本文分享了一次竞赛编程的经验,包括从预处理到调试的全过程,并详细解析了几道典型题目,如通过二分查找和动态规划解决问题的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

估分:280
实际分数:260
这次考得还过得去,终于逃脱了炸零魔咒,但最后一题线段树敲错Tle了20分还是很悲伤的
小C:你怎么那么low啊
是时候总结一下考试要干些什么了
1.敲模板(头文件,读入挂)
2.写备注(long long double 文件名 乘法 调试)
3.把文件名复制到freopen里(一定要复制!)
4.敲暴力(以前第一题炸零的经历告诉我,前面两题一定要敲暴力)
5.想正解,同时要预估时间(主要针对第三题)
6.想不出正解时,果断弃坑,开始往死里切分(一般切到50+还是容易的)
这样下来每场200+时有保证的
7.敲完后求内存,开始对拍
8.交之前 freopen 文件名 调试

题解:
A:这种题一看就是先找规律,能不能贪心,然后想不到再想想二分
结果真的是二分
B: 一道神dp,也就是先暴力n重循环,然后瞎调调出来了加上一些前缀和优化 这题暴力水不到分是最尴尬的
总结一下,这种计数的dp :先暴力n重循环,在前缀和优化时间,在滚动优化内存
C:这道题用点时间还是能轻松水到80分的,比敲正解加调试要容易得多(重点是切分之间可以互相对拍)

还是得说正解的,这题有两种做法,都很值得学习:
1.二分答案+差分前缀和
首先得想到答案是具有单调性的,就是趋向于一种满足.满足.不满足的状态(好像说反了)
然后根据二分出的答案筛选出不满足(大于)答案的边
这题的特性是只用删一条边,那么可以利用差分,标记这个点的lca,和左右两点,在配合dfs栈,在O(n)的复杂度内求出这些边的交边,然后判断是否能删掉一条边并得出答案
代码实现(卡过去的,倍增常数实在太大了):

#include<bits/stdc++.h>
using namespace std;
#define S 21
#define M 400005
int Fa[S][M],C[M],V[M],Val[M];
int dis[M],dep[M];
int dfsL[M],T;
struct node{int to,v;};
vector<node>edge[M];
struct NODE{int l,r,lca,v;}Q[M];
bool cmp(NODE a,NODE b){
    return a.v>b.v;
}
void dfs(int x,int f){
    Fa[0][x]=f;
    dep[x]=dep[f]+1;
    dfsL[++T]=x;
    for(int i=0;i<(int)edge[x].size();i++){
        int y=edge[x][i].to;
        if(y==f)continue;
        Val[y]=edge[x][i].v;
        dis[y]=dis[x]+Val[y];
        dfs(y,x);
    }
}
void UP(int &x,int len){
    for(int i=0;i<S;i++){
        if(len&(1<<i)){
            x=Fa[i][x];
        }
    }
}
int LCA(int x,int y){
    if(dep[x]>dep[y])swap(x,y);
    UP(y,dep[y]-dep[x]);
    if(x==y)return x;
    for(int i=S-1;i>=0;i--){
        if(Fa[i][x]!=Fa[i][y]){
            x=Fa[i][x];
            y=Fa[i][y];
        }
    }
    return Fa[0][x];
}
bool flag;
int k;
int n,m;
bool check(int res){
    memset(C,0,sizeof(C));
    k=0;
    int mx=0;
    for(int i=1;i<=m;i++){
        if(Q[i].v<=res)continue;
        C[Q[i].l]++,C[Q[i].r]++;
        C[Q[i].lca]-=2;
        k++;
        mx=max(mx,Q[i].v-res);
    }
    for(int i=n;i>1;i--){
        C[Fa[0][dfsL[i]]]+=C[dfsL[i]];
    }
    for(int i=2;i<=n;i++)if(C[i]==k&&mx<=Val[i])return 1;
    return 0;
}
int main(){
    int l=0,r=1e9,res=0;
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;i++){
        int x,y,v;
        scanf("%d%d%d",&x,&y,&v);
        edge[x].push_back((node){y,v});
        edge[y].push_back((node){x,v});
    }
    dfs(1,0);
    for(int i=1;i<=S-1;i++)
        for(int j=1;j<=n;j++)
            Fa[i][j]=Fa[i-1][Fa[i-1][j]];
    for(int i=1;i<=m;i++){
        scanf("%d%d",&Q[i].l,&Q[i].r);
        Q[i].lca=LCA(Q[i].l,Q[i].r);
        Q[i].v=dis[Q[i].l]+dis[Q[i].r]-2*dis[Q[i].lca];
    }
    while(l<=r){
        int mid=(l+r)>>1;
        if(check(mid)){
            r=mid-1;
            res=mid;
        }
        else l=mid+1;
    }
    printf("%d\n",res);
    return 0;
}

2.又是一种新的想法
把树压成一条链,然后得出答案:
首先得造出一条链
我们可以把最长的路径抽出来,然后把非链上的点压到链上
利用前缀和后缀O(1)得出不经过被删边的路径的最大值
以及最大路径删掉那条边后的值
代码实现:

#include<bits/stdc++.h>
using namespace std;
const int S=20,M=300005;
int fa[S][M],dep[M],L[M],R[M],lca[M],Val[M],dis[M];
int Lmx[M],Rmx[M];
struct node{int to,v;};
vector<node>edge[M];
void ldfs(int x,int f){
    dep[x]=dep[f]+1;
    fa[0][x]=f;
    for(int i=0;i<(int)edge[x].size();i++){
        int y=edge[x][i].to;
        if(f==y)continue;
        dis[y]=dis[x]+edge[x][i].v;
        ldfs(y,x);
    }
}
void up(int &x,int len){
    for(int i=0;i<S;i++){
        if(len&(1<<i))x=fa[i][x];
    }
}
int LCA(int x,int y){
    if(dep[x]>dep[y])swap(x,y);
    up(y,dep[y]-dep[x]);
    if(x==y)return x;
    for(int i=S-1;i>=0;i--){
        if(fa[i][x]!=fa[i][y]){
            x=fa[i][x];
            y=fa[i][y];
        }
    }
    return fa[0][x];
}
bool mark[M];
int Col[M],T[M],Cost[M];
int cnt;
void mk(int x,int y){
    if(dep[x]>dep[y])swap(x,y);
    int len=dep[y]-dep[x];
    mark[x]=1,mark[y]=1;
    for(int i=1;i<=len;i++){
        y=fa[0][y];
        mark[y]=1;
    }
    if(x==y)return;
    while(x!=y){
        x=fa[0][x];
        y=fa[0][y];
        mark[x]=1;
        mark[y]=1;
    }
}
void mdfs(int x,int f){
    T[++cnt]=x;
    for(int i=0;i<(int)edge[x].size();i++){
        int y=edge[x][i].to;
        if(mark[y]==0||y==f)continue;
        Cost[cnt]=edge[x][i].v;
        mdfs(y,x);
    }
}
void rdfs(int x,int f,int v){
    Col[x]=v;
    for(int i=0;i<(int)edge[x].size();i++){
        int y=edge[x][i].to;
        if(y==f||mark[y])continue;
        rdfs(y,x,v);
    }
}
int main(){
    int n,m;
    scanf("%d%d",&n,&m);

    for(int i=1;i<n;i++){
        int x,y,v;
        scanf("%d%d%d",&x,&y,&v);
        edge[x].push_back((node){y,v});
        edge[y].push_back((node){x,v});
    }

    ldfs(1,0);

    int mx=0,id=0;
    for(int i=1;i<S;i++)for(int j=1;j<=n;j++)fa[i][j]=fa[i-1][fa[i-1][j]];
    for(int i=1;i<=m;i++){
        scanf("%d %d",&L[i],&R[i]);
        lca[i]=LCA(L[i],R[i]);
        Val[i]=dis[L[i]]+dis[R[i]]-2*dis[lca[i]]; 
        if(Val[i]>mx){
            mx=Val[i];
            id=i;
        }
    }

    int ans=-1;
    mk(L[id],R[id]);
    mdfs(L[id],0);//抽直径

    for(int i=1;i<=cnt;i++)rdfs(T[i],0,i);

    for(int i=1;i<=m;i++){
        if(i==id)continue;
        int l=L[i],r=R[i];
        if(Col[l]>Col[r])swap(l,r);
        if(Lmx[Col[r]]<Val[i])Lmx[Col[r]]=Val[i];
        if(Rmx[Col[l]]<Val[i])Rmx[Col[l]]=Val[i];
    }

    for(int i=1;i<cnt;i++)if(Lmx[i+1]<Lmx[i])Lmx[i+1]=Lmx[i];
    for(int i=cnt;i>1;i--)if(Rmx[i]>Rmx[i-1])Rmx[i-1]=Rmx[i];

    for(int i=1;i<cnt;i++){
        int val=max(Lmx[i],Rmx[i+1]);
        val=max(val,mx-Cost[i]);
        if(ans>val||ans==-1)ans=val;
    } 

    if(ans==-1)puts("0");
    else printf("%d\n",ans);
    return 0; 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值