已知N个正整数:A1、A2、……、An 。今要将它们分成M组,使得各组数据的数值和最平均,即各组的均方差最小。均方差公式如下:
σ = ∑ i = 1 M ( x i − x ‾ ) M \sigma= \sqrt{\frac{\sum_{i=1}^M(x_{i}-\overline{x})}{M}} σ=M∑i=1M(xi−x)
x ‾ = ∑ i = 1 M x i M \overline{x}=\frac{\sum_{i=1}^M x_{i}}{M} x=M∑i=1Mxi
,其中σ为均方差, x ‾ \overline{x} x是各组数据和的平均值, x i x_i xi为第i组数据的数值和。
输入文件data.in包括:
第一行是两个整数,表示N,M的值(N是整数个数,M是要分成的组数)
第二行有N个整数,表示A1、A2、……、An。整数的范围是1–50。
(同一行的整数间用空格分开)
输出文件data.out包括一行,这一行只包含一个数,表示最小均方差的值(保留小数点后两位数字)。
6 3
1 2 3 4 5 6
0.00
博主的第一反应:这不是贪心吗?于是写了一个玄学暴力程序:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define R register
#define IN inline
#define W while
#define db double
#define gc getchar()
#define EPS 1e-8
using namespace std;
multiset <int> st;
multiset <int> :: iterator it, itt;
int data[100];
int empty = 0;
db va[100], tott, ave;
int main()
{
db minn = 1e60;
srand(time(0));
int num, div;
scanf("%d%d", &num, &div);
for (R int i = 1; i <= num; ++i) scanf("%d", &data[i]), ave += data[i];
ave /= div;
int time = 150000;
R int value, cnt;
W (time--)
{
st.clear();
for (R int i = 1; i <= div; ++i) st.insert(empty);
random_shuffle(data + 1, data + 1 + num);
for (R int i = 1; i <= num; ++i)
{
it = st.begin();
value = *it ;
value += data[i];
st.erase(it);
st.insert(value);
}
int top = 0;
for (it = st.begin(); it != st.end(); ++it)
{
top += (*it - ave) * (*it - ave);
}
if(top < minn) minn = top;
}
printf("%.2lf", sqrt(minn / div));
return 0;
}
然后成功地苟到了20分…
反过来思考一下, 如此暴力用到了set, 使得每次操作都带了一个log, 不利于大范围枚举, 并且只靠random_shuffle进行重新排列每次需要O(N)的时间, 十分浪费时间, 并且不可能枚举到所有情况。于是我们用到了更优的算法——退火算法。
退火算法也是一种玄学的随机算法。先将数据随机分配到每一组中, 再随时间随机调整。 开始时允许向局部次优解移动, 随时间的推移逐渐接近最优解, 此时将判定条件逐渐变得严格, 使解逐渐趋于最优解。这道题中我们通过调整移动数据的条件来限制解的移动, 具体见代码…
# include
#include
#include
#include
#include
#include
#include
using namespace std;
#define R register
#define W while
#define IN inline
#define gc getchar()
#define db double
#define MX 105
int data[MX], tot[MX], id[MX];
db ans, minn = 1e60, now, bound, tim, aver;
int dot, dv, pos, belong, tar;
int main()
{
srand(20020220);
scanf("%d%d", &dot, &dv);
for (R int i = 1; i <= dot; ++i)
{
scanf("%d", &data[i]);
aver += data[i];
}
aver /= (double) dv;
tim = 10000;
W (tim--)
{
memset(tot, 0, sizeof(tot));
ans = 0;
for (R int i = 1; i <= dot; ++i)
{
id[i] = rand() % dv + 1;
tot[id[i]] += data[i];
}
for (R int i = 1; i <= dv; ++i)
{ans += (aver - tot[i]) * (aver - tot[i]);}
bound = 10000;
W (bound > 0.01)
{
bound *= 0.9;
pos = rand() % dot + 1;
belong = id[pos];
if(bound > 500) tar = min_element(tot + 1, tot + 1 + dv) - tot;
now = ans;
now -= (tot[belong] - aver) * (tot[belong] - aver);
now -= (tot[tar] - aver) * (tot[tar] - aver);
tot[belong] -= data[pos];
tot[tar] += data[pos];
now += (tot[belong] - aver) * (tot[belong] - aver);
now += (tot[tar] - aver) * (tot[tar] - aver);
if(now < ans) ans = now, id[pos] = tar;
else if (rand() % 10000 > bound)
{
tot[belong] += data[pos];
tot[tar] -= data[pos];
}
else
{
ans = now, id[pos] = tar;
}
}
minn = min(ans, minn);
}
printf("%.2lf", sqrt(minn / dv));
return 0;
}