Educational CF Round 135 题解
签到题,找最大的那堆就行了
签到题,要让最后剩下的后缀和最大,首先
题意:
分析:首先这两个数组一定可以相似,因为所以数字最终通过
核心代码
std::multiset<int> a, b;
void Main()
{
int n;
read(n);
for (int i = 1, x; i <= n; ++i)
read(x), a.insert(x);
for (int i = 1, x; i <= n; ++i)
read(x), b.insert(x);
int ans = 0;
while (!a.empty())
{
if (*a.rbegin() == *b.rbegin())
{
a.erase(std::prev(a.end())), b.erase(std::prev(b.end()));
continue;
}
else
{
++ans;
if (*a.rbegin() > *b.rbegin())
{
int x = *a.rbegin();
a.erase(std::prev(a.end()));
a.insert(log10(x) + 1);
}
else
{
int x = *b.rbegin();
b.erase(std::prev(b.end()));
b.insert(log10(x) + 1);
}
}
}
printf("%d\n", ans);
}
题意:A和B从一个字符串
分析:
经典博弈论区间DP题型。
比较字符串的字典序就是找到更小的前缀,而字符串的前缀总是从更小的区间得到。
设
假设A取了
也就是:
add函数表示将两个比较结果合并的结果。
取
时间复杂度
核心代码
const int MAXN = 2000;
int dp[MAXN + 5][MAXN + 5];
char s[MAXN + 5];
int worst(int x, int y)
{
if (x == 0 || y == 0)
return 0;
if (x == 2 || y == 2)
return 2;
return 1;
}
int better(int x, int y)
{
if (x == 1 || y == 1)
return 1;
if (x == 2 || y == 2)
return 2;
return 0;
}
int add(int x, int y)
{
if (x == 1)
return 1;
if (x == 0)
return 0;
return y;
}
int cmp(char x, char y)
{
if (x < y)
return 1;
if (x > y)
return 0;
return 2;
}
void Main()
{
scanf("%s", s + 1);
memset(dp, -1, sizeof dp);
int n = strlen(s + 1);
for (int i = 1; i <= n - 1; ++i)
dp[i][i + 1] = s[i] == s[i + 1] ? 2 : 1;
for (int len = 4; len <= n; len += 2)
{
for (int i = 1; i <= n && (i + len - 1) <= n; ++i)
{
int j = i + len - 1;
dp[i][j] = 0;
// choose i
int tmp1 = worst(add(dp[i + 2][j], cmp(s[i], s[i + 1])), add(dp[i + 1][j - 1], cmp(s[i], s[j])));
int tmp2 = worst(add(dp[i + 1][j - 1], cmp(s[j], s[i])), add(dp[i][j - 2], cmp(s[j], s[j - 1])));
dp[i][j] = better(tmp1, tmp2);
// printf("dp[%d][%d] = %s\n", i, j, dp[i][j] == 0 ? "Lose" : (dp[i][j] == 1 ? "Win" : "Draw"));
}
}
if (dp[1][n] == 2)
printf("Draw\n");
else if (dp[1][n] == 0)
printf("Bob\n");
else
printf("Alice\n");
}
题意:
有
分析:
看到商店胡椒包关于不浪费的描述,很显然能想到扩展欧几里得,这样可以得到在该商店购买胡椒包是否可行。如果可行,方程
假设一共要做
美味度
展开之后变成
这个式子后半部分是定值,所以只需要最大化前半部分就可以了,发现这就是将
还可以发现,由于将
回到问题本身,现在有一系列
核心代码
const int MAXN = 3E5;
int diff[MAXN + 5];
ll sum[MAXN + 5];
int exgcd(int a, int b, ll &x, ll &y)
{
if (b == 0)
return x = 1, y = 0, a;
int d = exgcd(b, a % b, x, y);
int t = x;
x = y;
y = t - (a / b) * y;
return d;
}
void Main()
{
int n, m;
read(n);
ll tot = 0;
for (int i = 1, a, b; i <= n; ++i)
{
read(a, b);
tot += a;
diff[i] = b - a;
}
std::sort(diff + 1, diff + n + 1, std::greater<int>());
sum[0] = 0;
for (int i = 1; i <= n; ++i)
sum[i] = sum[i - 1] + diff[i];
int mx = 0;
for (int i = 1; i <= n; ++i)
if (sum[i] > sum[mx])
mx = i;
read(m);
for (int i = 1, a, b; i <= m; ++i)
{
read(a, b);
ll x, y;
int d = exgcd(a, b, x, y);
if (n % d != 0)
printf("-1\n");
else
{
int s = n / d;
x *= s, y *= s;
x = (x % (b / d) + b / d) % (b / d);
y = (n - 1ll * a * x) / b;
if (y < 0)
{
printf("-1\n");
continue;
}
int tmax = y / (a / d), ymin = y - tmax * (a / d);
int bymin = b * ymin, bymax = b * y;
ll ans = -1;
if (mx <= bymin)
ans = tot + sum[bymin];
else if (mx >= bymax)
ans = tot + sum[bymax];
else
{
int k = 1ll * (mx - bymin) * d / (1ll * a * b);
ans = std::max(tot + sum[bymin + 1ll * a * b / d * k],
tot + sum[bymin + 1ll * a * b / d * k + 1ll * a * b / d]);
}
printf("%lld\n", ans);
}
}
}
题意
有一个长度为
分析
其实这个题就是把每个
问题在于
不难想到网络流模型,由
这样建出来是一个二分图,左部有
考虑费用流的过程,只有右部节点向汇点边有费用,那么按最短路增广的话肯定是优先费用小的,也就是增广的顺序右部节点按权值从小到大排序。
那么不妨将脱离费用流,直接按右部节点从小到大排序,做增广顺序,跑二分图匹配。匈牙利算法的时间复杂度是
其实这里左部和右部的节点数量差距很大,最大匹配也就是n,而对右部节点按顺序增广有很多冗余。不妨在匈牙利算法没有找到增广路时,保留访问标记。因为当没有找到增广路的时候,图的匹配没变化,当前访问节点i没有找到增广路,下一次访问节点i也不会找到增广路。这样的话每两次最大匹配+1的时候,访问
其实还有很多种可能的优化,比如动态加点、动态删边等,这是一道上限很高的题目,因此它的难度分数在3100。
核心代码
constexpr int MAXN = 1E3;
std::vector<int> G[MAXN * MAXN + MAXN + 5];
bool vis[MAXN + 5];
int n, a[MAXN + 5], b[MAXN * MAXN + 5];
int link[MAXN + 5];
bool find(int u)
{
for (int v : G[u])
{
if (!vis[v])
{
vis[v] = true;
if (link[v] == 0 || find(link[v]))
return link[v] = u, true;
}
}
return false;
}
void Main()
{
read(n);
for (int i = 1; i <= n; ++i)
read(a[i]);
int cnt = 0;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
b[++cnt] = a[i] * j;
std::sort(b + 1, b + cnt + 1);
cnt = std::unique(b + 1, b + cnt + 1) - (b + 1);
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= n; ++j)
{
int u = std::lower_bound(b + 1, b + cnt + 1, a[i] * j) - (b);
G[u].push_back(i);
}
}
ll ans = 0;
for (int i = 1; i <= cnt; ++i)
{
if (find(i))
{
ans += b[i];
for (int j = 1; j <= n; ++j)
vis[j] = false;
}
}
printf("%lld\n", ans);
}
题意:
在一个长度为
有
分析:
先不考虑加灯。
看到m这么小的取值,很自然地想到状态压缩。设
这样做时间复杂度过高了。注意到一个很棘手的点在于,一个位置可能被重复照亮,这样在状态转移的时候不是很好枚举。
正难则反,不妨考虑一下容斥。
首先很显然,总方案数是
不合法的方案则是某几盏灯不亮,其他灯随意。假设
那么最终答案就是
所以接下来考虑
注意到一盏灯,其实只会照亮以该灯为中心,一段距离内的所有兴趣点。假设
设
为了方便计算,我们添加
这一步预处理时间复杂度为
现在考虑添加一个灯
如果从头再做一次dp,时间复杂度显然无法接受。
注意到添加灯的时候,对
事实上是存在的。类比背包问题,每个物体可选可不选,如果用状态压缩去算的话显然也是枚举每种集合。实际上我们可以一位一位地递推。
重新设置dp状态,设
由于我们知道第
这样做的话dp的时间复杂度是
最终时间复杂度为
除此之外还有很多做法。例如官方题解中用莫比乌斯反演在添加一盏灯的时候直接对相关的
总的来说这道题还是很灵活的,充分利用了很多组合数学和动态规划中的知识。
核心代码
constexpr int MAXN = 2E5;
constexpr int MAXD = 3e5;
constexpr int MAXM = 16;
int g[MAXM + 5][MAXM + 5], sg[MAXM + 5][MAXM + 5], dp[MAXM + 5];
int l[MAXN + 5], p[MAXM + 5];
void Main()
{
int d, n, m;
read(d, n, m);
for (int i = 1; i <= n; ++i)
read(l[i]);
for (int i = 1; i <= m; ++i)
read(p[i]);
std::sort(l + 1, l + n + 1);
std::sort(p + 1, p + m + 1);
p[0] = -d * 10, p[m + 1] = d * 10;
ll all = 1;
for (int i = 1; i <= n; ++i)
all = (all * (d + 1)) % mod;
for (int i = 0; i <= m + 1; ++i)
for (int j = 0; j <= m + 1; ++j)
g[i][j] = 1;
for (int i = 1, L = 0; i <= n; ++i)
{
while (L <= m && p[L + 1] < l[i])
++L;
int R = L + 1;
for (int x = 0; x <= L; ++x)
for (int y = R; y <= m + 1; ++y)
{
int dist = std::min(std::abs(l[i] - p[x]) - 1, std::abs(l[i] - p[y]) - 1);
dist = std::min(d, dist);
g[x][y] = 1ll * (dist + 1) * g[x][y] % mod;
}
}
int q;
read(q);
while (q--)
{
int f, L = 0;
read(f);
while (L <= m && p[L + 1] < f)
++L;
int R = L + 1;
dp[0] = mod - 1;
for (int x = 0; x <= L; ++x)
for (int y = R; y <= m + 1; ++y)
{
int dist = std::min(std::abs(f - p[x]) - 1, std::abs(f - p[y]) - 1);
dist = std::min(d, dist);
sg[x][y] = g[x][y];
g[x][y] = 1ll * (dist + 1) * g[x][y] % mod;
}
for (int i = 1; i <= m + 1; ++i)
{
dp[i] = 0;
for (int j = 0; j <= i - 1; ++j)
dp[i] = (dp[i] + mod - 1ll * dp[j] * g[j][i] % mod) % mod;
}
printf("%d\n", dp[m + 1]);
for (int x = 0; x <= L; ++x)
for (int y = R; y <= m + 1; ++y)
{
g[x][y] = sg[x][y];
}
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】