训练日记-多校

这篇训练日记详细记录了在ACM竞赛中的三道题目,分别是关于环形路径上的苹果收集优化、最短路与最大流结合的迷宫问题以及树上最短路查询。通过动态规划和图论策略,分析了解题思路并提供了代码实现,揭示了环状结构和树结构对问题复杂度的影响。

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

Delicious Apples

题目大概:

给出一个有n个位置的环,有m个位置有苹果树,你的仓库在0的位置,你有一个大小为k的篮子,问最少走多少步,可以把所有苹果拿到位置0的仓库。

思路:

有3中拿的方式

1,从右边走,拿够苹果,然后直接返回。

2,从左边走,拿够苹果,然后返回。

3,绕一圈,把所有剩余苹果拿走。

显然,第三种方式,是最后剩余的苹果很少<=k个时,这样拿一圈,可以会走的步数少。但也有可能直接回去少,需要比较。

利用背包,把这三种方式模拟一遍。

感想:

如果改在直线上,仓库位置不固定,会怎么样,那就没有绕圈拿了,直接分析前两种情况就i选哪个了,所有,很多题,有环后,就复杂了一些。

代码:

#include <bits/stdc++.h>

using namespace std;
const int maxn=1e5+10;
pair<int,int>G[maxn];
long long d[maxn];
long long dl[maxn],dr[maxn];
int cmp(pair<int,int> a,pair<int,int> b)
{
    return a.first<b.first;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        long long L;
        int n,k;
        memset(G,0,sizeof(G));
        memset(d,0,sizeof(d));
        memset(dl,0,sizeof(dl));
        memset(dr,0,sizeof(dr));
        scanf("%I64d%d%d",&L,&n,&k);
        for(int i=0;i<n;i++)
        {
            scanf("%d%d",&G[i].first,&G[i].second);
        }
        sort(G,G+n,cmp);
        int cnt=0;
        for(int i=0;i<n;i++)
        {
            for(int j=1;j<=G[i].second;j++)
            {
                d[++cnt]=(long long)2*G[i].first;
            }
        }
        for(int i=1;i<=cnt;i++)
        {
            if(i>k)
            {
                dl[i]=dl[i-k]+d[i];
            }
            else
            {
                dl[i]=d[i];
            }
        }
        for(int i=cnt;i>=1;i--)
        {
            if(i+k<=cnt)
            {
                dr[i]=dr[i+k]+(2*L-d[i]);
            }
            else
            {
                dr[i]=(2*L-d[i]);
            }
        }
        long long ans=0;
        for(int i=0;i<=cnt;i++)
        {
            if(ans==0)ans=dl[i]+dr[i+1];
            ans=min(ans,dl[i]+dr[i+1]);
        }
        for(int i=0;i+k+1<=cnt;i++)
        {
            ans=min(ans,dl[i]+dr[i+k+1]+L);
        }
        if(k>=cnt)ans=min(ans,L);
        printf("%I64d\n",ans);
    }
    return 0;
}

Tricks Device

题目大概:

有一个迷宫,有n个房间,m条道。1为入口,n为出口。从1道n有最短路。

问最少切断几条道,可以使人不能从1走到n为最短路。

问最多切断几条道,还可以从1走最短路走到n。

思路:

求一遍1道n的最短路,并且维护1到n的最短路,最少变数为min,第二问答案就是m-min。

把图中的所有最短路的边重新建立最短路图,然后跑一边1到n的最大流。就是求的最小割。就是第一问的答案。

感想:

这是一个最短路和最大流结合的题目。

最大流,就考察了一个最小割转化为最大流来求,感觉难点还是求最短路的最少边,需要改变最短路的代码。

就是改动最短路的代码,既然可以求最少,也可以求最多,当然,也可以在最短路过程中维护其他的。

代码:

#include <bits/stdc++.h>

using namespace std;

#define RPEF( i , a , b ) for ( int i = a ; i <= b ; ++ i )
#define REP( i , n ) for ( int i = 0 ; i < n ; ++ i )
#define copy( a , x ) memcpy ( a , x , sizeof a )
typedef long long LL ;

const int MAXN = 3010 ;
const int MAXE = 100010;
const int MAXQ = 200010;
const int INF = 0x3f3f3f3f;

struct Edge {
    int v , n ;
    LL c ;
    Edge ( int var = 0 , LL cap = 0 , int next = 0 ) :
        v ( var ) , c ( cap ) , n ( next ) {}
} ;

struct netWork {
    Edge edge[MAXE] ;
    int adj[MAXN] , cntE ;
    int cur[MAXN] , d[MAXN] , num[MAXN] , pre[MAXN] ;
    bool vis[MAXN] ;
    int Q[MAXQ] , head , tail ;
    int s , t , nv ;
    LL flow ;

