比赛共 254 人提交代码,最高分 250 分,有 102 人 100 分以上。
第一题共 85 人满分,第二题最高分 90,第三题最高分 100。
设 s(x) 表示 x 的数位和,给定k,p,q,l,r,求出所有满足 x∈[l,r] 且 x=s(x)k∗p+q 的 x 。
1≤k≤5,0≤p,|q|≤10000,0≤l≤r≤109。
不难发现,对于方程的解 x ,s(x)和 x 是唯一对应的,而0≤s(x)≤81,显然应该枚举 s(x) 来判断 x 是否是解,时间复杂度O(1)。
#include
#include
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;
}
给定一个长度为 n 的序列A和一个长度为 m 的序列B,序列 A 代表一个图中的印章,印章的图案不是中心对称的
,序列B代表一条边界线,现在问 A 在可以旋转180∘或者不旋转的情况下,经过平移使得印章的上边缘或是下边缘与边界线完全重合,能拓印出多少种完全不重叠的图案。
n,m≤106 ,序列元素不超过 109 。
印章与边界线重合有四种情况,印章的上或下边缘在边界线的上或下方重合,现在来分别分析这四种情况的方案数。
1. 印章的下边缘在边界线的上方或下方重合,即 B 序列里有长度为n的满足元素完全相等的连续子序列对应上下两个方案。
2. 印章的上边缘在边界线的下方重合,即序列 A 的元素加上同一个值之后与B的连续子序列相等对应一个方案。
3. 印章的上边缘在边界线的上方重合,即序列 A 的元素加上同一个值之后与B的连续子序列的逆序列相等。
由于印章的图案不是中心对称的,所以当印章的上边缘完全与下边缘平行时,可能有同样位置的方案,但是印章的图案不重合,直接分别考虑即可。
考虑情况1,由于下边缘是完全水平的,所以只需要看 B 序列里有多少个长度为n的连续子序列是元素完全相等的,由于上下方都可行则方案数再乘 2 即可。可以算出B里的所有互不相交的最长连续相同子序列,对于每个连续的序列,如果其长度 k 不小于n,则对答案贡献 k−n+1 个长度为 n 的连续相同子序列。直接扫一遍序列B即可,时间复杂度 O(m) 。
考虑情况2和3,不难发现如果序列 A 的相邻元素之差等于序列B的子序列 B′ 的相邻元素之差,则 A 和B′可以重合,或者说是匹配。则问题转化为长度为 n−1 的差分序列 dA 和长度为 m−1 的差分序列 dB 的匹配问题,其中 dAi=Ai+1−Ai,dBi=Bi+1−Bi 。可以将 dB 和 dB 的逆序列连接,中间加一个极大元素保证 dA 不会同时匹配到 dB 和 dB 的逆序列,将其作为文本串,将 dA 作为模板串,直接进行kmp匹配,时间复杂度 O(n+m) 。
注意情况2和3需要特判模板串长度为 0 的情况,即n=1的情况。
#include
#include
#include
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;
}
定义一颗无向树的直径是树上最远两点的距离。给定一颗 n 个点的边有长度的无向树R,一颗 m 个点的边有长度的无向树F,现在可以在两颗树之间添加一条长度为 t 的边将两颗树连接成一颗新的n+m个点的无向树 S ,求最优加边方案能得到的树S直径的最小值,以及等概率随机加边情况下树 S 直径的期望。
n,m≤200000,任意边的长度不超过 min(1000,4∗106max(n,m)) 。
对于每个棵树上,可以在 O(节点数) 的时间复杂度里,通过两遍dfs完成树形dp,计算出每个节点到其最远点的距离,而一棵树的直径即这些距离的最大值。
考虑一棵树,定义 f1[i] 表示以 i 为根的子树里到i的最远点距离,f2[i]表示以 i 为根的子树里到i的次远点距离, p[i] 表示以 i 为根的子树里到i的最远点所在孩子子树的孩子编号, f3[i] 表示整棵树里到 i 的最远点距离。
f1,f2,p可以通过一遍dfs得出,对于叶子节点 leaf , f1[leaf]=f2[leaf]=0,p[leaf]=∅ ,对于非叶节点 u ,只需要考虑孩子节点向下走的最远距离即可。
设其一个孩子为son,距离为 w ,则走孩子son子树的最远距离为 f1[son]+w ,那么 f1[u] 为所有 f1[son]+w 的最大值, p[u] 为 f1[u] 对应的 son , f2[u] 为次大值即可,注意到这里 f1[u] 可以等于 f2[u] 。
考虑再用一遍dfs得到 f3 ,对于树根节点 root , f3[root]=f1[root] ,对于非根节点 u ,向孩子走的方案已经包含在f1[u]里了,只需要再考虑向父亲走的情况。
设其父亲为 fa ,距离为 w ,若父亲的最远距离来自u子树,则 u 向上走到fa,再走到其他子树的最远点距离为 f2[fa]+w ,那么 f3[u]=max(f1[u],f2[fa]+w) ,若父亲的最远距离不是来自 u 子树,则u向上走到 fa ,再走到其他子树的最远点距离为 f1[fa]+w ,那么 f3[u]=max(f1[u],f1[fa]+w) 。
在实际的实现上, f3 可以直接用 f1 代替,具体可以参见代码。
对于加边方案,设其连接的是树 R 的节点i和树 F 的节点j,设其对应的在其树中的到最远点的距离为 f[i],g[j] ,设树 R,F,S 的直径分别为 diaR,diaF,diaS ,则这种连边方案对应的新树 S 满足diaS=max(diaR,diaF,f[i]+g[j]+t)。
令 w=max(diaR,diaF) ,则对于给定的 i ,任意满足f[i]+g[j]+t<w的加边方案,新树的直径 diaS=w ,否则 diaS=f[i]+g[j]+t ,可以发现,将 g[j] 排序后,对应的 j 是一段后缀区间,即j′≤j≤m,那么可以二分得到 j′ 。但是我们还可以观察到,如果 i 产生变化导致f[i]变小, j′ 只会变大或不变,那么将 f[i] 也排序后, i 的递增伴随着j′的非增,而j' j′ 最多减 m 次,所以这段计算直径可以通过两个下标做到O(n+m),但是由于排序的复杂度,整体的时间复杂度还是 O(nlogn) ,但是比二分好写。
由此我们可以通过枚举 i ,变化j′的形式得到最小的可能的直径,也可在计算了 g[i] 的前缀和之后快速计算出所有加边方案对应的直径之和,题目的两个问题都得到了解决。
#include
#include
#include
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;
}
为了NOIP提高组难度而设定的部分分真是不忍直视。