【Luogu P3237】[HNOI2014]米特运输

在以米特为主要能源的D星上,通过构建高速通道网络实现米特资源的储存和传输。考虑到技术限制,需调整各城市的米特储存器容量以确保安全高效的能源分配。

题目链接

题目描述

米特是D星球上一种非常神秘的物质,蕴含着巨大的能量。在以米特为主要能源的D星上,这种米特能源的运输和储存一直是一个大问题。

D星上有N个城市,我们将其顺序编号为1到N,1号城市为首都。这N个城市由N-1条单向高速通道连接起来,构成一棵以1号城市(首部)为根的树,高速通道的方向由树中的儿子指向父亲。树按深度分层:根结点深度为0,属于第1层;根结点的子节点深度为1,属于第2层;依此类推,深度为i的结点属于第i+l层。

建好高速通道之后,D星人开始考虑如何具体地储存和传输米特资源。由于发展程度不同,每个城市储存米特的能力不尽相同,其中第i个城市建有一个容量为A[i]的米特储存器。这个米特储存器除了具有储存的功能,还具有自动收集米特的能力。

如果到了晚上六点,有某个储存器处于未满的状态,它就会自动收集大气中蕴含的米特能源,在早上六点之前就能收集满;但是,只有在储存器完全空的状态下启动自动收集程序才是安全的,未满而又非空时启动可能有安全隐患。

早上六点到七点间,根节点城市(1号城市)会将其储存器里的米特消耗殆尽。根节点不会自动搜集米特,它只接受子节点传输来的米特。

早上七点,城市之间启动米特传输过程,传输过程逐层递进:先是第2层节点城市向第1层(根节点城市,即1号城市)传输,直到第1层的储存器满或第2层的储存器全为空;然后是第3层向第2层传输,直到对于第2层的每个节点,其储存器满或其予节点(位于第3层)的储存器全为空;依此类推,直到最后一层传输完成。传输过程一定会在晚上六点前完成。

由于技术原因,运输方案需要满足以下条件:

(1)不能让某个储存器到了晚上六点传输结束时还处于非空但又未满的状态,这个时候储存器仍然会启动自动收集米特的程序,而给已经储存有米特的储存器启动收集程序可能导致危险,也就是说要让储存器到了晚上六点时要么空要么满;

(2)关于首都——即1号城市的特殊情况, 每天早上六点到七点间1号城市中的米特储存器里的米特会自动被消耗殆尽,即运输方案不需要考虑首都的米特怎么运走;

(3)除了1号城市,每个节点必须在其子节点城市向它运输米特之前将这座城市的米特储存器中原本存有的米特全部运出去给父节点,不允许储存器中残存的米特与外来的米特发生混合;

(4)运向某一个城市的若干个来源的米特数量必须完全相同,不然,这些来源不同的米特按不同比例混合之后可能发生危险。

现在D星人已经建立好高速通道,每个城市也有了一定储存容量的米特储存器。为了满足上面的限制条件,可能需要重建一些城市中的米特储存器。你可以,也只能,将某一座城市(包括首都)中屎来存在的米特储存器摧毁,再新建一座任意容量的新的米特储存器,其容量可以是小数(在输入数据中,储存器原始容量是正整数,但重建后可以是小数),不能是负数或零,使得需要被重建的米特储存器的数目尽量少。

题解

读懂题意很重要!
值得注意的是如果一个点有一些儿子节点是叶子节点,貌似可以那些是叶子节点的儿子节点不向父节点运输米特(其他不是叶子的儿子节点把父节点的储存器填满),这样依然能够满足每一个储存器满或空的条件,但这样是不行的(可能是自己太渣无法理解题意吧)。

那么我们就很简单知道有这么一个结论:
如果某个点的父节点的儿子数是k,那么该点权值应为父节点的k分之一即父节点权值应为该点的k倍。
于是如果有一个点的权值确定了,那么整棵树就确定了,假设某一个点的权值不变,那么根节点的权值为该点权值的其到根路径上的点(不包括自己)的儿子数乘积倍。且对于每个点若算出的根节点权值相同,则为同一种合法方案。

