题解:P11132 【MX-X5-T4】「GFOI Round 1」epitaxy
1.做法及证明
因为 \(n\) 一定会被包含在某一区间内,所以最后答案肯定是 \(n\) 的因数。
先给出结论:对于 \(n\) 的因数 \(d\),其合法的充要条件为 \(d\le m\),所以我们只需要找到第一个小于等于 \(m\) 的 \(d\) 即可。
接下来我们来证明。
下文用 \(i'\) 表示以第 \(i\) 位开头的长度为 \(m\) 的区间,\(p_i\) 同题意。
对于一个值 \(p_i\),其能影响到的 \(i'\) 为所有的 \(j',j\in[i-m+1,i]\)。我们称其管辖着区间 \([i-m+1,i]\)。当然越过边界的自动将左边界变为 \(1\)。
令 \(kd\) 表示最小的成为所管辖区间最大值的值。
显然的,我们需要由 \(d\) 的倍数来作为所有 \(i'\) 的最大值,而一个数可管辖的区间显然长度不超过 \(m\),如果 \(d>m\),那么就说明 \(kd\) 管辖的区间无法填入所有大于 \((k-1)d\) 的数,那么由 \((k-1)d\) 管辖的区间就一定会与某个大于其的数所管辖区间相交,那么一定存在 \(i'\) 的最大值不为 d 的倍数,不合法。
相反的,如果 \(d\le m\) 就一定不存在 \(i'\) 的最大值不为 d 的倍数,因为所有大于 \((k-1)d\) 的数都被填掉了。
知道以上几点就很好构造了,只要在 \(m\) 的倍数的位置,从大到小填入 \(d\) 的倍数,接着在空位从大到小填入剩下没填的数即可。
2.Code:
因为写的时候很难受,所以很丑,轻喷。
#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define m_p make_pair
#define m_t make_tuple
#define N 1000010
using namespace std;
int a[N];
bitset<N> vis;
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int T;
int n, m, qn, d, x, fl, num;
vector<int> p;
cin >> T;
while (T--)
{
cin >> n >> m;
p.clear();
qn = sqrt(n);
for (int i = 1; i <= n; i++)
a[i] = vis[i] = 0;
for (int i = 1; i < qn; i++)
{
if (n % i)
continue;
p.push_back(i);
p.push_back(n / i);
}
if (qn * qn == n)
p.push_back(qn);
else if (n % qn == 0)
{
p.push_back(qn);
p.push_back(n / qn);
}
sort(p.begin(), p.end());
for (int i = p.size() - 1; i >= 0; i--)
{
d = p[i];
if (d <= m)
break;
}
x = n;
for (int i = m; i <= n; i += m)
{
a[i] = x;
vis[x] = 1;
x -= d;
}
x = n;
for (int i = 1; i <= n; i++)
{
if (a[i])
cout << a[i] << " ";
else
{
while (vis[x])
--x;
cout << x << " ";
--x;
}
}
cout << "\n";
}
return 0;
}
人生中距离 AK 最近的一次,结果我没有把握好。
作为 J 组的最后一题,如果我当时写了对拍,或许结果就会不一样了。