    void init () {
        cntE = 0 ;
        memset(adj,-1,sizeof(adj));
    }

    void addedge ( int u , int v , LL c , LL rc = 0 ) {
        edge[cntE] = Edge ( v ,  c , adj[u] ) ;
        adj[u] = cntE ++ ;
        edge[cntE] = Edge ( u , rc , adj[v] ) ;
        adj[v] = cntE ++ ;
    }

    void rev_Bfs () {
        memset(vis,0,sizeof(vis));
        memset(num,0,sizeof(num));
        d[t] = 0 ;
        vis[t] = 1 ;
        head = tail = 0 ;
        Q[tail ++] = t ;
        num[0] = 1 ;
        while ( head != tail ) {
            int u = Q[head ++] ;
            for ( int i = adj[u] ; ~i ; i = edge[i].n ) {
                int v = edge[i].v ;
                if ( vis[v] )
                    continue ;
                vis[v] = 1 ;
                d[v] = d[u] + 1 ;
                ++ num[d[v]] ;
                Q[tail ++] = v ;
            }
        }
    }

    LL ISAP () {
        copy ( cur , adj ) ;
        rev_Bfs () ;
        flow = 0 ;
        int i , u = pre[s] = s ;
        while ( d[s] < nv ) {
            if ( u == t ) {
                LL f = INF ;
                int pos ;
                for ( i = s ; i != t ; i = edge[cur[i]].v )
                    if ( f > edge[cur[i]].c )
                        f = edge[cur[i]].c , pos = i ;
                for ( i = s ; i != t ; i = edge[cur[i]].v )
                    edge[cur[i]].c -= f , edge[cur[i] ^ 1].c += f ;
                u = pos ;
                flow += f ;
            }
            for ( i = cur[u] ; ~i ; i = edge[i].n )
                if ( edge[i].c && d[u] == d[edge[i].v] + 1 )
                    break ;
            if ( ~i ) {
                cur[u] = i ;
                pre[edge[i].v] = u ;
                u = edge[i].v ;
            }
            else {
                if ( 0 == ( -- num[d[u]] ) )
                    break ;
                int mmin = nv ;
                for ( i = adj[u] ; ~i ; i = edge[i].n )
                    if ( edge[i].c && mmin > d[edge[i].v] )
                        cur[u] = i , mmin = d[edge[i].v] ;
                d[u] = mmin + 1 ;
                ++ num[d[u]] ;
                u = pre[u] ;
            }
        }
        return flow ;
    }
} ;

netWork net ;


int n,m;
struct node1{
    int v,next,w;
}edge[MAXE];

int id;
int head[MAXN];
void init1()
{
    memset(head,-1,sizeof(head));
    id=0;
}

void addedge(int u,int v,int w)
{
    edge[id].v=v;
    edge[id].w=w;
    edge[id].next=head[u];
    head[u]=id++;

    edge[id].v=u;
    edge[id].w=w;
    edge[id].next=head[v];
    head[v]=id++;
}
bool vis[MAXN];
int side[MAXN];
int dist[MAXN];
void spfa(int s)
{
    memset(vis,false,sizeof(vis));
    memset(dist,INF,sizeof(dist));
    memset(side,INF,sizeof(side));
    vis[s]=true;
    dist[s]=0;
    side[s]=0;
    queue<int>que;
    while(!que.empty()) que.pop();
    que.push(s);
    while(!que.empty())
    {
        int u=que.front();
        que.pop();
        vis[u]=false;
        for(int i=head[u]; i!=-1; i=edge[i].next)
        {
            int v=edge[i].v;
            if (dist[v]>dist[u]+edge[i].w)
            {
                dist[v]=dist[u]+edge[i].w;
                side[v]=side[u]+1;
                if (!vis[v])
                {
                    vis[v]=true;
                    que.push(v);
                }
            }
            if (dist[v]==dist[u]+edge[i].w)
            {
                side[v]=min(side[u]+1,side[v]);
                if (!vis[v])
                {
                    vis[v]=true;
                    que.push(v);
                }
            }
        }
    }
}

void getmap()
{
    for(int i=1;i<=n;i++)
    {
        for(int j=head[i];j!=-1;j=edge[j].next)
        {
            int v=edge[j].v;
            if(dist[v]==dist[i]+edge[j].w)
            net.addedge(i,v,1);
        }
    }
}