那么我们就考虑怎么统计。
本人太弱不会哈希,因此只能 暴力多个数取模+排序 来统计了……(简单粗暴)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<queue>
#include<vector>
#include<map>
#define hash HASH
using namespace std;
inline int read()
{
    int x=0;char ch=getchar();int t=1;
    for(;ch>'9'||ch<'0';ch=getchar())if(ch=='-') t=-1;
    for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<1)+(x<<3)+(ch-48);
    return x*t;
}

const int N=5e5+100;
struct edge{
    int to,next;
}a[N<<1];
int head[N];int cnt;
inline void add(int x,int y){a[++cnt]=(edge){y,head[x]};head[x]=cnt;}
typedef unsigned long long ll;
int n;
const ll mod[3]={998244353,14233333,1000000007};
int w[N];
struct Number{
    ll x[3];
    inline void clear(){x[0]=x[1]=x[2]=0;return ;}
    inline Number operator *(const ll y) {
        Number c;c.clear();
        for(int i=0;i<3;i++) c.x[i]=x[i]*y%mod[i];
        return c;
    }
    inline bool operator <(Number b)const{return x[0]<b.x[0];}
    inline bool operator ==(Number b){return (x[0]=b.x[0]&&x[1]==b.x[1]&&x[2]==b.x[2]);}
}fac[N];
ll k[N];
inline void dfs(int u,int fa)
{
    for(register int v,i=head[u];i;i=a[i].next){v=a[i].to;if(v==fa) continue;k[u]++;}
    for(register int v,i=head[u];i;i=a[i].next)
    {
        v=a[i].to;
        if(v==fa) continue;
        fac[v]=fac[u]*k[u];
        dfs(v,u);
    }
}
Number st[N];
int main()
{
    n=read();
    for(register int i=1;i<=n;i++) w[i]=read();
    register int x,y;
    for(register int i=1;i<n;i++){
        x=read();y=read();
        add(x,y);add(y,x);
    }
    register int ans=1;
    fac[1]=(Number){1,1,1};
    dfs(1,0);int head=0;
    for(register int i=1;i<=n;i++) {
        register Number res=fac[i]*w[i];
        st[++head]=res;
    }
    sort(st+1,st+1+n);
    for(head=1;head<n;head++)
    {
        int sum=1;
        while(st[head]==st[head+1]&&head<n) sum++,head++;
        ans=max(ans,sum);
    }
    printf("%d\n",n-ans);
}
洛谷P1177是【模板】排序题,可使用归并排序来解决。归并排序的核心思想是分治法,即将一个大问题分解为多个小问题,分别解决后再合并结果。 归并排序主要步骤如下: 1. **分解**:将待排序数组从中间分成两个子数组,递归地对这两个子数组进行排序。 2. **合并**:将两个已排序的子数组合并成一个有序数组。 以下是使用归并排序解决洛谷P1177题目的代码实现: ```cpp #include<bits/stdc++.h> #include<iomanip> using namespace std; #define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0) const int MAXN = 1e5 + 5; int a[MAXN], b[MAXN]; int n; // 数组长度 // 合并两个已排序的子数组 void mergesort(int l1, int r1, int l2, int r2) { int i = l1, j = l2, k = l1; while (i <= r1 && j <= r2) { if (a[i] <= a[j]) { b[k++] = a[i++]; } else { b[k++] = a[j++]; } } while (i <= r1) b[k++] = a[i++]; while (j <= r2) b[k++] = a[j++]; for (i = l1; i <= r2; i++) { a[i] = b[i]; } } // 递归进行归并排序 void merge(int l, int r) { if (l >= r) { return; } int mid = (l + r) / 2; merge(l, mid); merge(mid + 1, r); mergesort(l, mid, mid + 1, r); } int main() { IOS; cin >> n; for (int i = 0; i < n; i++) { cin >> a[i]; } merge(0, n - 1); for (int i = 0; i < n; i++) { cout << a[i]; if (i < n - 1) cout << " "; } cout << endl; return 0; } ``` 上述代码中,`merge`函数用于递归地将数组分解为子数组,`mergesort`函数用于合并两个已排序的子数组。在`main`函数中,首先读取输入的数组,然后调用`merge`函数进行排序,最后输出排序后的数组。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值