先大喊一句
最大值最小 用 二分
综合性很强的题目,用到了 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;
}

989

被折叠的 条评论
为什么被折叠?