int main()
{

    while(scanf("%d%d",&n,&m)!=EOF)
    {
        int u,v,w;
        init1();
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d%d",&u,&v,&w);
            addedge(u,v,w);
        }
        spfa(1);
        net.init();
        getmap();

        net.s=1;net.t=n;net.nv=net.t+1;
        printf("%I64d %d\n",net.ISAP(),m-side[n]);
    }
    return 0;
}

 

Annoying problem

题目大概:

给出一棵树,n个点,(n-1)条边,然后给出q个操作,有一个集合s。每次操作后,给出s集合中所有点相连的最短路长度。

1  x   为s集合增加x节点,

2  x  把s集合中的x节点删除。

思路:

用dfs序和lca就可以解决这道题。

dis数组是给点到根的长度,lca(x,y)求x和ylca。

用了一个公式,dis【u】-dis【lca(x,u)】-dis【lca(y,u)】+dis【lca(x,y)】;

有点容斥的味道。

感想:

dfs序有很多树上的应用,算是一种简单树链剖分题的有效解决方法。

代码:

#include <bits/stdc++.h>

using namespace std;
const int maxn=1e5+50;

int cnt;
int head[maxn<<1];
struct poin
{
    int to,w,next;
}edge[maxn<<1];
set<int>s;
int dis[maxn],dxu[maxn],be[maxn],d[maxn];
int vis[maxn];
int fa[maxn][25];
void add(int u,int v,int w)
{
    edge[cnt].to=v;
    edge[cnt].w=w;
    edge[cnt].next=head[u];
    head[u]=cnt++;

    edge[cnt].to=u;
    edge[cnt].w=w;
    edge[cnt].next=head[v];
    head[v]=cnt++;
}
int ans;
void init()
{
    cnt=0;
    ans=0;
    memset(head,-1,sizeof(head));
    memset(vis,0,sizeof(vis));
    memset(dis,0,sizeof(dis));
    memset(dxu,0,sizeof(dxu));
    memset(fa,0,sizeof(fa));
    s.clear();
}

void dfs(int u,int fat)
{
    dxu[u]=++ans;
    be[ans]=u;
    for(int i=1; i<19; i++)
    {
        fa[u][i]=fa[fa[u][i-1]][i-1];
    }
    for(int i=head[u]; i!=-1; i=edge[i].next)
    {
        int v=edge[i].to;
        int w=edge[i].w;
        if(v==fat)continue;
        d[v]=d[u]+1;
        dis[v]=dis[u]+w;
        fa[v][0]=u;
        
        dfs(v,u);
    }
}
int lca(int x,int y)
{
    if(d[x]<d[y])swap(x,y);
    for(int i=19; i>=0; i--)
    {
        if(d[fa[x][i]]>=d[y])
            x=fa[x][i];
        if(x==y)return x;
    }
    for(int i=19; i>=0; i--)
    {
        if(fa[x][i]!=fa[y][i])
        {
            x=fa[x][i];
            y=fa[y][i];
        }
    }
    return fa[x][0];
}

int solve(int u)
{
    if( s.empty() ) return 0 ;
    int x , y ;
    set<int>::iterator iter;
    iter = s.upper_bound(u) ;
    if( iter == s.end() || iter == s.begin() )
    {
        x = be[ *s.begin() ] ;
        y = be[ *s.rbegin()] ;
    }
    else
    {
        x = be[*iter] ;
        iter-- ;
        y = be[*iter] ;
    }
    u = be[u] ;
    /*
    cout<<"****"<<endl;
    cout<<x<<" "<<y<<" "<<u<<endl;
    cout<<dis[u]<<" "<<dis[lca(x,u)]<<" "<<dis[lca(y,u)]<<" "<<dis[lca(x,y)]<<endl;
    cout<<dis[u]-dis[lca(x,u)]-dis[lca(y,u)]+dis[lca(x,y)]<<endl;
    cout<<"****"<<endl;
    */
    return dis[u]-dis[lca(x,u)]-dis[lca(y,u)]+dis[lca(x,y)] ;
}

int fl=0;
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        init();
        int n,q;
        scanf("%d%d",&n,&q);
        int u,v,w;
        for(int i=1; i<n; i++)
        {
            scanf("%d%d%d",&u,&v,&w);
            add(u,v,w);
        }
        int sum=0;
        printf("Case #%d:\n",++fl);
        d[1]=1;
        dfs(1,-1);
        int flag,x;
        while(q--)
        {
            scanf("%d%d",&flag,&x);

            x=dxu[x];
            if(flag==1)
            {
                if(!vis[x])
                {
                    vis[x]=1;
                    sum+=solve(x);
                    s.insert(x);
                }
            }
            else
            {
                if(vis[x])
                {
                    vis[x]=0;
                    s.erase(x);
                    sum-=solve(x);
                }
            }
            printf("%d\n",sum);
        }
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值