FJWC2020 Day2 题解
T1
Description
给定长度为 \(n\) 的数组 \(h\),现在你有一个全 \(0\) 数组 \(a\),每次你可以执行下面两个操作之一:
- \(a_i++,a_{i+1}+=2,i∈[1,n-1]\)
- \(a_i+=2,a_{i+1}++,i∈[1,n-1]\)
求最少的操作次数使得 \(\forall 1\leq i\leq n,a_i\geq h_i\)。
\(n\leq 10^6\)。
\(3\) 题时空限制均为 \(1s/512MB\)。
Solution
先考虑一个显然错误的贪心。遍历一遍,当 \(a_i<h_i\) 时,就令 \(a_i+=2,a_{i+1}++\)。否则 \(i++\)。这样只能保证前面的 \(i\) 的操作次数最少,不能保证总操作次数最少。
因此考虑如下的反悔操作:
- 若之前执行了 \(a_i+=2,a_{i+1}++\)。考虑撤销这个操作,改成 \(a_i++,a_{i+1}+=2\),然后再加一步 \(a_i++,a_{i+1}+=2\)。这样使得 \(a_i\) 不变,并且花 \(1\) 的代价让 \(a_{i+1}+=3\)。
- 若之前执行了 \(a_i+=3\)。考虑撤销这个操作,改成 \(a_i++,a_{i+1}+=2\),然后再加一步 \(a_i+=2,a_{i+1}++\)。这样使得 \(a_i\) 不变,并且花 \(1\) 的代价让 \(a_{i+1}+=3\)。
- 若之前执行了 \(a_i+=3\)。考虑撤销这个操作,但是加上 \(3\) 次的 \(a_i++,a_{i+1}+=2\)。这样使得 \(a_i\) 不变,并且花 \(2\) 的代价让 \(a_{i+1}+=6\)。
这 \(3\) 种反悔操作是足够的。因为反悔必须保证 \(a_i\) 不变。因此操作 \(a_i++,a_{i+1}+=2\) 无法撤销。撤销 \(a_i+=2,a_{i+1}++\) 之后只能加上两个含 \(a_i++\) 的操作才能保证 \(a_i\) 不变。撤销 \(a_i+=3\) 之后只能加上一个含 \(a_i++\),一个含 \(a_i+=2\) 的操作,或者加上 \(3\) 个含 \(a_i++\) 的操作,才能保证 \(a_i\) 不变。
我们的贪心还是保证靠前的 \(a_i\) 的操作次数尽量小。因此记一下之前用了几个 \(a_{i-1}+=3\),用了几个 \(a_{i-1}+=2,a_i++\),即可知道能用多少次 \(a_i+=3\)。如果次数都用完了还是 \(a_i<h_i\),设此时 \(h_i-a_i=k\),那么我们用 \(\lfloor\frac{k}{2}\rfloor\) 次 \(a_i+=2,a_{i+1}++\),用 \(k\%2\) 次 \(a_i++,a_{i+1}+=2\) 即可。
时间复杂度 \(O(n)\)。
Code
#include <bits/stdc++.h>
using namespace std;
#define ll long long
template <class t>
inline void read(t & res)
{
char ch;
while (ch = getchar(), !isdigit(ch));
res = ch ^ 48;
while (ch = getchar(), isdigit(ch))
res = res * 10 + (ch ^ 48);
}
const int e = 1e6 + 5;
int t1, t2, t3, h[e], a[e], n;
ll ans;
int main()
{
read(n);
int i;
for (i = 1; i <= n; i++) read(h[i]);
for (i = 1; i <= n; i++)
{
h[i] = max(0, h[i]);
t1 = min(h[i] / 3, a[i]);
h[i] -= t1 * 3;
t2 = h[i] / 2;
h[i] -= t2 * 2;
t3 = h[i];
h[i] -= t3;
ans += t1 + t2 + t3;
a[i + 1] += t1 * 2 + t2;
h[i + 1] -= t2 + t3 * 2;
}
cout << ans << endl;
fclose(stdin);
fclose(stdout);
return 0;
}
T2
Description
给你一个含 \(n\) 个珠子的环。你要对这个环染色。每个珠子可以染上 \(k\) 种颜色之一或者不染,不能有相邻两个珠子同时染色。
如果一种方案旋转之后可以变成另一种,那么这两种方案本质相同。求本质不同的染色方案数 \(\%10^9+7\)。
\(n,k\leq 10^9\)。
Solution
菜鸡流下了不会 \(\text{Burnside}\) 的泪水。
\(\text{Burnside}\) 引理:$$ans=\frac{1}{|G|}\sum_{g∈G}f(g)$$ 即本质不同的方案数为各个置换下不动方案数的平均数。
本题的置换集合为:旋转 \(i\) 个位置,\(\forall 1\leq i\leq n\)。
现在我们要求旋转 \(i\) 个位置下的不动方案数。令 \(t=gcd(n,i)\),显然合法的方案中,可以把 \(n\) 个珠子分成 \(t\) 组,每组 \(\frac{n}{t}\) 个。同组珠子必须同色,相邻组的珠子不能同时染色,包括第 \(1\) 组和第 \(t\) 组。
问题转化为对一个长度为 \(t\) 的环进行染色,每个点可以染 \(k\) 种颜色种的一种,或者无色,相邻的点至少一个无色,求方案数。
考虑 \(\text{dp}\),令 \(f[i][j][k]\) 表示前 \(i\) 个点,\(1\) 号点是否染色,\(i\) 号点是否染色的方案数。可以用矩阵乘法优化到 \(O(\log t)\)。
发现 \(gcd(n,i)\) 相同的 \(i\) 的答案是一样的。那么枚举 \(n\) 的约数 \(t\),易得:$$ans=\frac{1}{n}\sum_{t|n}calc(t)×\phi(\frac{n}{t})$$
其中 \(\phi(\frac{n}{t})\) 表示 \(gcd(n,i)=t\) 的 \(i\) 的个数。
时间复杂度 \(O(\sqrt n\log n)\)。
Code
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e6, mod = 1e9 + 7, e = 1e6 + 5;
int n, m, ans, pri[e], cnt;
bool bo[e];
struct matrix
{
int r, c, a[2][2];
matrix(){}
matrix(int _r, int _c) :
r(_r), c(_c) {}
};
inline void add(int &x, int y)
{
(x += y) >= mod && (x -= mod);
}
inline int mul(int x, int y)
{
return (ll)x * y % mod;
}
inline int ksm(int x, int y)
{
int res = 1;
while (y)
{
if (y & 1) res = (ll)res * x % mod;
y >>= 1;
x = (ll)x * x % mod;
}
return res;
}
inline matrix operator * (matrix a, matrix b)
{
matrix res = matrix(a.r, b.c);
memset(res.a, 0, sizeof(res.a));
int i, j, k;
for (i = 0; i < a.r; i++)
for (k = 0; k < a.c; k++)
for (j = 0; j < b.c; j++)
res.a[i][j] = (res.a[i][j] + (ll)a.a[i][k] * b.a[k][j]) % mod;
return res;
}
inline matrix operator ^ (matrix a, int y)
{
matrix res = matrix(a.r, a.c);
memset(res.a, 0, sizeof(res.a));
res.a[0][0] = res.a[1][1] = 1;
while (y)
{
if (y & 1) res = res * a;
y >>= 1;
a = a * a;
}
return res;
}
inline int calc(int t)
{
matrix c = matrix(1, 2), b = matrix(2, 2), a;
int res = 0;
c.a[0][0] = 1; c.a[0][1] = 0;
b.a[0][0] = b.a[1][0] = 1; b.a[0][1] = m; b.a[1][1] = 0;
a = c * (b ^ (t - 1));
add(res, a.a[0][0]); add(res, a.a[0][1]);
c.a[0][0] = 0; c.a[0][1] = m;
b.a[0][0] = b.a[1][0] = 1; b.a[0][1] = m; b.a[1][1] = 0;
a = c * (b ^ (t - 1));
add(res, a.a[0][0]);
return res;
}
inline void init()
{
int i, j;
for (i = 2; i <= N; i++)
{
if (!bo[i]) pri[++cnt] = i;
for (j = 1; j <= cnt && i * pri[j] <= N; j++)
{
bo[i * pri[j]] = 1;
if (i % pri[j] == 0) break;
}
}
}
inline int phi(int x)
{
int res = x;
for (int i = 1; i <= cnt && x >= pri[i]; i++)
if (x % pri[i] == 0)
{
res = (ll)res * (pri[i] - 1) / pri[i];
while (x % pri[i] == 0) x /= pri[i];
}
if (x != 1) res = (ll)res * (x - 1) / x;
return res;
}
int main()
{
init();
cin >> n >> m;
int s = sqrt(n), i;
for (i = 1; i <= s; i++)
if (n % i == 0)
{
add(ans, mul(phi(i), calc(n / i)));
if (n / i != i) add(ans, mul(phi(n / i), calc(i)));
}
ans = mul(ans, ksm(n, mod - 2));
cout << ans << endl;
fclose(stdin);
fclose(stdout);
return 0;
}
T3
Description
给定两个数 \(n,k\),接下来有 \(n\) 次操作,每次可能是加入一个数 \(x\),或删除一个数 \(x\)。每次操作之后要回答有几对数的 \(gcd=k\)。
\(1\leq n,k,x\leq 10^5\)。
Solution
把每个数 \(÷\ k\),忽略 \(\%\ k≠0\) 的数。问题转化为求 \(gcd=1\) 的数的对数。
记 \(cnt_i\) 表示 \(i\) 的倍数有几个,则答案为 $$\sum_{i=1}{105}\mu_i×C_{cnt_i}^2$$ 所以加入一个数 \(x\) 的时候,只要枚举 \(x\) 的约数 \(y\),把 \(cnt_y++\),同时计算新的答案即可。删除同理。
时间复杂度 \(O(n\sqrt x)\)。\(10^5\) 以内的数的约数最多 \(128\) 个。
Code
#include <bits/stdc++.h>
using namespace std;
#define ll long long
template <class t>
inline void read(t & res)
{
char ch;
while (ch = getchar(), !isdigit(ch));
res = ch ^ 48;
while (ch = getchar(), isdigit(ch))
res = res * 10 + (ch ^ 48);
}
template <class t>
inline void print(t x)
{
if (x > 9) print(x / 10);
putchar(x % 10 + 48);
}
const int e = 2e5 + 5;
vector<int>g[e];
int n, k, cnt[e], a[e], m = 100000, miu[e];
ll ans;
bool bo[e];
inline ll c2(ll x)
{
return x * (x - 1) / 2;
}
inline void add(int x)
{
if (x % k) return;
a[x]++;
x /= k;
int len = g[x].size(), i;
for (i = 0; i < len; i++)
{
int y = g[x][i];
ans -= miu[y] * c2(cnt[y]);
cnt[y]++;
ans += miu[y] * c2(cnt[y]);
}
}
inline void del(int x)
{
if (x % k) return;
if (!a[x]) return;
a[x]--;
x /= k;
int len = g[x].size(), i;
for (i = 0; i < len; i++)
{
int y = g[x][i];
ans -= miu[y] * c2(cnt[y]);
cnt[y]--;
ans += miu[y] * c2(cnt[y]);
}
}
int main()
{
int op, x, i, j;
for (i = 1; i <= m; i++)
for (j = i; j <= m; j += i)
g[j].push_back(i);
for (i = 1; i <= m; i++) miu[i] = 1;
for (i = 2; i <= m; i++)
if (!bo[i])
{
miu[i] = -1;
for (j = 2 * i; j <= m; j += i)
{
bo[j] = 1;
if (j / i % i == 0) miu[j] = 0;
else miu[j] *= -1;
}
}
read(n); read(k);
while (n--)
{
read(op); read(x);
if (op == 0) del(x);
else add(x);
print(ans); putchar('\n');
}
fclose(stdin);
fclose(stdout);
return 0;
}