蒟蒻の图论刷题(更新中)Ⅰ 树/图dp

总算来补自己好久前买下的坑了,题目内容均来自洛谷题单

[TJOI2017]可乐

tag上是分层图+矩阵优化,但是被我用暴力+滚动数组水过去了(赞美O2!)对每个点有三种状态,0:上一秒已经在这个城市了;1:这一秒刚到;2:自爆。只需要记录最后时刻0、1的和以及所有时刻2的和即可,注意滚动数组的更新。

#include<bits/stdc++.h>
#define mp make_pair
#define pb push_back
#define ll long long
#define fi first
#define se second
#define inf 0x3f3f3f3f
#define io std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
#define mod 2017
#define IOS  ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define maxn 40
vector<int> g[40];
const int N = 1e6+5;
int dp[maxn][3][2];int n,m,t;
//0 原地 1 移动 2爆炸
int main(){
    IOS
    cin>>n>>m;
    while(m--){
        int u,v;
        cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dp[1][0][0]=1;
    cin>>t;
    int cnt=1;
    ll ans=0;
    for (int i = 1; i <=t ; ++i) {
        for (int j = 1; j <=n ; ++j) {
            int tmp=((cnt-1)%2+2)%2;
            dp[j][0][cnt]=(dp[j][0][tmp]+dp[j][1][tmp])%mod;
            dp[j][2][cnt]=(dp[j][1][tmp]+dp[j][0][tmp])%mod;
            for(auto k:g[j]){
                dp[k][1][cnt]+=(dp[j][1][tmp]+dp[j][0][tmp])%mod;
            }
            dp[j][1][tmp]=0;
            ans=(ans+dp[j][2][cnt])%mod;
        }
        cnt++;
        cnt%=2;
    }
    cnt=((cnt-1)%2+2)%2;
    for (int l = 1; l <=n ; ++l) {
        ans=(ans+dp[l][0][cnt]+dp[l][1][cnt])%mod;
    }
    cout<<ans;
    return 0;
}

[ZJOI2006]物流运输

这种数据范围当然直接暴力啦
(又是写乱前向星的日常,debug了半天发现是前向星的锅)
一道不错的最短路+dp的题,先记录每个码头不能运输的日子,然后用dp[i][j]记录i到j天的最短路,然后就是从1开始枚举+区间分割了。

#include<bits/stdc++.h>
#define ll long long
#define fi first
#define io std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
#define mod 2017
#define IOS  ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define maxn 25
#define int long long
struct edge{
    int u,v,w,nxt;
}E[2010];
int head[maxn],cnt=0;
void addedge(int u,int v,int w){
    E[cnt].nxt=head[u];
    head[u]=cnt;
    E[cnt].u=u;
    E[cnt].v=v;
    E[cnt++].w=w;
}
int n,m,k,e;
int wx[maxn][110],wait[maxn],vis[maxn];
int way[110][110],d[maxn];
int spfa(int x,int y){
    memset(wait,0, sizeof(wait));
    memset(vis,0, sizeof(vis));
    for (int i = 1; i <=m ; ++i){
        d[i]=0x3f3f3f3f3f3f3f;
        for (int j = x; j <=y ; ++j) {
            if(wx[i][j]){wait[i]=1;}
        }
    }
    d[1]=0;
    queue<int> q;
    q.push(1);
    while(!q.empty()){
        int now=q.front();
        q.pop();
        vis[now]=0;
        for (int i = head[now]; i !=-1 ; i=E[i].nxt) {
            int v=E[i].v,w=E[i].w;
            if(wait[v])continue;
            if(d[v]>=d[now]+w){
                d[v]=d[now]+w;
                if(!vis[v]){
                    vis[v]=1;
                    q.push(v);
                }
            }
        }
    }
    return d[m];
}
int ans[110];
signed main(){
    IOS
    cin>>n>>m>>k>>e;
    memset(head,-1,sizeof head);
    memset(wx,0, sizeof(wx));
    while(e--){
        int u,v,w;
        cin>>u>>v>>w;
        addedge(u,v,w);
        addedge(v,u,w);
    }
    int tmp;
    cin>>tmp;
    while(tmp--){
        int p,a,b;
        cin>>p;
        cin>>a>>b;
        for (int i = a; i <=b ; ++i) {
            wx[p][i]=1;
        }
    }
    for (int i = 1; i <=n ; ++i) {
        for (int j = i; j <=n ; ++j) {
            way[i][j]=spfa(i,j);
        }
        ans[i]=0x3f3f3f3f3f3f3f;
    }
    ans[0]=-k;
    for (int l = 1; l <=n ; ++l) {
        ans[l]=way[1][l]*l;
        for (int i = l-1; i>=0 ; --i) {
            ans[l]=min(ans[l],ans[i]+way[i+1][l]*(l-i)+k);
        }
    }
    cout<<ans[n];
    return 0;
}

[HNOI/AHOI2018]道路

本来挺简单的,但是被我日常不看完条件,硬是卡了半天没出QAQ
题目中说的很清楚了,每个城市选一条道路,那我们就可以开一个三维数组,记录到第u个节点时,有i条未翻修公路和j条未翻修铁路的不便利值(由于深度不超过40,后两维开到45就可)。每次向下走,有两种可能,一种是修公路,一种是修铁路。到叶节点时,直接计算,然后向上更新即可。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
#define mod 2017
#define IOS  ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define maxn 40005
int n;
const int N = 20005;
int son[maxn][2];
struct node{
    ll a,b,c;
}v[maxn];
ll dp[N][45][45];
ll dfs(int rt,int x,int y){
    if(son[rt][0]==son[rt][1]){
        return v[rt].c*(v[rt].a+x)*(v[rt].b+y);
    }
    if(dp[rt][x][y]>=9187201950435737000)dp[rt][x][y]=min(dfs(son[rt][0],x+1,y)+dfs(son[rt][1],x,y),dfs(son[rt][1],x,y+1)+dfs(son[rt][0],x,y));
    return dp[rt][x][y];
}
signed main(){
    IOS
    cin>>n;
    memset(dp,0x7f, sizeof(dp));
    for (int i = 1; i <=n-1 ; ++i) {
        int s,t;
        cin>>s>>t;
        if(s<0){
            s*=-1;
            s+=n-1;
        }
        if(t<0){
            t*=-1;
            t+=n-1;
        }
        son[i][0]=s;
        son[i][1]=t;
    }
    for (int j = 1; j <=n ; ++j) {
        cin>>v[j+n-1].a>>v[j+n-1].b>>v[j+n-1].c;
    }
    cout<<dfs(1,0,0);
    return 0;
}

[ZJOI2007]时态同步

虽然题意说的很多,但其实就是以s为根,所有的终止节点即为叶节点。那么两遍dfs就能过了这道题。显然最优方案就是将所有叶节点的费用变成初始费用的最大值,那么第一次dfs先求出最大值。第二次dfs时,先求出每个叶节点变成最大值需要增加多少次,找出相同父亲的叶节点中花费的最小值,这个最小值我们就可以通过改变父节点与其父亲的连边来减少次数,多余的就直接加入答案即可,向上同理。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
#define mod 2017
#define IOS  ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define maxn N
int n,s;
ll ans=0,ma=0;
const int N = 5e5+5;
struct edge{
    int u,v,w,nxt;
}e[N*2];
int head[maxn],cnt=0;
void add(int u,int v,int w){e[cnt].u=u;e[cnt].v=v;e[cnt].w=w;e[cnt].nxt=head[u];head[u]=cnt++;}
ll w[maxn],t[maxn],leave[maxn];
void dfs1(int x,int fa){
    int fl=0;
    for (int i = head[x]; i !=-1 ; i=e[i].nxt) {
        if(e[i].v==fa)continue;
        t[e[i].v]=t[x]+e[i].w;
        dfs1(e[i].v,x);
        fl=1;
    }
    if(!fl){
        leave[x]=1;
        ma=max(ma,t[x]);
    }
}
void dfs2(int x,int fa){
    if(leave[x]){
        w[x]=ma-t[x];
        return ;
    }
    ll mi=0x3f3f3f3f3f3f3f3f;
    for (int i = head[x]; i !=-1 ; i=e[i].nxt) {
        if(e[i].v==fa)continue;
        dfs2(e[i].v,x);
        mi=min(mi,w[e[i].v]);
    }
    for (int i = head[x]; i !=-1 ; i=e[i].nxt) {
        if(e[i].v==fa)continue;
        ans+=w[e[i].v]-mi;
    }
    w[x]=mi;
}
signed main(){
    IOS
    cin>>n>>s;
    memset(head,-1, sizeof(head));
    for (int i = 0; i <n-1 ; ++i) {
        int u,v,we;
        cin>>u>>v>>we;
        add(u,v,we);
        add(v,u,we);
    }
    dfs1(s,0);
    dfs2(s,0);
    cout<<ans;
    return 0;
}

[TJOI2017]城市

树的直径+树的半径
由于数据范围比较小,可以直接O(n^2)dfs。枚举删除的边,每次会产生两个联通块,此时的答案有三种可能:

  • 联通块A的直径
  • 联通块B的直径
  • 联通块A的半径最小值+联通块B的半径最小值+边的权值

直径就很好求了,对于以v为根的子树,设其父亲结点为f,那么半径的值有三种可能:

  • 子树v的最长链
  • 子树f中的最长链+v和f的距离
  • 子树f外的最长链+v和f的距离
#include<bits/stdc++.h>
#define ll long long
#define io std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
#define IOS  ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
const int N = 5e3+5;int n,col[N];
struct edge{
    int u,v,w,nxt;
}e[N*2];
struct node{
    int u,v,w;
};
vector<node> t;
int head[N],cnt=0;
void add(int u,int v,int w){e[cnt].u=u;e[cnt].v=v;e[cnt].w=w;e[cnt].nxt=head[u];head[u]=cnt++;}
int r[N],d_max=0,dm[N],dm_c[N];
void dfs_d(int x,int fa,int c){
    dm[x]=0;
    col[x]=c;
    int ma1=0,ma2=0;
    for (int i = head[x]; i !=-1 ;i=e[i].nxt) {
        int v=e[i].v;
        if(v==fa)continue;
        dfs_d(v,x,c);
        if(dm[v]+e[i].w>ma1){
            ma2=ma1;
            ma1=dm[v]+e[i].w;
        }else{
            if(dm[v]+e[i].w>ma2)ma2=dm[v]+e[i].w;
        }
    }
    dm[x]=ma1;
    dm_c[x]=ma2;
    d_max=max(d_max,ma1+ma2);
}
void dfs_r(int x,int fa,int fr){
    int tmp=0;
    for (int i = head[x]; i !=-1 ; i=e[i].nxt) {
        int v=e[i].v,w=e[i].w;
        if(v==fa)continue;
        if(dm[v]+w==dm[x]){
            r[v]=max(dm[v],w+dm_c[x]);
            tmp=max(fr,dm_c[x]);
        }else{
            r[v]=max(dm[v],dm[x]+w);
            tmp=max(fr,dm[x]);
        }
        r[v]=max(r[v],tmp+w);
        dfs_r(v,x,tmp+w);
    }
}
signed main(){
    IOS
    cin>>n;
    memset(head,-1, sizeof(head));
    for (int i = 0; i <n-1 ; ++i) {
        int u,v,w;
        cin>>u>>v>>w;
        t.push_back({u,v,w});
        add(u,v,w);
        add(v,u,w);
    }
    ll ans=0x3f3f3f3f3f3f3f3f;
    for(auto i:t){
        int u=i.u,v=i.v,w=i.w;
        d_max=0;
        dfs_d(u,v,1);
        dfs_d(v,u,2);
        r[v]=dm[v];r[u]=dm[u];
        dfs_r(u,v,0);dfs_r(v,u,0);
        for (int j = 1; j <=n ; ++j) {
            if(col[j]==1&&r[j]<r[u])u=j;
            if(col[j]==2&&r[j]<r[v])v=j;
        }
        ans=min(ans,max(d_max,r[u]+r[v]+w)*1ll);
    }
    cout<<ans;
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值