NOI 春季测试题解
T1. 涂色游戏
记录每一行和每一列最后一次修改的时间戳。
对于 \((i,j)\),我们取第 \(i\) 行和第 \(j\) 列最晚的时间戳,就是它的颜色。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define max(x...) max({x})
#define min(x...) min({x})
#define FOR(i, x, y) for(int i = (x); i <= (y); i++)
#define ROF(i, x, y) for(int i = (x); i >= (y); i--)
inline int rd()
{
int sign = 1, re = 0; char c = getchar();
while(!isdigit(c)){if(c == '-') sign = -1; c = getchar();}
while(isdigit(c)){re = re * 10 + (c - '0'); c = getchar();}
return sign * re;
}
const int N = 1e5 + 5;
int n, m, q, x[N], y[N], cl[N];
inline void solve()
{
n = rd(), m = rd(); q = rd();
FOR(i, 1, n) x[i] = 0;
FOR(i, 1, m) y[i] = 0;
FOR(i, 1, q)
{
int opt = rd(), p = rd();
cl[i] = rd();
if(opt) y[p] = i;
else x[p] = i;
}
FOR(i, 1, n)
{
FOR(j, 1, m) printf("%d ", x[i] > y[j] ? cl[x[i]] : cl[y[j]]);
puts("");
}
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);
#endif
int T = rd();
while(T--) solve();
return 0;
}
T2. 幂次
初始想法:
我们考虑枚举底数 \(a\)。
一个显然的结论:如果 \(a\) 能被表示成 \(a=x^b(b>1)\),那么它不能再作为底数进行计算答案。
对于 \(k\ge 3\),显然 \(a\le 1e6\),因此我们只需要枚举到 \(1e6\) ,每个 \(a\) 操作 \(k\) 次不到,并用 unordered_map 标记 \(\le 1e6\) 的数即可。
对于 \(k=2\),我们发现,对于 \(1e6 < a \le 1e9\) 的底数,它只能贡献到 \(a^2\),设这样的数的个数为 \(p\),因此我们依旧是枚举到 \(1e6\),再标记 \(\le 1e6\) 的数的同时,我们把 \((1e6,1e9]\) 之间的数从 \(p\) 中减去,最后答案加上 \(p\) 即可。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define max(x...) max({x})
#define min(x...) min({x})
#define FOR(i, x, y) for(int i = (x); i <= (y); i++)
#define ROF(i, x, y) for(int i = (x); i >= (y); i--)
inline LL rd()
{
LL sign = 1, re = 0; char c = getchar();
while(!isdigit(c)){if(c == '-') sign = -1; c = getchar();}
while(isdigit(c)){re = re * 10 + (c - '0'); c = getchar();}
return sign * re;
}
LL n; int k;
unordered_map<LL, bool> f;
LL sum = 1;
signed main()
{
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);
#endif
n = rd(), k = rd();
if(k == 1) return printf("%lld", n), 0;
if(k >= 60) return puts("1"), 0;
if(k > 2 || n <= 1e12)
{
FOR(i, 2, 1e6) if(!f.count(i))
{
if(i > n) break;
LL t = i; bool ok = 1;
FOR(j, 2, k)
{
if((__int128)t * i > n) { ok = 0; break; }
if(t <= 1e6) f[t] = 1;
t *= i;
}
if(!ok) break;
FOR(j, k, 59)
{
sum++;
if(t <= 1e6) f[t] = 1;
if((__int128)t * i > n) break;
t *= i;
}
}
printf("%lld", sum);
return 0;
}
LL B = sqrt(n), p = B - 1e6;
FOR(i, 2, 1e6) if(!f.count(i))
{
LL t = 1ll * i * i;
FOR(j, 2, 59)
{
sum++;
if(t <= 1e6) f[t] = 1;
if(1e6 < t && t <= B) p--;
if((__int128)t * i > n) break;
t *= i;
}
}
printf("%lld", sum + p);
return 0;
}
但开始写的时候忘了只是标记 \(\le 1e6\) 的了,导致被卡 unordered_map(实际上可以过),因此有了更优的做法:
我们换个思路,考虑枚举指数 \(b\)。
我们会发现,对于两个数 \(p_1,p_2\),他们统计过的答案的重复部分,就是 \(b=p_1\times p_2\) 的答案。例如 \(4^3=64,~8^2=64,~2^6=64\)。
然后这其实就是一个莫比乌斯函数,因此我们只需要求出每个 \(b\) 的答案的时候,暴力更新 \(\mu\) 即可。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define max(x...) max({x})
#define min(x...) min({x})
#define FOR(i, x, y) for(int i = (x); i <= (y); i++)
#define ROF(i, x, y) for(int i = (x); i >= (y); i--)
inline LL rd()
{
LL sign = 1, re = 0; char c = getchar();
while(!isdigit(c)){if(c == '-') sign = -1; c = getchar();}
while(isdigit(c)){re = re * 10 + (c - '0'); c = getchar();}
return sign * re;
}
LL n; int k, mu[65];
LL sum = 1, ans;
bool chk(LL a, int b)
{
LL t = 1;
FOR(i, 1, b)
{
if((__int128)t * a > n) return 0;
t *= a;
}
return 1;
}
inline void Div(int b)
{
LL l = 1, r = ans;
while(l <= r)
{
LL mid = (l + r) >> 1;
if(chk(mid, b)) l = mid + 1, ans = mid;
else r = mid - 1;
}
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);
#endif
n = rd(), k = rd(); ans = n;
if(k == 1) return printf("%lld", n), 0;
if(k >= 60) return puts("1"), 0;
FOR(i, k, 59)
{
Div(i); sum += (ans - 1) * (mu[i] + 1);
for(int j = i + i; j < 60; j += i) mu[j] -= mu[i] + 1;
}
printf("%lld", sum);
return 0;
}
T3. 圣诞树
先抛出一个结论:如果连线有交,显然不优。
因此,我们连的线一定是凸包的连续的某一段,但不一定是一直是顺时针或逆时针方向,可以交替进行。
因此我们设计一个状态:\(f[l][r][0/1]\) 表示已经将 \(l\sim r\) 的点连起来,当前位置是在 \(l/r\) 上。
然后我们可以得到转移:
答案取 \(\min\{f[1][n-1][0]+dis(1, n),f[1][n-1][1]+dis(n-1,n)\}\)。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define max(x...) max({x})
#define min(x...) min({x})
#define FOR(i, x, y) for(int i = (x); i <= (y); i++)
#define ROF(i, x, y) for(int i = (x); i >= (y); i--)
inline int rd()
{
int sign = 1, re = 0; char c = getchar();
while(!isdigit(c)){if(c == '-') sign = -1; c = getchar();}
while(isdigit(c)){re = re * 10 + (c - '0'); c = getchar();}
return sign * re;
}
int n, pos;
struct Node { double x, y; int i; } p[1005], rp[1005];
inline double dis(Node x, Node y)
{
return sqrt((x.x - y.x) * (x.x - y.x) + (x.y - y.y) * (x.y - y.y));
}
double f[1005][1005][2]; int from[1005][1005][2];
void print(int l, int r, int o)
{
if(l == r) return void(printf("%d ", p[l].i));
if(o) printf("%d ", p[r].i), print(l, r - 1, from[l][r][o]);
else printf("%d ", p[l].i), print(l + 1, r, from[l][r][o]);
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);
#endif
n = rd();
FOR(i, 1, n)
scanf("%lf%lf", &p[i].x, &p[i].y), p[i].i = i;
pos = 1;
FOR(i, 2, n) if(p[i].y > p[pos].y) pos = i;
if(pos != n)
{
rp[n] = p[pos];
for(int i = 1, j = pos % n + 1; i < n; i++, j = j % n + 1)
rp[i] = p[j];
FOR(i, 1, n) p[i] = rp[i];
}
FOR(len, 2, n - 1) for(int i = 1, j = len; j < n; i++, j++)
{
f[i][j][0] = f[i][j][1] = 1e18;
if(f[i][j][0] > f[i + 1][j][0] + dis(p[i], p[i + 1]))
{
f[i][j][0] = f[i + 1][j][0] + dis(p[i], p[i + 1]);
from[i][j][0] = 0;
}
if(f[i][j][0] > f[i + 1][j][1] + dis(p[i], p[j]))
{
f[i][j][0] = f[i + 1][j][1] + dis(p[i], p[j]);
from[i][j][0] = 1;
}
if(f[i][j][1] > f[i][j - 1][0] + dis(p[j], p[i]))
{
f[i][j][1] = f[i][j - 1][0] + dis(p[j], p[i]);
from[i][j][1] = 0;
}
if(f[i][j][1] > f[i][j - 1][1] + dis(p[j], p[j - 1]))
{
f[i][j][1] = f[i][j - 1][1] + dis(p[j], p[j - 1]);
from[i][j][1] = 1;
}
}
printf("%d ", p[n].i);
if(f[1][n - 1][0] + dis(p[1], p[n]) < f[1][n - 1][1] + dis(p[n - 1], p[n])) print(1, n - 1, 0);
else print(1, n - 1, 1);
return 0;
}
T4. 密码锁
首先,我们求出全局最大值和最小值所在的位置。
显然,全局最大值不能和全局最小值放在一起。
因此我们考虑二分一个答案 \(mid\),然后将全局最小值固定在第一行,枚举权值最大值所在的行 \(s\)。
对于第 \(i\) 个锁,我们先判断 \(a[i][1]\) 与全局最小值的差是否 \(\le mid\),以及 \(a[i][s]\) 与全局最大值的差是否 \(\le mid\)。如果都满足,就将剩下的两行的值记下来。然后循环移位重复上面的操作。
现在问题就变成了:二维平面上有若干个点,点有颜色(代表是从第几个锁来的),问是否存在一个位置,使得 \(mid\times mid\) 的矩形中包含所有颜色的点。
这个可以用扫描线+线段树进行维护。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define max(x...) max({x})
#define min(x...) min({x})
#define FOR(i, x, y) for(int i = (x); i <= (y); i++)
#define ROF(i, x, y) for(int i = (x); i >= (y); i--)
inline int rd()
{
int sign = 1, re = 0; char c = getchar();
while(!isdigit(c)){if(c == '-') sign = -1; c = getchar();}
while(isdigit(c)){re = re * 10 + (c - '0'); c = getchar();}
return sign * re;
}
int n, k, Mx[50005];
int a[50005][5];
int mnp, mxp;
int cnt[50005];
#define pii pair<int, int>
vector<pii> pos[30005];
multiset<int> ss[30005];
namespace Seg
{
#define ls (now << 1)
#define rs (now << 1 | 1)
int tr[200005], tag[200005];
void build(int now, int l, int r)
{
tr[now] = tag[now] = 0;
if(l == r) return;
int mid = (l + r) >> 1;
build(ls, l, mid), build(rs, mid + 1, r);
}
inline void dn(int now)
{
if(tag[now])
{
tag[ls] += tag[now], tr[ls] += tag[now];
tag[rs] += tag[now], tr[rs] += tag[now];
tag[now] = 0;
}
}
void modify(int now, int l, int r, int L, int R, int val)
{
if(L <= l && r <= R)
{
tr[now] += val;
tag[now] += val;
return;
}
int mid = (l + r) >> 1; dn(now);
if(L <= mid) modify(ls, l, mid, L, R, val);
if(mid < R) modify(rs, mid + 1, r, L, R, val);
tr[now] = max(tr[ls], tr[rs]);
}
} using namespace Seg;
inline void solve()
{
n = rd();
FOR(j, 1, k) FOR(i, 1, n)
a[i][j] = rd();
if(k == 1)
{
int Mn = 3e4 + 1, Mx = 0;
FOR(i, 1, n) Mn = min(Mn, a[i][1]), Mx = max(Mx, a[i][1]);
return void(printf("%d\n", Mx - Mn));
}
FOR(i, 1, n)
{
int Mn = min_element(a[i] + 1, a[i] + k + 1) - a[i];
if(Mn > 1) rotate(a[i] + 1, a[i] + Mn, a[i] + k + 1);
}
if(k == 2)
{
int Mn = 3e4 + 1, Mx = 0;
FOR(i, 1, n) Mn = min(Mn, a[i][1]), Mx = max(Mx, a[i][1]);
int ans = Mx - Mn;
Mn = 3e4 + 1, Mx = 0;
FOR(i, 1, n) Mn = min(Mn, a[i][2]), Mx = max(Mx, a[i][2]);
ans = max(ans, Mx - Mn);
return void(printf("%d\n", ans));
}
if(k == 3)
{
mxp = mnp = 1;
FOR(i, 1, n)
{
if(a[i][2] > a[i][3]) Mx[i] = 2;
else Mx[i] = 3;
if(i > 1)
{
if(a[i][Mx[i]] > a[mxp][Mx[mxp]]) mxp = i;
if(a[i][1] < a[mnp][1]) mnp = i;
}
}
auto chk = [&](int mid) -> bool
{
int sum = 0;
FOR(s, 2, 3) if(mnp != mxp || (mnp == mxp && Mx[mxp] == s))
{
FOR(i, 1, a[mxp][Mx[mxp]]) pos[i].clear();
memset(cnt, 0, sizeof(cnt)); sum = 0;
FOR(i, 1, n)
{
if(i == mnp)
{
if(a[mxp][Mx[mxp]] - a[i][s] <= mid)
pos[a[i][5 - s]].emplace_back(i, 0);
}
else if(i == mxp)
{
if(s == Mx[i])
{
if(a[i][1] - a[mnp][1] <= mid)
pos[a[i][5 - s]].emplace_back(i, 0);
}
else
{
if(a[i][s] - a[mnp][1] <= mid)
pos[a[i][1]].emplace_back(i, 0);
}
}
else
{
if(s == 2)
{
if(a[i][1] - a[mnp][1] <= mid && a[mxp][Mx[mxp]] - a[i][2] <= mid)
pos[a[i][3]].emplace_back(i, 0);
if(a[i][2] - a[mnp][1] <= mid && a[mxp][Mx[mxp]] - a[i][3] <= mid)
pos[a[i][1]].emplace_back(i, 0);
if(a[i][3] - a[mnp][1] <= mid && a[mxp][Mx[mxp]] - a[i][1] <= mid)
pos[a[i][2]].emplace_back(i, 0);
}
else
{
if(a[i][1] - a[mnp][1] <= mid && a[mxp][Mx[mxp]] - a[i][3] <= mid)
pos[a[i][2]].emplace_back(i, 0);
if(a[i][2] - a[mnp][1] <= mid && a[mxp][Mx[mxp]] - a[i][1] <= mid)
pos[a[i][3]].emplace_back(i, 0);
if(a[i][3] - a[mnp][1] <= mid && a[mxp][Mx[mxp]] - a[i][2] <= mid)
pos[a[i][1]].emplace_back(i, 0);
}
}
}
FOR(i, 1, mid) for(auto [x, y] : pos[i])
if(++cnt[x] == 1) sum++;
FOR(i, mid + 1, a[mxp][Mx[mxp]])
{
for(auto [x, y] : pos[i - mid - 1])
if(--cnt[x] == 0) sum--;
for(auto [x, y] : pos[i])
if(++cnt[x] == 1) sum++;
if(sum == n) return 1;
}
}
return 0;
};
int l = -1, r = a[mxp][Mx[mxp]];
while(l + 1 < r)
{
int mid = (l + r) >> 1;
if(chk(mid)) r = mid;
else l = mid;
}
return void(printf("%d\n", r));
}
mnp = mxp = 1;
FOR(i, 1, n)
{
if(a[i][2] == max(a[i][2], a[i][3], a[i][4])) Mx[i] = 2;
if(a[i][3] == max(a[i][2], a[i][3], a[i][4])) Mx[i] = 3;
if(a[i][4] == max(a[i][2], a[i][3], a[i][4])) Mx[i] = 4;
if(i > 1)
{
if(a[i][1] < a[mnp][1]) mnp = i;
if(a[i][Mx[i]] > a[mxp][Mx[mxp]]) mxp = i;
}
}
auto chk = [&](int mid) -> bool
{
FOR(s, 2, 4) if(mxp != mnp || (mxp == mnp && s == Mx[mxp]))
{
build(1, 1, a[mxp][Mx[mxp]]);
FOR(i, 1, a[mxp][Mx[mxp]]) pos[i].clear();
FOR(i, 1, n) ss[i].clear();
FOR(i, 1, n)
{
if(i == mnp)
{
if(a[mxp][Mx[mxp]] - a[i][s] <= mid)
{
if(s == 2) pos[a[i][3]].emplace_back(i, a[i][4]);
else if(s == 3) pos[a[i][2]].emplace_back(i, a[i][4]);
else pos[a[i][2]].emplace_back(i, a[i][3]);
}
}
else if(i == mxp)
{
int b[5]; FOR(j, 1, 4) b[j] = a[i][j];
rotate(b + 1, b + (((Mx[i] - s) % 4 + 4) % 4 + 1), b + 5);
if(b[1] - a[mnp][1] <= mid)
{
if(s == 2) pos[b[3]].emplace_back(i, b[4]);
else if(s == 3) pos[b[2]].emplace_back(i, b[4]);
else pos[b[2]].emplace_back(i, b[3]);
}
}
else
{
FOR(j, 1, 4)
{
if(a[i][1] - a[mnp][1] <= mid && a[mxp][Mx[mxp]] - a[i][s] <= mid)
{
if(s == 2) pos[a[i][3]].emplace_back(i, a[i][4]);
else if(s == 3) pos[a[i][2]].emplace_back(i, a[i][4]);
else pos[a[i][2]].emplace_back(i, a[i][3]);
}
rotate(a[i] + 1, a[i] + 2, a[i] + 5);
}
}
}
FOR(i, 1, mid) for(auto [x, y] : pos[i])
{
int L = max(y - mid, 1), R = y;
auto it = ss[x].lower_bound(y);
if(it != ss[x].begin())
{
int Ly = *prev(it);
L = max(L, Ly + 1);
}
if(it != ss[x].end())
{
int Ry = *it;
R = min(R, Ry - mid - 1);
}
if(L <= R) modify(1, 1, a[mxp][Mx[mxp]], L, R, 1);
ss[x].insert(y);
}
FOR(i, mid + 1, a[mxp][Mx[mxp]])
{
for(auto [x, y] : pos[i - mid - 1])
{
int L = max(y - mid, 1), R = y;
auto it = ss[x].lower_bound(y);
if(it != ss[x].begin())
{
int Ly = *prev(it);
L = max(L, Ly + 1);
}
if(it != ss[x].end() && next(it) != ss[x].end())
{
int Ry = *next(it);
R = min(R, Ry - mid - 1);
}
if(L <= R) modify(1, 1, a[mxp][Mx[mxp]], L, R, -1);
ss[x].erase(it);
}
for(auto [x, y] : pos[i])
{
int L = max(y - mid, 1), R = y;
auto it = ss[x].lower_bound(y);
if(it != ss[x].begin())
{
int Ly = *prev(it);
L = max(L, Ly + 1);
}
if(it != ss[x].end())
{
int Ry = *it;
R = min(R, Ry - mid - 1);
}
if(L <= R) modify(1, 1, a[mxp][Mx[mxp]], L, R, 1);
ss[x].insert(y);
}
if(tr[1] == n) return 1;
}
}
return 0;
};
int l = -1, r = a[mxp][Mx[mxp]];
while(l + 1 < r)
{
int mid = (l + r) >> 1;
if(chk(mid)) r = mid;
else l = mid;
}
printf("%d\n", r);
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);
#endif
int T = rd(); k = rd();
while(T--) solve();
return 0;
}