比赛情况
比赛共 254 人提交代码,最高分 250 分,有 102 人 100 分以上。
第一题共 85 人满分,第二题最高分 90,第三题最高分 100。
A 数学奇才琪露诺
题意
设
s(x)
表示
x
的数位和,给定
题解
不难发现,对于方程的解
x
,
代码
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long LL;
int k, p, q, l, r, ans[100];
int sum(int x)
{
int ret = 0;
while(x)
{
ret += x % 10;
x /= 10;
}
return ret;
}
int main()
{
scanf("%d%d%d%d%d", &k, &p, &q, &l, &r);
ans[0] = 0;
for(int s = 0; s <= 81; ++s)
{
LL x = p;
for(int i = 0; i < k; ++i)
x *= s;
x += q;
if(l <= x && x <= r && sum((int)x) == s)
ans[++ans[0]] = (int)x;
}
sort(ans + 1, ans + ans[0] + 1);
printf("%d\n", ans[0]);
for(int i = 1; i <= ans[0]; ++i)
printf("%d%c", ans[i], " \n"[i == ans[0]]);
return 0;
}
B 完美拓印
题意
给定一个长度为
n
的序列印章的图案不是中心对称的
,序列
n,m≤106
,序列元素不超过
109
。
题解
印章与边界线重合有四种情况,印章的上或下边缘在边界线的上或下方重合,现在来分别分析这四种情况的方案数。
1. 印章的下边缘在边界线的上方或下方重合,即
B
序列里有长度为
2. 印章的上边缘在边界线的下方重合,即序列
A
的元素加上同一个值之后与
3. 印章的上边缘在边界线的上方重合,即序列
A
的元素加上同一个值之后与
由于印章的图案不是中心对称的,所以当印章的上边缘完全与下边缘平行时,可能有同样位置的方案,但是印章的图案不重合,直接分别考虑即可。
考虑情况1,由于下边缘是完全水平的,所以只需要看
B
序列里有多少个长度为
考虑情况2和3,不难发现如果序列
A
的相邻元素之差等于序列
注意情况2和3需要特判模板串长度为
0
的情况,即
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 1000010;
int n, m, s[maxn << 1], t[maxn], f[maxn], ans;
int main()
{
scanf("%d%d", &m, &n);
if(m == 1)
{
printf("%d\n", n << 2);
return 0;
}
for(int i = 0; i < m; ++i)
scanf("%d", t + i);
for(int i = 0; i < n; ++i)
scanf("%d", s + i);
for(int i = 0, j = 0; i < n; i = j)
{
while(j < n && s[i] == s[j])
++j;
if(j - i >= m)
ans += j - i - m + 1 << 1;
}
for(int i = --m; i > 0; --i)
t[i] -= t[i - 1];
t[0] = 0;
for(int i = --n; i > 0; --i)
s[i] -= s[i - 1];
s[0] = 0;
s[n + 1] = ~0u >> 1;
for(int i = 1; i <= n; ++i)
s[n * 2 + 2 - i] = s[i];
n = n * 2 + 1;
for(int i = 2, j = 0; i <= m; ++i)
{
while(j > 0 && t[i] != t[j + 1])
j = f[j];
if(t[i] == t[j + 1])
++j;
f[i] = j;
}
for(int i = 1, j = 0; i <= n; ++i)
{
while(j > 0 && s[i] != t[j + 1])
j = f[j];
if(s[i] == t[j + 1])
++j;
if(j == m)
{
++ans;
j = f[j];
}
}
printf("%d\n", ans);
return 0;
}
C 幻影阁的难题
题意
定义一颗无向树的直径是树上最远两点的距离。给定一颗
n
个点的边有长度的无向树
题解
对于每个棵树上,可以在
O(节点数)
的时间复杂度里,通过两遍dfs完成树形dp,计算出每个节点到其最远点的距离,而一棵树的直径即这些距离的最大值。
考虑一棵树,定义
f1[i]
表示以
i
为根的子树里到i的最远点距离,
设其一个孩子为
考虑再用一遍dfs得到
f3
,对于树根节点
root
,
f3[root]=f1[root]
,对于非根节点
u
,向孩子走的方案已经包含在
设其父亲为
fa
,距离为
w
,若父亲的最远距离来自
在实际的实现上,
f3
可以直接用
f1
代替,具体可以参见代码。
对于加边方案,设其连接的是树
R
的节点
令
w=max(diaR,diaF)
,则对于给定的
i
,任意满足
由此我们可以通过枚举
i
,变化
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = (int)3e5 + 100;
typedef long long LL;
int n, m, t, tot, p[maxn], f[maxn], g[maxn], ff[maxn], pos[maxn], maxl, ans1;
LL sg[maxn], ans21, ans22;
struct Edge
{
int nxt, v, w;
} e[maxn << 1];
void scan(int &x)
{
static int ch;
while((ch = getchar()) < '0' || ch > '9');
x = ch - '0';
while((ch = getchar()) >= '0' && ch <= '9')
x = (x << 3) + (x << 1) + (ch - '0');
}
LL gcd(LL a, LL b)
{
return b ? gcd(b, a % b) : a;
}
void dfs1(int u, int fa)
{
for(int it = p[u]; it != -1; it = e[it].nxt)
{
int &v = e[it].v, &w = e[it].w;
if(v == fa)
continue;
dfs1(v, u);
if(f[u] < f[v] + w)
{
pos[u] = v;
ff[u] = f[u];
f[u] = f[v] + w;
}
else if(ff[u] < f[v] + w)
ff[u] = f[v] + w;
}
}
void dfs2(int u, int fa)
{
for(int it = p[u]; it != -1; it = e[it].nxt)
{
int &v = e[it].v, &w = e[it].w;
if(v == fa)
continue;
int &pp = pos[u] == v ? ff[u] : f[u];
if(f[v] < pp + w)
{
pos[v] = u;
ff[v] = f[v];
f[v] = pp + w;
}
else if(ff[v] < pp + w)
ff[v] = pp + w;
dfs2(v, u);
}
}
int dp()
{
tot = 0;
memset(p, -1, sizeof p);
for(int i = 1; i < n; ++i)
{
int u, v, w;
scan(u);
scan(v);
scan(w);
e[tot] = (Edge){p[u], v, w};
p[u] = tot++;
e[tot] = (Edge){p[v], u, w};
p[v] = tot++;
}
memset(f, 0, sizeof f);
memset(ff, 0, sizeof ff);
memset(pos, 0, sizeof pos);
dfs1(1, -1);
dfs2(1, -1);
sort(f + 1, f + n + 1);
return f[n];
}
int main()
{
scan(n);
scan(m);
scan(t);
ans1 = ~0u >> 1;
ans21 = 0;
ans22 = (LL)n * m;
maxl = dp();
swap(n, m);
memcpy(g, f, sizeof f);
maxl = max(maxl, dp());
memset(sg, 0, sizeof sg);
for(int i = m; i; --i)
sg[i] = sg[i + 1] + g[i];
for(int i = 1, j = m; i <= n; ++i)
{
while(j && f[i] + g[j] + t >= maxl)
--j;
ans21 += (LL)maxl * j;
if(j > 0 && ans1 > maxl)
ans1 = maxl;
ans21 += (LL)(f[i] + t) * (m - j) + sg[j + 1];
if(j < m && ans1 > f[i] + g[j + 1] + t)
ans1 = f[i] + g[j + 1] + t;
}
LL r = gcd(ans21, ans22);
printf("%d\n%lld/%lld\n", ans1, ans21 / r, ans22 / r);
return 0;
}
一些无脑部分分的设定
- B 30pts: n∗m≤2∗107 。直接暴力枚举。
- B 60pts: n,m≤105 。让Hash+二分、SA过。(会这些还不会kmp?)
- C 20pts: n,m≤50 。枚举点,计算两边的直径,及其他乱搞大法。
- C 40pts: n,m≤500 。枚举点,计算新树直径。
- C 60pts: n,m≤5000 。暴力预处理两边的最远点,暴力枚举加边方案。
为了NOIP提高组难度而设定的部分分真是不忍直视。