2024.2.26

前言

还有 4 天就结束了呜呜呜,我还不想走,我还没打过国赛呜呜呜。

博弈

以为是个吊题,结果真是签到题啊QAQ。

首先我们要读明白题,我们一个点可以放多个棋子,所以可以得出一个结论:每个点是互不影响的。

所以我们可以每个点分开来算。

正如题解所说:“因为在自己所属点上的棋子是完全由自己操控的,不会莫名其妙地被对方抢走”,所以最优策略就是让自己可以走的步数减去对面可以走的步数。

然后我们就可以很轻易的得到一个 dpi,表示若 i 有棋子,A 可以走的比 B 多的步数。若 i 为白色,那么 dpi 为最大值;若 i 为黑色,那么 dpi 为最小值。

注意要把每个 dpi 赋为 0,因为如果这个点实在不符合当前出手的人的利益,那么就不管这个点了。同时也可以将棋子全在黑点的情况抛去。

最后的统计答案的时候背包,找大于 0 的方案就行。

博客园

#include <bits/stdc++.h>
const int SIZE = 310;
const int mod = 998244353;
int N, M;
int cnt = 1, head[SIZE], to[SIZE * SIZE], next[SIZE * SIZE];
int degree[SIZE], dp[SIZE], f[2 * SIZE * SIZE];
char str[SIZE];
bool color[SIZE];
void AddEdge(int u, int v) {
++ cnt;
next[cnt] = head[u];
head[u] = cnt;
to[cnt] = v;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
freopen("game.in", "r", stdin);
freopen("game.out", "w", stdout);
std::cin >> N >> M;
std::cin >> (str + 1);
for (int i = 1; i <= N; ++ i)
color[i] = (str[i] == 'W' ? 0 : 1);
for (int i = 1, x, y; i <= M; ++ i) {
std::cin >> x >> y;
if (x < y)
std::swap(x, y);
degree[y] ++;
AddEdge(x, y);
}
for (int now = N; now >= 1; -- now) {
for (int i = head[now]; i; i = next[i]) {
if (color[to[i]] == 0)
dp[to[i]] = std::max(dp[to[i]], dp[now] + 1);
if (color[to[i]] == 1)
dp[to[i]] = std::min(dp[to[i]], dp[now] - 1);
}
}
int begin = 301 * 301;
f[begin] = 1;
for (int i = 1; i <= N; ++ i) {
if (dp[i] < 0) {
for (int j = begin - 300 * 300; j <= begin + 300 * 300; ++ j) {
if (f[j] == 0)
continue;
f[j + dp[i]] = (f[j + dp[i]] + f[j]) % mod;
}
}
else {
for (int j = begin + 300 * 300; j >= begin - 300 * 300; -- j) {
if (f[j] == 0)
continue;
f[j + dp[i]] = (f[j + dp[i]] + f[j]) % mod;
}
}
}
int answer = 0;
for (int i = begin + 1; i <= begin + 300 * 300; ++ i)
answer = (answer + f[i]) % mod;
std::cout << answer << '\n';
return 0;
}

排列

呃呃呃,这道题属实有些逆天了。

依靠人类智慧,我们发现对于题目的限制,我们可以将点对 (a,b) 连边。

然后我们称一个区间是好(或区间是有传递性的)的,当且仅当在一个区间内,若 a 能到达 c,则 ac 一定有连边。

这样我们对于一个题目要求的合法排列一定存在:排列任意的前缀和任意的后缀都是好的。

然鹅上面和正解关系不大

根据爆搜,我们可以知道对于一个 DAG(有向无环图),自身和补图(若自身是前缀,则补图是除了自身以外的后缀)同时是好的的个数是 n! 的。可以爆搜!

那我们怎么维护呐?

继续人类智慧QAQ:

我们先拿出来的个 1n 的严格单调递增的排列。对于相邻的 a,b 并且 a<b,我们若交换 a,b 则表示 a,b 连边。最后我们求变成排列 n1 的方案数就行。

对于题目里要求的情况,我们每次交换 c,d 的时候判断一下 a,b 有没有交换过就行。

