大家佛( cut )
题目描述
提交答案题。
把一个矩阵划分成k个四连通块,使得包含的所有点的和最大的一个连通块的这个和与包含的所有点的和最小的一个连通块的这个和的差最小。
部分分(骗分)做法
A:手算
针对前几个规模较小的点可以这样做。
B:蛇形填
先求出每个连通块的和的平均值(\(\sum a_{ij}\div k\)),蛇形填的时候一旦超过这个值,就换下一个连通块。
#include <bits/stdc++.h>
using namespace std;
long long n, m, k, w, mat[1001][1001], id[1001][1001];
double every;
int main()
{
char in[20], out[20];
for (int i = 1, cnt; i <= 10; i++)
{
sprintf(in, "cut%d.in", i);
sprintf(out, "cut%d.out", i);
FILE *fin = fopen(in, "r"), *fout = fopen(out, "w");
fscanf(fin, "%d%d%d%d", &n, &m, &k, &w);
every = 0;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
fscanf(fin, "%d", &mat[i][j]);
every += (double)mat[i][j] / k; //边加边除,防止全加起来以后会爆
}
}
every = floor(every);
long long now = 0, idd = 1;
for (int i = 1; i <= n; i++)
{
if (i % 2) //奇数行从左向右填
{
for (int j = 1; j <= m; j++)
{
id[i][j] = idd;
now += mat[i][j];
if (idd < k && now >= every)//超过平均值就换下一组,但要注意处理最后一个连通块
idd++, now = 0;
}
}
else //偶数行从右向左填
{
for (int j = m; j >= 1; j--)
{
id[i][j] = idd;
now += mat[i][j];
if (idd < k && now >= every)
idd++, now = 0;
}
}
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
fprintf(fout, "%d ", id[i][j]);
}
fprintf(fout, "\n");
}
}
}
这个做法已经可以取得一定的分数。
C:数学方法
针对6、7两个点,矩阵里所有的数都是1。
以6号测试点为例,\(n=m=200,k=97\),此时可以求出平均值为\(200\times200\div97\approx412.37113\),那么我们不妨就用大小分别为412和413的连通块来填充。
设大小为412的连通块有\(x\)个,那么\(412x+413(97-x)=40000\),解得\(x=61\)。那么就可以先填61个大小为412的,再填36个大小为413的。
#include <bits/stdc++.h>
using namespace std;
long long n, m, k, w, mat[1001][1001], id[1001][1001];
int main()
{
char in[20], out[20];
for (int i = 6, cnt; i <= 6; i++)
{
sprintf(in, "cut%d.in", i);
sprintf(out, "cut%d.out", i);
FILE *fin = fopen(in, "r"), *fout = fopen(out, "w");
fscanf(fin, "%d%d%d%d", &n, &m, &k, &w);
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
fscanf(fin, "%d", &mat[i][j]);
}
}
long long now = 0, idd = 1;
for (int i = 1; i <= n; i++)
{
if (i % 2)
{
for (int j = 1; j <= m; j++)
{
id[i][j] = idd;
if (idd <= 61)
{
if (now == 412)
{
idd++;
now = 0;
}
}
else
{
if (now == 413)
{
idd++;
now = 0;
}
}
now++;
}
}
else
{
for (int j = m; j >= 1; j--)
{
id[i][j] = idd;
if (idd <= 61)
{
if (now == 412)
{
idd++;
now = 0;
}
}
else
{
if (now == 413)
{
idd++;
now = 0;
}
}
now++;
}
}
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
fprintf(fout, "%d ", id[i][j]);
}
fprintf(fout, "\n");
}
}
}
7号同理。
#include <bits/stdc++.h>
using namespace std;
long long n, m, k, w, mat[1001][1001], id[1001][1001];
int main()
{
char in[20], out[20];
for (int i = 7, cnt; i <= 7; i++)
{
sprintf(in, "cut%d.in", i);
sprintf(out, "cut%d.out", i);
FILE *fin = fopen(in, "r"), *fout = fopen(out, "w");
fscanf(fin, "%d%d%d%d", &n, &m, &k, &w);
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
fscanf(fin, "%d", &mat[i][j]);
}
}
long long now = 0, idd = 1;
for (int i = 1; i <= n; i++)
{
if (i % 2)
{
for (int j = 1; j <= m; j++)
{
id[i][j] = idd;
if (idd <= 110)
{
if (now == 314)
{
idd++;
now = 0;
}
}
else
{
if (now == 315)
{
idd++;
now = 0;
}
}
now++;
}
}
else
{
for (int j = m; j >= 1; j--)
{
id[i][j] = idd;
if (idd <= 110)
{
if (now == 314)
{
idd++;
now = 0;
}
}
else
{
if (now == 315)
{
idd++;
now = 0;
}
}
now++;
}
}
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
fprintf(fout, "%d ", id[i][j]);
}
fprintf(fout, "\n");
}
}
}
D:平均撕
每次把这个矩阵撕成两部分尽量平均的,撕的过程中使用随机退火之类算法。但是这个算法在“密度较大”(即nm小K大)矩阵上表现的很优秀,但是密度越小的矩阵就越不优秀。
E:半随机
先预定这些块的样子,然后在这些块之间使用DP,随机退火,指数枚举等方法找到最优解。比如可以将矩阵分成网格状的K块,或者是长条形的绕起来。这些方法也能得到不错的分数。