ccf 202012-4 食材运输

先大喊一句

最大值最小 用 二分

综合性很强的题目,用到了 dfs,二分,dp
这个dfs就很难想,思路提前有了一点,就是不知道怎么dfs

这个是别人的代码。

#include<bits/stdc++.h>
using namespace std;
const int N=110,M=11,inf=1<<29;
typedef long long ll;
typedef pair<int,int> pii;
#define mk(a,b) make_pair(a,b)
int d[N][M],n,m,k; //d[i][j]从i出发走完食材j的所有点的最小时间,即运送时间表
int tot,head[N],g[N][M] ;
struct Edge{
    int v,next,w;
}edge[N*2];
void add(int u,int v,int w) {
    edge[++tot]={v,head[u],w};
    head[u]=tot;
}
pii dfs(int u,int fa,int op) {
    pii res(0,-1);//first表示总时间长,second表示最长的点到根节点的时长
    if(g[u][op] ) res.second=0;
    for(int i=head[u];i;i=edge[i].next) {
        int v=edge[i].v,w=edge[i].w;
        if(v==fa) continue;
        auto t=dfs(v,u,op);
        if(t.second != -1) { //子树中 是否有需要的点
            res.first += t.first+w*2;
            res.second=max(res.second,t.second+w);
        }
    } return res;
}
int state[N], f[1<<M];
bool check(int mid) {
    memset(state,0,sizeof state);
    for(int i=1;i<=n;i++) { //状态
        for(int j=0;j<k;j++) {
            if(d[i][j]<=mid) state[i] |= 1<<j;
        }
    }

    memset(f,0x3f,sizeof f);
    f[0] = 0 ;
    for(int i=1;i<=n;i++ ) { //阶段:每个行的选择
        for(int j=0;j< 1<<k ; j++){ //状态划分后的位置
            f[j|state[i]]=min(f[j|state[i]],f[j]+1); //用或者不用第i行数据
            //此时f中状态j为1~i行中可以到达j状态的最小值
        }
    }
    return f[(1<<k)-1]<=m;
}
int main(){
    ios::sync_with_stdio(false); cin.tie(0);cout.tie(0);
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++)
        for(int j=0;j<k;j++) cin>>g[i][j];
    for(int i=1;i<n;i++) {
        int a,b,c; cin>>a>>b>>c;
        add(a,b,c),add(b,a,c);
    }

    for(int i=1;i<=n;i++) {
        for(int j=0;j<k;j++) {
            auto t=dfs(i,-1,j);
                d[i][j] = t.first-t.second;//以第i个点为起点,运输第j种食材所花最短时间
        }
    }

    int l=0,r=inf;
    while(l<r) {
        int mid=(l+r)/2;
        if(check(mid)) r=mid;
        else l=mid+1;
    }
    cout<<r<<'\n';

    return 0;
}

这个是我看懂之后照着写的。

#include<bits/stdc++.h>
#define go(i,a,b) for(int i=a;i<=b;++i)
#define ll long long
#define pii pair<int,int>
using namespace std;
int n,m,k;
int g[110][20];
int tot=0;
int head[110];//对于head[u],在建树的时候,总是等于向当前  最后一条 起点为u的边 的下标
int d[110][20];//起点为i 时运输第j种食材 所需最短时间
struct Edge{
int v,w;
//last把 起点相同的边下标 串成一串
int last;//上一个与该边起点相同的边的下标

}edge[300];

void add(int u,int v,int w){
edge[++tot]={v,w,head[u]};

head[u]=tot;//是等于向当前  最后一条 起点为u的边 的下标

}

pii dfs(int u,int fa,int food){//如果返回的pii.second==-1, 那么说明该点不是不要food,子节点也不要food,
    pii res(0,-1);
    if(g[u][food])res.second=0;
    for(int i=head[u];i;i=edge[i].last){
        if (edge[i].v==fa)continue;

        pii t=dfs(edge[i].v,u,food);

        if(t.second!=-1){
            res.first+=t.first+2*edge[i].w;
            res.second=max(res.second,t.second+edge[i].w);


        }



    }

    return res;




}

int s[(1<<11)];
bool check(int a){
int sm=(1<<k)-1;
int dp[(1<<11)];

    memset(s,0,sizeof(s));

go(i,1,n){

    go(j,0,k-1){
        if(d[i][j]<=a)
            s[i]|=1<<j;//对于每个起点,多选食材 不消耗 m ,所以可以使贡献最大化

    }


}


memset(dp,0x3f,sizeof(dp));
dp[0]=0;
//找到需要 起点数 最少的情况

go(ss,0,sm){
    go(i,1,n){
        //对于当前每个dp[ss],已经是最优的
        //dp[ ss|s[i] ] 的最优情况一定是由比它小的状态的 某个最优值推出来的
        dp[ ss|s[i] ]=min( dp[ ss|s[i] ],dp[ss]+1);



    }



}

return dp[sm]<=m;


}





int main(){



scanf("%d%d%d",&n,&m,&k);
go(i,1,n)
    go(j,0,k-1) scanf("%d",&g[i][j]);

go(i,1,n-1){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);

add(a,b,c);add(b,a,c);

}



//求起点为i 时运输第j种食材 所需最短时间d[i][j]
go(i,1,n){
    go(j,0,k-1){
        pii t=dfs(i,-1,j);
        d[i][j]=t.first-t.second;
       // printf("%d ",d[i][j]);
    }
//printf("\n");
}



int l=0,r=200000000+50;
while(l<r){
    int mid=(r+l)/2;
    if(check(mid)) r=mid;
    else l=mid+1;
}

printf("%d",r);

return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值