洛谷
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
typedef long long ll;
const int mod = 998244353;
int N, M;
std::vector<std::pair<int, int>> limit[11][11];
std::vector<int> edge[4000000];
int array[11], pos[11], small[11];
ll fac[11], dp[4000000];
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
freopen("perm.in", "r", stdin);
freopen("perm.out", "w", stdout);
std::cin >> N >> M;
for (int i = 1, a, b, c, d; i <= M; ++i) {
std::cin >> a >> b >> c >> d;
limit[c][d].emplace_back(a, b);
}
fac[0] = 1;
for (int i = 1; i <= N; ++i) {
fac[i] = fac[i - 1] * i;
array[i] = i;
}
int id = 0;
do {
++id;
for (int i = 1; i <= N; ++i) {
pos[array[i]] = i;
small[i] = 0;
for (int j = i + 1; j <= N; ++j)
if (array[i] > array[j])
++small[i];
}
for (int i = 2; i <= N; ++i) {
if (array[i - 1] > array[i])
continue;
int c = array[i - 1];
int d = array[i];
bool flag = true;
for (const std::pair<int, int> &iter : limit[c][d]) {
if (pos[iter.first] < pos[iter.second]) {
flag = false;
break;
}
}
if (!flag)
continue;
int to = id;
to -= fac[N - i] * small[i];
to -= fac[N - i + 1] * small[i - 1];
to += fac[N - i] * small[i - 1];
to += fac[N - i + 1] * (small[i] + 1);
// std::cout << id << ' ' << to << ' ' << i - 1 << ' ' << i << '\n';
// for (int j = 1; j <= N; ++ j)
// std::cout << small[j] << ' ';
// std::cout << '\n';
edge[id].emplace_back(to);
}
} while (std::next_permutation(array + 1, array + 1 + N));
dp[1] = 1;
for (int i = 1; i <= id; ++i)
for (const int &to : edge[i]) dp[to] = (dp[to] + dp[i]) % mod;
std::cout << dp[id] << '\n';
return 0;
}

子段和

先想一想暴力。

我们发现:若我们现在求删完了 k 的值不能从 k1 转移过来。那么我们只能每次求一遍 k 能到的最小值。

然后我就不会了。

但是题解告诉我:我们二分答案最后的最小值就行了。

我的内心:哈哈_,你真幽默。

具体说说做法。

暴力写法1

我们每次二分答案可以到达的最小值。

在二分的 check 里面,我们维护一个前缀和的最小值,然后用现在的前缀和减去前缀和最小值,看一下是不是大于二分的值,若大于则让现在这个点的值减去大于的部分就行了。

注意我们对于每个点减去的值,在这个点之后的前缀和里面都要减去。

最后判断减去的值打不大于我们的 k 就行了。

但是我的写法好像没办法优化。

csdn
#include <bits/stdc++.h>
typedef long long ll;
const int SIZE = 1100;
const int mod = 998244353;
int N, K;
ll a[SIZE], pre[SIZE], answer;
bool check(int max, int rest) {
max -= 2e7;
ll min = 0, differ = 0;
for (int i = 1; i <= N; ++ i) {
ll now = pre[i] - differ;
if (now - min > max)
differ += (now - min - max);
now = pre[i] - differ;
min = std::min(min, now);
}
return (differ <= rest);
}
ll Answer(int k) {
int l = 1, r = 4e7 + 1, result = 0;
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid, k)) {
result = mid;
r = mid - 1;
}
else {
l = mid + 1;
}
}
return result - 2e7;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
std::cin >> N;
for (int i = 1; i <= N; ++ i) {
std::cin >> a[i];
pre[i] = pre[i - 1] + a[i];
}
std::cin >> K;
for (int i = 1; i <= K; ++ i) {
answer += Answer(i);
answer %= mod;
}
std::cout << answer << '\n';
return 0;
}

暴力写法2

同样是二分,只不过改一下 check

我们先设 limit 表示我们前缀和可以到的最大值。

然后若当前位置的前缀和大于 limit,那么我们就减去大于的部分就行。

但是这里把减法变成加法了,我们将此时的 limit 赋值成 prei(表示 1i 的前缀和) 就行。

若我们之后一直保持现在的 limit,那么意味着后半部分一直受修改之前的影响。

limit 被更新成 prei+maxmax 表示二分的区间最大和为 max) 了,那么就不受修改之前的影响了。

codeforces
#include <bits/stdc++.h>
typedef long long ll;
const int SIZE = 1100;
const int mod = 998244353;
int N, K;
ll a[SIZE], pre[SIZE], answer;
bool check(int max, int rest) {
max -= 2e7;
ll limit = max, has = 0;
for (int i = 1; i <= N; ++ i) {
if (pre[i] > limit) {
has += pre[i] - limit;
limit = pre[i];
}
limit = std::min(limit, pre[i] + max);
}
return (has <= rest);
}
ll Answer(int k) {
int l = 1, r = 4e7 + 1, result = 0;
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid, k)) {
result = mid;
r = mid - 1;
}
else {
l = mid + 1;
}
}
return result - 2e7;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
std::cin >> N;
for (int i = 1; i <= N; ++ i) {
std::cin >> a[i];
pre[i] = pre[i - 1] + a[i];
}
std::cin >> K;
for (int i = 1; i <= K; ++ i) {
answer += Answer(i);
answer %= mod;
}
std::cout << answer << '\n';
return 0;
}
posted @   觉清风  阅读(42)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示