12 月 4 日 小 视 野 模 拟 赛 \rm 12 ~ 月 ~ 4 ~ 日小视野模拟赛 12 月 4 日小视野模拟赛
A. Clock Sequence
科学家温斯顿定义了一个无限重复的数列:1234321234321234321……
,并将其称为时钟序列。
他发现可以将数列人为分成几段:
1, 2, 3, 4, 32, 123, 43, 2123, 432, 1234, 32123, ...
他又定义了新数列中第 n n n 项为 V n V_n Vn,这样分组能够满足 V n V_n Vn 的数字和等于 n n n。
例如, V 2 = 2 , V 7 = 43 , V 11 = 32123 V_2=2,V_7=43,V_{11}=32123 V2=2,V7=43,V11=32123。
请帮助他求出数列 V V V 的前 n n n 项和。
sol
找规律即可。
#include <bits/stdc++.h>
#define int long long
const int MOD = 123454321, k = 96007682;
using namespace std;
int n, ans, a, b;
int sum1[16] = {0, 1, 3, 6, 10, 42, 165, 208, 2331, 2763, 3997, 36120, 79332, 113653, 137085, 260517};
int sum2[16] = {0, 234321, 577533, 1009656, 1330890, 1454322, 1886445, 2098788, 2530911, 2654343, 2975577, 3407700, 3750912, 3985233, 4108665, 4232097};
int qpow(int x, int y)
{
int ans = 1ll;
while (y)
{
if (y & 1)
ans = ans * x % MOD;
x = x * x % MOD;
y >>= 1;
}
return ans;
}
signed main()
{
scanf("%lld", &n);
a = n / 15, b = n % 15;
int a1 = (qpow(1000000, a) - 1) * k % MOD;
int a2 = (a1 - a + MOD) % MOD * k % MOD;
int a3 = (qpow(1000000, a + 1) - 1) * k % MOD;
a3 = (a3 - a1 + MOD) % MOD;
ans = (a1 * sum1[15] % MOD + a2 * sum2[15] % MOD + a3 * sum1[b] % MOD + a1 * sum2[b] % MOD) % MOD;
printf("%lld\n", ans);
return 0;
}
B. 灯
突然,牛棚的电源跳闸了,所有的灯都被关闭了。你需要将所有的灯都给重新开起来!
牛棚中一共有 N ( 1 < = N < = 35 ) N(1 <= N <= 35) N(1<=N<=35) 盏灯,编号为 1 1 1 到 N N N。这些灯被置于一个非常复杂的网络之中。有 M M M条很神奇的无向边,每条边连接两盏灯。
每盏灯上面都带有一个开关。当按下某一盏灯的开关的时候,这盏灯本身,还有所有有边连向这盏灯的灯的状态都会被改变。状态改变指的是:当一盏灯是开着的时候,这盏灯被关掉;当一盏灯是关着的时候,这盏灯被打开。
问最少要按下多少个开关,才能把所有的灯都给重新打开。
sol
由于 N N N 非常小,可以考虑状压加暴力搜索。
但复杂度是 O ( 2 25 ) \mathcal O(2^{25}) O(225) 的必然超时。
考虑优化,折半搜索,分成 2 17 2^{17} 217 和 2 18 2^{18} 218 两个部分搜索。
先搜前 17 17 17 盏灯,将状态保存起来。
再搜后 18 18 18 盏灯,看当前状态是否可以和之前状态的异或值是否为 2 n − 1 2^n-1 2n−1 , a n s ans ans 取最小即可。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, m;
int ans = INT_MAX;
int mid, cnt;
int vis[1007], pos[1007];
vector<int> d[1007];
struct abc
{
int num, pos;
};
struct cmp
{
bool operator()(const abc &a, const abc &b) const
{
return a.pos < b.pos;
}
};
vector<abc> vec;
map<int, int> mp;
void dfs(int x, int num, int p)
{
if (!mp[p])
vec.push_back({num, p}), mp[p] = vec.size();
else
vec[mp[num]].num = min(vec[mp[num]].num, num);
if (x == mid + 1ll)
return;
dfs(x + 1ll, num, p);
dfs(x + 1ll, num + 1ll, p ^ pos[x]);
}
void dfss(int x, int num, int p)
{
abc tmp = {num, p ^ ((1ll << n) - 1ll)};
auto it = lower_bound(vec.begin(), vec.end(), tmp, cmp());
if (it != vec.end())
if (((*it).pos ^ p) == ((1ll << n) - 1ll))
ans = min(ans, (*it).num + num);
if (x == n + 1ll)
return;
dfss(x + 1ll, num, p);
dfss(x + 1ll, num + 1ll, p ^ pos[x]);
}
signed main()
{
scanf("%lld%lld", &n, &m);
mid = (n + 1ll) >> 1ll;
for (int i = 1ll; i <= m; i++)
{
int u, v;
scanf("%lld%lld", &u, &v);
d[u].push_back(v);
d[v].push_back(u);
}
for (int i = 1ll; i <= n; i++)
{
for (int j : d[i])
vis[j] = 1ll;
vis[i] = 1ll;
for (int j = 1ll, k = 1ll; j < (1ll << n); j <<= 1ll, ++k)
{
if (vis[k])
pos[i] += j, vis[k] = 0;
}
}
dfs(1ll, 0, 0);
sort(vec.begin(), vec.end(), cmp());
dfss(mid + 1ll, 0, 0);
printf("%lld\n", ans);
return 0;
}
C. 硬币游戏
初始时,一个有 N N N 枚硬币的堆栈放在地上,从堆顶数起的第 i i i 枚硬币的币值为 C i C_i Ci。
开始玩游戏时,第一个玩家可以从堆顶拿走一枚或两枚硬币。
在每一轮中,当前的玩家至少拿走一枚硬币,至多拿走对手上一次所拿硬币数量的两倍。
当没有硬币可拿时,游戏结束。
两个玩家都希望拿到最多钱数的硬币。请问,当游戏结束时,第一个玩家最多能拿多少钱呢?
sol
这道题目我们可以选择倒着推:因为最后一个因此必然是要取完的,因此可以确定状态一定是可行的。
我们给每一个金币自底向上标号为 1 ∼ i 1 \sim i 1∼i。
状态:设 f i , j f_{i,j} fi,j 表示某一方取完以后还剩下编号为 1 ∼ i 1 \sim i 1∼i 的金币,上一个人取了 j j j 枚金币的最大取值。
转移:对于每一个状态我们必然需要知道一个当前所取的金币数 k k k,必然和 f i − k , k f_{i-k,k} fi−k,k 有关,表示 1 ∼ i 1 \sim i 1∼i 堆中取了 k k k,还剩下 1 ∼ i − k 1 \sim i-k 1∼i−k 枚金币;那么这个人当前取的金币数就是总金币减去下一个决策的金币。
可得状态转移方程:
f
i
,
j
=
max
(
f
i
,
j
,
s
u
m
i
−
f
i
−
k
,
k
)
f_{i,j} = \max(f_{i,j}, sum_i - f_{i - k,k})
fi,j=max(fi,j,sumi−fi−k,k)
优化:观察每一个
f
i
,
j
f_{i,j}
fi,j,对于每一个
f
i
,
j
−
1
f_{i,j-1}
fi,j−1,多余的决策只有选
2
j
−
1
2j-1
2j−1 枚金币和
2
j
2j
2j 枚,只要
k
k
k 等于这两个值做两遍转移,和原来求最大值取一个最大值即可。
#include <bits/stdc++.h>
using namespace std;
const int _ = 2005;
int n, a[_], s[_], f[_][_];
signed main()
{
scanf("%d", &n);
for (int i = n; i >= 1; --i)
scanf("%d", &a[i]);
for (int i = 1; i <= n; ++i)
s[i] = s[i - 1] + a[i];
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
f[i][j] = f[i][j - 1];
int k = 2 * j - 1;
if (k <= i)
f[i][j] = max(f[i][j], s[i] - f[i - k][k]);
k++;
if (k <= i)
f[i][j] = max(f[i][j], s[i] - f[i - k][k]);
}
}
printf("%d\n", f[n][1]);
return 0;
}
D. Gcd
给定整数 N N N,求 1 ≤ x , y ≤ N 1 \leq x,y \leq N 1≤x,y≤N 且 gcd ( x , y ) \gcd(x,y) gcd(x,y) 为素数的数对 ( x , y ) (x,y) (x,y) 有多少对。
sol
算法一
我会欧拉函数!!!
设 gcd ( x , y ) = k \gcd(x,y)=k gcd(x,y)=k,那么 g c d ( x k , y k ) = 1 gcd(\frac{x}{k},\frac{y}{k})=1 gcd(kx,ky)=1。
根据 φ \varphi φ 的定义, q ≥ p q \ge p q≥p,满足 g c d ( q , p ) = 1 gcd(q,p)=1 gcd(q,p)=1 的 p p p 的个数即为 φ ( q ) \varphi(q) φ(q)。
那么满足 gcd ( x , y ) = k \gcd(x,y)=k gcd(x,y)=k 的二元组个数为 2 ∗ ∑ i = 1 N / k φ ( i ) − 1 2* \sum\limits_{i=1}^{N/k}\varphi(i)-1 2∗i=1∑N/kφ(i)−1(其中 gcd ( k , k ) = k \gcd(k,k)=k gcd(k,k)=k 这组多算了一次。
用前缀和维护即可。
枚举所有个数计算贡献即可。
#include <bits/stdc++.h>
const int _ = 1e7 + 7;
using namespace std;
int prime[_], phi[_];
long long g[_ / 2];
long long ans;
int n, tot;
signed main()
{
scanf("%d", &n);
phi[1] = 1;
g[1] = 1;
for (int i = 2; i <= n; i++)
{
if (!phi[i])
{
prime[++tot] = i;
phi[i] = i - 1;
}
for (int j = 1; j <= tot && prime[j] * i <= n; j++)
{
if (i % prime[j] == 0)
{
phi[i * prime[j]] = phi[i] * prime[j];
break;
}
phi[i * prime[j]] = phi[i] * (prime[j] - 1);
}
g[i] = g[i - 1] + phi[i];
}
for (int i = 1; i <= tot; i++)
ans += g[n / prime[i]];
printf("%lld\n", ans * 2 - tot);
return 0;
}
算法二
我会莫比乌斯反演!!!
我们令 f ( n ) f(n) f(n) 表示两个数最大公因数为 n n n 的方案数, F ( n ) F(n) F(n) 表示两个数的 gcd \gcd gcd 为 n n n 的倍数的方案数。
显然 f ( x ) = ∑ x ∣ d μ ( d x ) F ( d ) f(x)=\sum\limits_{x|d}\mu(\frac{d}{x})F(d) f(x)=x∣d∑μ(xd)F(d)。
F ( x ) F(x) F(x) 怎么求呢?可以先记录每个数 i i i 出现的次数 c n t i = ⌊ n i ⌋ cnt_i=\left\lfloor \frac{n}{i} \right\rfloor cnti=⌊in⌋,则是 x x x 的倍数的数有 ∑ x ∣ d c n t d \sum\limits_{x|d}cnt_d x∣d∑cntd 个,记为 p p p,那么 F ( x ) = ( p 2 ) F(x)=\begin{pmatrix}p\\2\end{pmatrix} F(x)=(p2),也就是从 p p p 个数中选 2 2 2 个数的方案数。
显然, A n s = ∑ i = 1 n u m [ p r i i ≤ n ] 2 ∗ ∑ p r i i ∣ d μ ( d p r i i ) F ( d ) + 1 Ans=\sum\limits_{i=1}^{num}[pri_i \leq n]{2*\sum\limits_{pri_i|d} \mu(\frac{d}{pri_i})F(d)}+1 Ans=i=1∑num[prii≤n]2∗prii∣d∑μ(priid)F(d)+1(乘 2 2 2 是因为要求无序对,加 1 1 1 是指加上 gcd ( p r i i , p r i i ) = p r i i \gcd(pri_i,pri_i)=pri_i gcd(prii,prii)=prii 这组。
具体实现见代码。
#include <bits/stdc++.h>
#define int long long
const int _ = 1e7;
using namespace std;
bool vis[_ + 7];
int num, prime[_/2], mu[_ + 7];
int n;
void init()
{
mu[1] = 1;
for (int i = 2; i <= _; ++i)
{
if (!vis[i])
prime[++num] = i, mu[i] = -1;
for (int j = 1; j <= num; ++j)
{
if (prime[j] * i > _)
break;
vis[prime[j] * i] = 1;
if (i % prime[j] == 0)
{
mu[prime[j] * i] = 0;
break;
}
mu[prime[j] * i] = -mu[i];
}
}
}
int Ans;
signed main()
{
init();
scanf("%lld", &n);
if (n < 2)
{
puts("0");
return 0;
}
for (int x = 1; x <= num; ++x)
{
int i = prime[x];
if (i > n)
break;
int ans = 0;
for (int j = i; j <= n; j += i)
{
if (n / j >= 2)
ans += mu[j / i] * ((n / j) * (n / j - 1) / 2);
}
Ans += ans * 2 + 1;
}
printf("%lld\n", Ans);
return 0;
}