前言
还有 \(4\) 天就结束了呜呜呜,我还不想走,我还没打过国赛呜呜呜。
博弈
以为是个吊题,结果真是签到题啊QAQ。
首先我们要读明白题,我们一个点可以放多个棋子,所以可以得出一个结论:每个点是互不影响的。
所以我们可以每个点分开来算。
正如题解所说:“因为在自己所属点上的棋子是完全由自己操控的,不会莫名其妙地被对方抢走”,所以最优策略就是让自己可以走的步数减去对面可以走的步数。
然后我们就可以很轻易的得到一个 \(dp_i\),表示若 \(i\) 有棋子,\(A\) 可以走的比 \(B\) 多的步数。若 \(i\) 为白色,那么 \(dp_i\) 为最大值;若 \(i\) 为黑色,那么 \(dp_i\) 为最小值。
注意要把每个 \(dp_i\) 赋为 \(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\),则 \(a\) 与 \(c\) 一定有连边。
这样我们对于一个题目要求的合法排列一定存在:排列任意的前缀和任意的后缀都是好的。
然鹅上面和正解关系不大
根据爆搜,我们可以知道对于一个 \(DAG\)(有向无环图),自身和补图(若自身是前缀,则补图是除了自身以外的后缀)同时是好的的个数是 \(n!\) 的。可以爆搜!
那我们怎么维护呐?
继续人类智慧QAQ:
我们先拿出来的个 \(1\) 到 \(n\) 的严格单调递增的排列。对于相邻的 \(a,b\) 并且 \(a < b\),我们若交换 \(a,b\) 则表示 \(a, b\) 连边。最后我们求变成排列 \(n\) 到 \(1\) 的方案数就行。
对于题目里要求的情况,我们每次交换 \(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\) 的值不能从 \(k-1\) 转移过来。那么我们只能每次求一遍 \(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\) 赋值成 \(pre_i\)(表示 \(1\) 到 \(i\) 的前缀和) 就行。
若我们之后一直保持现在的 \(limit\),那么意味着后半部分一直受修改之前的影响。
若 \(limit\) 被更新成 \(pre_i+max\)(\(max\) 表示二分的区间最大和为 \(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;
}
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;
}