标签:选择 因此 signed freopen 16px 需要 相对 相等 方案
恩这题在以前的DP专题里也说过,同样没打代码
圣诞老人共用有个饼干,准备全部分给N个孩子。每个孩子有一个贪婪度,第i个孩子的贪婪度为g[i]。如果有a[i]个孩子拿到的饼干数比第i个孩子多,那么第i个孩子会产生g[i]*a[i]的怨气。给定N,M和序列g,圣诞老人请你帮他安排一种分配方式,使得每一个孩子至少分到一块饼干,并且所以孩子的怨气总和最小。1<=N<=30,N<=M<=5000
每个孩子的怒气值与其他孩子获得的饼干数量相关联
emmm似乎很难对状态进行划分得到“子结构”,也很难计算
出每个孩子的怒气值
仔细(简单?)思考后我们发现,贪婪度大的孩子应该得到更多的饼干,因此首先可以把孩子的贪婪度从大到小,孩子得到饼干的数量将是单调递减的
设f[i,j]表示前i个孩子一个分配了j块饼干时,怒气值总和的最小值,直观的思考是考虑分配给第i+1个孩子多少饼干,然后进行转移。
转移时有两种情况:
1.当前孩子的贪婪度比下一个孩子大,即:第i个孩子的饼干数比第i+1个孩子多,a[i+1]=i;
2.当前孩子的贪婪度与下一个孩子一样,即:第i个孩子的饼干数与第i+1个孩子一样,那么这时我们还需要知道前i个孩子中有多少个获得饼干数与第i个孩子相同才能求出a[i+1]
总而言之,无论哪种情况,我们都需要知道第i个孩子获得的饼干数,以及i前面有多少个孩子与i获得的饼干数相同,然而在现有DP状态下,很难高效维护这两个信息,虽然不是没法维护,比如我们添加两个维度去记录,那是那样我们要多大的数组?四维数组,极限一共30个孩子,5000块饼干,30*5000*30^2,135000000,10位数的总空间,显然没法写。
不扩维呢,我们需要多开数组,似乎并非不可写?我们似乎确实可以做得到,但是我们对于每个孩子获得多少饼干似乎并没办法得到实现,或者实现麻烦(我懒得想了qwq)
那么我们不妨对状态转移做一个等价交换,
1.若第i个孩子获得的饼干数大于1,则等价于分配j-i个饼干给前i个孩子,也就是说平均每个孩子都少分一块饼干,获得饼干数的相对大小顺序不变,从而怨气和也不变
2.若第i个孩子获得的饼干数为1,枚举i前面有多少个孩子也获得1块饼干
想想为什么能这样,我们在第i个孩子那,用j-i的方式强行使前i个孩子每个人获得的饼干都少了1,那么这样不断-1-1-1-1...下去,肯定会出现到最后只获得了1块的情况,由1状态可知,这样操作下去并不会是结果有任何变化,所以2状态并不会对结果产生影响,这样我们也就能得到状态转移方程
i
f[i,j] = min{f[i,j-i], min{f[k,j-(i-k)] + k * Σg[p]}(0<=k<i)]}
p=k+1
i
对min中嵌套的min,我们先挨个枚举k,f[k,j-(i-1)]表示到第k个孩子,共分了j-(i-k)块饼干。k*Σg[p]则表示假设从第k
p=k+1
个孩子向后获得的饼干数都相同,总的怨气和,这样一遍后,我们得到加入i之前有和i获得饼干相等的情况,所能得到的最小怨气和,然后再与i之前没有孩子与i获得饼干数相等的情况所需的怨气和,就能得到第i个孩子得到第j个饼干并且怨气和最小结果
初态:f[0][0],末态:f[n,m]
分析一下,O(nmk)(0<=k<i),加上n和k的范围极大的小于m,m也仅有5000,可写
这道题启发我们,有时可以通过额外的算法确定DP计算顺序,有时可以在状态空间中运用等效手段对状态进行缩放。这样一般可以使需要计算的问题得到极大的简化
在本题中,在DP前对N个孩子执行排序,使他们获得的饼干单调递减,利用相对大小不变性,把第i+1个孩子获得的饼干缩放到1,在考虑i前面有多少个孩子获得的饼干数量相等,使问题得到极大的简化,容易进行维护和转移
最后在新简单说一下枚举的问题,枚举的k表示的是从k开始到i全部只获得了1块饼干,也就是说从k到i获得的饼干数量相同。为什么这么枚举呢?我们都把怒气值排过序了,自然按照怒气值来就好了
最后输出方案时,由于本题有spj,只需输出其中一种方案即可
1 #include<bits/stdc++.h> 2 #define ll long long 3 #define uint unsigned int 4 #define ull unsigned long long 5 using namespace std; 6 const int maxm = 5100; 7 int g[maxm], id[maxm]; 8 int f[31][maxm]; 9 int jc[31][maxm], bi[31][maxm]; 10 int s[maxm], ans[maxm]; 11 int n, m; 12 13 inline int read() { 14 int x = 0, y = 1; 15 char ch = getchar(); 16 while(!isdigit(ch)) { 17 if(ch == ‘-‘) y = -1; 18 ch = getchar(); 19 } 20 while(isdigit(ch)) { 21 x = (x << 1) + (x << 3) + ch - ‘0‘; 22 ch = getchar(); 23 } 24 return x * y; 25 } 26 27 inline bool cmp(int a, int b) { 28 return g[a] > g[b];} 29 30 void print(int n, int m) { 31 if(n == 0) return; 32 print(jc[n][m], bi[n][m]); 33 if(jc[n][m] == n) //当前阶段不存在与选择的孩子所发到的饼干数量相同的孩子 34 for(int i = 1; i <= n; ++i) ans[id[i]]++;//从第1到当前位置所有孩子全部加上一块饼干 35 else for(int i = jc[n][m] + 1; i <= n; ++i) ans[id[i]] = 1;//如果不相同,存在分到饼干相同的孩子 36 //将这些孩子缩放到1,因为我们逐层退出递归的顺序与DP计算的顺序相同,所以计算方案是模拟DP转移思路计算 37 } 38 39 int main() { 40 freopen("_.in", "r", stdin); 41 freopen("_.out", "w", stdout); 42 n = read(), m = read(); 43 for(int i = 1; i <= n; ++i) { 44 g[i] = read(); 45 id[i] = i; 46 } 47 sort(id + 1, id + n + 1, cmp); 48 for(int i = 1; i <= n; ++i) 49 s[i] = s[i - 1] + g[id[i]]; 50 memset(f, 0x3f, sizeof(f)); 51 f[0][0] = 0; 52 for(int i = 1; i <= n; ++i) 53 for(int j = i; j <= m; ++j) { 54 f[i][j] = f[i][j - i]; 55 jc[i][j] = i, bi[i][j] = j - i; 56 for(int k = 0; k < i; ++k) {//从k到i获得饼干数全为1 57 if(f[i][j] > f[k][j - (i - k)] + k * (s[i] - s[k])) { 58 f[i][j] = f[k][j - (i - k)] + k * (s[i] - s[k]); 59 jc[i][j] = k, bi[i][j] = j - (i - k); 60 } 61 } 62 } 63 printf("%d\n", f[n][m]); 64 print(n, m); 65 for(int i = 1; i <= n; ++i) 66 printf("%d ", ans[i]); 67 printf("\n"); 68 return 0; 69 }
标签:选择 因此 signed freopen 16px 需要 相对 相等 方案
原文地址:https://www.cnblogs.com/ywjblog/p/9744438.html