打多校8场以来,第一场差点给零封的场。。全场1000+支队给零封了600支。。可怕
Battlestation Operational
一道好莫比乌斯题。
题意:
要求1到n内,满足$gcd(i, j) == 1且 i > j 的\sum\left \lceil \frac {i}{j}\right \rceil $
其实要满足gcd(i, j) == 1只需要拿莫比乌斯反演一下就好了。
因为莫比乌斯满足
f ( n ) = ∑ d ∣ n u ( d ) ∗ F ( n d ) f(n) = \sum_{d | n} u(d) * F(\frac{n}{d}) f(n)=∑d∣nu(d)∗F(dn)
所以我们只需要把F(n)求出来就好了。
$F(n) = \sum_{i = 1}^{n}\sum_{j = 1}^{i}\left \lceil \frac {i}{j}\right \rceil $
但这样求F(n)会成 n 2 n^2 n2
所以,对于求1到n的F(n),有递推公式
我们设f(n)为1到n的 i j \frac{i}{j} ji向下取整的和
我们设F(n)为1到n的 i j \frac{i}{j} ji向上取整的和
我们可以得到:
F(i) = f(i - 1) + i
f(i) = F(i) - i + cnt(i的因子数)
这样的递推公式
就可以快速求得F(n)了。打表即可
#include <bits/stdc++.h>
#define MAXN 1000005
#define MOD 1000000007
using namespace std;
int mu[MAXN];
int prime[MAXN];
int tot;
int f[MAXN], F[MAXN];
bool flag[MAXN];
void moblus() {
mu[1] = 1;
tot = 0;
for (int i = 2; i < MAXN; i++) {
if (!flag[i]) {
prime[tot++] = i;
mu[i] = -1;
}
for (int j = 0; j < tot; j++) {
if (i * prime[j] >= MAXN) {
break;
}
flag[i * prime[j]] = true;
if (i % prime[j] == 0) {
mu[i * prime[j]] = 0;
break;
} else {
mu[i * prime[j]] = -mu[i];
}
}
}
}
void init() {
moblus();
f[1] = F[1] = 1;
for (int i = 2; i < MAXN; i++) {
F[i] = f[i - 1] + i;
int x = i, ans = 1;
for (int j = 0; prime[j] * prime[j] <= i; j++) {
int cnt = 1;
while (x % prime[j] == 0) {
cnt++;
x /= prime[j];
}
ans *= cnt;
}
if (x > 1) {
ans *= 2;
}
f[i] = F[i] - i + ans;
}
for (int i = 2; i < MAXN; i++) {
f[i] = F[i];
}
for (int i = 2; i < MAXN; i++) {
for (int j = i; j < MAXN; j += i) {
F[j] += mu[i] * f[j / i];
}
}
for (int i = 2; i < MAXN; i++) {
F[i] += F[i - 1];
F[i] %= MOD;
}
}
int main() {
init();
int n;
while (~scanf("%d", &n)) {
printf("%d\n", F[n]);
}
}
##Death Podracing##
比赛的时候没读过的相对水题。
题意:
有一个操场之类的东西长度为L,有n个人在上面跑,告诉他们的起始位置和速度给你,如果两个人相撞,能量小的那个人会被淘汰,输入顺序决定能力值的大小。
现在问最后剩一个人的时间是多少,时间要用分数表示。
思路:
其实很容易能想到,每个人会发生事故只会跟他在位置的前一个或者后一个相撞。
那我们可以用双指针维护他的前一个人的下标和后一个人的下表。
用优先队列维护发生事故的时间,两个人如果发生事故,那就比较一下哪个会被淘汰。模拟即可。
#include <bits/stdc++.h>
#define ll long long
#define MAXN 200005
using namespace std;
struct pop {
ll d, v, idx;
bool operator < (const pop &a) const {
return d < a.d;
}
} group[MAXN];
struct node {
ll pre, nxt;
double time;
bool operator < (const node &a) const {
return time > a.time;
}
node() {}
node(ll p, ll n, double t) : pre(p), nxt(n), time(t) {}
};
priority_queue<node> q;
ll len;
ll L[MAXN], R[MAXN];
bool vis[MAXN];
ll gcd(ll a, ll b) {
return b == 0 ? a : gcd(b, a % b);
}
double getTime(ll x, ll y) {
ll dx = (group[y].d - group[x].d + len) % len;
ll dv = group[x].v - group[y].v;
if (dv < 0) {
dv = -dv;
dx = len - dx;
}
return dx * 1.0 / dv;
}
void output(ll x, ll y) {
ll dx = (group[y].d - group[x].d + len) % len;
ll dv = group[x].v - group[y].v;
if (dv < 0) {
dv = -dv;
dx = len - dx;
}
ll p = gcd(dx, dv);
dx /= p;
dv /= p;
printf("%lld/%lld\n", dx, dv);
}
int main() {
int T, n;
scanf("%d", &T);
while (T--) {
while (!q.empty()) {
q.pop();
}
scanf("%d %lld", &n, &len);
for (int i = 0; i < n; i++) {
scanf("%lld", &group[i].d);
}
for (int i = 0; i < n; i++) {
scanf("%lld", &group[i].v);
group[i].idx = i;
}
sort(group, group + n);
for (int i = 0; i < n; i++) {
double t = getTime(i, (i + 1) % n);
q.push(node(i, (i + 1) % n, t));
L[i] = (i - 1 + n) % n;
R[i] = (i + 1) % n;
}
memset(vis, false, sizeof(vis));
node tmp;
while (q.size() > 1) {
tmp = q.top();
q.pop();
ll l = tmp.pre, r = tmp.nxt;
if (vis[l] || vis[r]) {
continue;
}
if (L[l] == r && R[r] == l) {
break;
}
if (group[l].idx > group[r].idx) {
vis[r] = true;
R[l] = R[r];
L[R[r]] = l;
double t = getTime(l, R[r]);
q.push(node(l, R[r], t));
} else {
vis[l] = true;
L[r] = L[l];
R[L[l]] = r;
double t = getTime(L[l], r);
q.push(node(L[l], r, t));
}
if (--n <= 0) {
break;
}
}
tmp = q.top();
output(tmp.pre, tmp.nxt);
}
}
##Hybrid Crystals##
比赛的时候没读的最水题。最后一个钟把一道给带错榜的组合数学题过了之后。。没心情读这题题目了。。这题毫无疑问,是一道阅读题。。。。
题意:
有三种数,一种是可以为正或者负的,一种只能为正,一种只能为负,现在问用这些数能不能表达出数k。
其实题目一大片都是一堆废话。。最有用的一句就是 a 1 = 1 , b 1 = N a_1 = 1, b_1 = N a1=1,b1=N
这句话的意思就是,我保证给你一个[-1, 1]的区间。。。
那每次进来数只需要维护这个区间的左右两边即可。因为一个区间加上一个数,区间一定是扩增这个数的长度的。最后只要判断k是否在这个区间内就好了orz全场最水题,结果给带歪榜
(L和D写反了也过了,这数据要有多水)
#include <bits/stdc++.h>
#define MAXN 1005
#define ll long long
using namespace std;
struct node {
ll val;
char c;
} num[MAXN];
int main() {
int n, m;
int T;
ll k;
scanf("%d", &T);
while (T--) {
num[0].c = 'X';
scanf("%d %lld", &n, &k);
for (int i = 1; i <= n; i++) {
scanf("%lld", &num[i].val);
}
getchar();
for (int i = 1; i <= n; i++) {
scanf("%c", &num[i].c);
getchar();
}
ll l = -1, r = 1;
for (int i = 1; i <= n; i++) {
if (num[i].c == 'N') {
l -= num[i].val;
r += num[i].val;
} else if (num[i].c == 'L') {
l -= num[i].val;
} else if (num[i].c == 'D') {
r += num[i].val;
}
}
if (k <= r && k >= l) {
puts("yes");
} else {
puts("no");
}
}
}
##Killer Names##
本场导致差点给0封的题,开场10分钟,我对队友说,怎么这题还没有dalao过,简单组合数题啊。orz结果最后推到意识模糊4个钟后才过。。。
题意:
给first name 和last name,他们都由n长度的m个字符随机组成。
如果first name和last name之间没有出现过相同的字符,则称他们为Killer Names
现在问能组合出多少种这样的名字
思路:
死怼容斥结果硬生生怼不掉。
其实可以转换成高中的组合数学题。
n种颜色在m个位置上涂色,这n种颜色必须都用上的情况有多少种。
有递推公式
dp[i][j] = (j * (dp[i - 1][j] + dp[i - 1][j - 1]))
然后处理一下i,j的其他情况
i == j时有dp[i][j] = fac[i]
然后暴力枚举每一种情况相乘即可。。
#include <bits/stdc++.h>
#define ll long long
#define MAXN 2050
using namespace std;
const ll MOD = 1e9 + 7;
ll dp[MAXN][MAXN];
ll fac[MAXN];
ll c[MAXN][MAXN];
void init() {
ll i, j;
dp[0][0] = dp[1][0] = dp[0][1] = c[0][0] = fac[0] = 1;
for (i = 1; i < MAXN; i++) {
fac[i] = (fac[i - 1] * i) % MOD;
c[i][0] = c[i][i] = 1;
dp[i][i] = fac[i];
for (j = 1; j < i; j++) {
if (j > i) {
dp[i][j] = 0;
} else if (j == 1) {
dp[i][j] = 1;
} else {
dp[i][j] = (j * (dp[i - 1][j] + dp[i - 1][j - 1]) % MOD) % MOD;
}
c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % MOD;
}
}
}
int main() {
init();
int T;
ll n, m, ans;
while (~scanf("%d", &T)) {
while (T--) {
scanf("%lld %lld", &n, &m);
ans = 0;
if (m == 1) {
puts("0");
continue;
}
for (ll i = 1; i < m; i++) {
for (ll j = 1; j <= m - i; j++) {
ans += (((c[m][i] * c[m - i][j]) % MOD) * ((dp[n][i] * dp[n][j]) % MOD)) % MOD;
ans %= MOD;
}
}
printf("%lld\n", ans);
}
}
}