Cookies

恩这题在以前的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 }

 

posted @ 2018-10-05 11:25  YuWenjue  阅读(411)  评论(0编辑  收藏  举报