D. Dark LaTeX vs. Light LaTeX
题意:
给你两个字符串
input
abab
ab
output
8
数据范围
分析
首先思考:我们应该怎么找,才能不重不漏呢?我们为什么会有这样的问题?因为我们有两个串。
但我们只考虑一个串的时候,会有很多方法能够做到这一点。进一步的,如果
好现在我们已经确定好顺序了。既然
如果
如果
我们貌似还是不好统计。但是“
|
|---|---|
|
|
|
|
|
|
最后处理一下长度相等的。信息可以在
为方便处理长串和短串,在讨论
所以我们只需
code
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define X first
#define Y second
#define umap unordered_map
using ll = long long;
using ull = unsigned long long;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int maxn = 2e5 + 5, mod = 1e9 + 7, maxp = 31, inf = 1e9;
const ll INF = 1e18;
const double eps = 1e-6;
const int N = 5005;
int lcs[N][N], lcp[N][N];
int f[N][N], g[N][N];
void solve() {
string ss, tt;
cin >> ss >> tt;
const int n = ss.size(), m = tt.size();
ll ans = 0;
bool ok = true;
auto work = [&](string s, string t) {
const int n = s.size(), m = t.size();
s = " " + s;
t = " " + t;
const int N = max(n, m);
memset(lcp, 0, sizeof lcp);
memset(lcs, 0, sizeof lcs);
memset(f, 0, sizeof f);
memset(g, 0, sizeof g);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (s[i] == t[j]) {
lcs[i][j] = lcs[i - 1][j - 1] + 1;
}
else {
lcs[i][j] = 0;
}
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int len = lcs[i][j];
if (len) {
f[i][len]++;
if (ok) {
ans += len;
}
}
}
}
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= N; j++) {
g[i][j] = j * f[i][j] + g[i][j - 1];
}
}
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= N; j++) {
f[i][j] += f[i][j - 1];
}
}
for (int i = n; i >= 1; i--) {
for (int j = i - 1; j >= 1; j--) {
if (s[i] == s[j]) {
lcp[j][i] = lcp[j + 1][i + 1] + 1;
}
else {
lcp[i][j] = 0;
}
}
}
// 这里枚举的位置是l和r + 1
for (int p1 = 1; p1 <= n; p1++) {
for (int p2 = p1 + 2; p2 <= n; p2++) {
if (p2 == p1 + 1) continue;
int lo = max(1, p2 - (p1 + lcp[p1][p2] - 1) - 1);
int hi = p2 - p1 - 1;
int len = hi - lo + 1;
ans += g[p2 - 1][hi] - g[p2 - 1][lo - 1] - 1ll * (lo - 1) * (f[p2 - 1][hi] - f[p2 - 1][lo - 1]) + 1ll * (f[p2 - 1][m] - f[p2 - 1][hi]) * len;
}
}
ok = false;
};
work(ss, tt);
reverse(ss.begin(), ss.end());
reverse(tt.begin(), tt.end());
work(tt, ss);
cout << ans << endl;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int tt = 1;
// cin >> tt;
while (tt--) solve();
return 0;
}
I. Three Rectangles
看似过题人数I < B < D,实际I题非常非常水。
题意:
给你一个高为
input
10
2 2
1 1
1 1
1 1
2 2
1 1
1 2
1 2
2 2
1 1
1 2
2 1
2 2
1 2
1 2
1 2
2 2
1 2
1 2
2 1
1 3
1 1
1 2
1 3
1 4
1 1
1 2
1 3
1 5
1 1
1 2
1 3
1 6
1 1
1 2
1 3
1000000000 1000000000
1 1
1 1
1000000000 1000000000
output
0
8
4
6
4
6
12
14
6
2401
数据范围
有样例数,
分析
切入点是四个角。这四个角上是肯定要放的。所以想枚举四个角放谁。
但是这样是有问题的。如果一个长方形占了两个角甚至四个角呢?想到这个点之后就有新的思路了。如果有一个长方形占了大于一个角,那么肯定有
下面记
1. 存在
if (h[i] == H && w[i] == W) {
ll ans = 1;
for (int j = 0; j < 3; j++) {
ans = ans * (H - h[j] + 1) % mod * (W - w[j] + 1) % mod;
}
cout << ans << endl;
return;
}
。这是最复杂的一种情况了,但也不难。此时长方形退化成了一条线段。因为要做到不重不漏,我们可以枚举谁靠着最左端(下称 ),谁靠着最右端(下称 )。因为有可能有重叠的部分会重复计算(重叠是指有两个放在最左端,两个放在最右端),因此我们分离开来。
- 先讨论不重叠的情况。如果
,那么 除了最左最右端是随便放的。否则, 能放的最大位置就是贴着 ,能放的最小位置就是贴着 。 - 那么重叠的部分呢?显然不可能三个都贴着一端。此时枚举谁单独在一端就行了,剩下两个重叠。由于左右端可以互换,因此一旦满足就需要
。
if (cnt == 3) {
ll s = 0;
for (int i = 0; i < 3; i++) {
s += w[i];
}
if (s < W) {
cout << 0 << endl;
return;
}
vector<int> p(3);// p与上文意义相同
iota(p.begin(), p.end(), 0);
ll ans = 0;
do {
if (w[p[0]] + w[p[2]] < W) {
ll mn = max(1ll, W - w[p[2]] - w[p[1]]);
ll mx = min(W - w[p[1]] - 1, w[p[0]]);
ans += mx - mn + 1;
ans %= mod;
}
else {
ans += max(0ll, W - w[p[1]] - 1);
ans %= mod;
}
} while (next_permutation(p.begin(), p.end()));
for (int i = 0; i < 3; i++) {
for (int j = i + 1; j < 3; j++) {//i和j是两个重叠的
int k = 3 - i - j;
if (max(w[i], w[j]) + w[k] >= W) {
ans += 2;
}
}
}
cout << ans % mod << endl;
return;
}
。这就非常好判断了。你会发现如果满足 的这两个中间留了一条缝,那么答案肯定是 。否则,这两个已经把所有空间占满了。剩下的那个随便放即可。
if (cnt == 2) {
int p1 = -1, p2 = -1;
for (int i = 0; i < 3; i++) {
if (h[i] == H) {
if (p1 == -1) {
p1 = i;
}
else {
p2 = i;
}
}
}
int p3 = 3 - p1 - p2;
if (w[p1] + w[p2] >= W) {
cout << 2 * (W - w[p3] + 1) * (H - h[p3] + 1) % mod << endl;
}
else {
cout << 0 << endl;
}
return;
}
。剩下两个不满足 的如果不能把 填满,答案是0;或者不能把 填满的,答案为0。否则上下互换,左右互换, 。
for (int i = 0; i < 3; i++) {
if (h[i] == H) {
ll mn = inf, s = 0;
for (int j = 0; j < 3; j++) {
if (j == i) continue;
s += h[j];
mn = min(mn, w[j]);
}
if (s >= H && mn + w[i] >= W) {
cout << 4 << endl;
return;
}
}
}
。只能占三个角。答案是0。
时间复杂度。(带点常数,问题不大
最后贴个总代码
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define X first
#define Y second
#define umap unordered_map
using ll = long long;
using ull = unsigned long long;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int maxn = 2e5 + 5, mod = 1e9 + 7, maxp = 31, inf = 1e9;
const ll INF = 1e18;
const double eps = 1e-6;
void solve() {
ll H, W;
cin >> H >> W;
vector<ll> w(3), h(3);
for (int i = 0; i < 3; i++) {
cin >> h[i] >> w[i];
}
for (int i = 0; i < 3; i++) {
if (h[i] == H && w[i] == W) {
ll ans = 1;
for (int j = 0; j < 3; j++) {
ans = ans * (H - h[j] + 1) % mod * (W - w[j] + 1) % mod;
}
cout << ans << endl;
return;
}
}
int cnt = 0;
for (int i = 0; i < 3; i++) {
if (h[i] == H) {
cnt++;
}
}
if (cnt == 3) {
ll s = 0;
for (int i = 0; i < 3; i++) {
s += w[i];
}
if (s < W) {
cout << 0 << endl;
return;
}
vector<int> p(3);
iota(p.begin(), p.end(), 0);
ll ans = 0;
do {
if (w[p[0]] + w[p[2]] < W) {
ll mn = max(1ll, W - w[p[2]] - w[p[1]]);
ll mx = min(W - w[p[1]] - 1, w[p[0]]);
ans += mx - mn + 1;
ans %= mod;
}
else {
ans += max(0ll, W - w[p[1]] - 1);
ans %= mod;
}
} while (next_permutation(p.begin(), p.end()));
for (int i = 0; i < 3; i++) {
for (int j = i + 1; j < 3; j++) {
int k = 3 - i - j;
if (max(w[i], w[j]) + w[k] >= W) {
ans += 2;
}
}
}
cout << ans % mod << endl;
return;
}
if (cnt == 2) {
int p1 = -1, p2 = -1;
for (int i = 0; i < 3; i++) {
if (h[i] == H) {
if (p1 == -1) {
p1 = i;
}
else {
p2 = i;
}
}
}
int p3 = 3 - p1 - p2;
if (w[p1] + w[p2] >= W) {
cout << 2 * (W - w[p3] + 1) * (H - h[p3] + 1) % mod << endl;
}
else {
cout << 0 << endl;
}
return;
}
swap(H, W);
swap(h, w);
cnt = 0;
for (int i = 0; i < 3; i++) {
if (h[i] == H) {
cnt++;
}
}
cnt = 0;
for (int i = 0; i < 3; i++) {
if (h[i] == H) {
cnt++;
}
}
if (cnt == 3) {
ll s = 0;
for (int i = 0; i < 3; i++) {
s += w[i];
}
if (s < W) {
cout << 0 << endl;
return;
}
vector<int> p(3);
iota(p.begin(), p.end(), 0);
ll ans = 0;
do {
if (w[p[0]] + w[p[2]] < W) {
ll mn = max(1ll, W - w[p[2]] - w[p[1]]);
ll mx = min(W - w[p[1]] - 1, w[p[0]]);
ans += mx - mn + 1;
ans %= mod;
}
else {
ans += max(0ll, W - w[p[1]] - 1);
ans %= mod;
}
} while (next_permutation(p.begin(), p.end()));
for (int i = 0; i < 3; i++) {
for (int j = i + 1; j < 3; j++) {
int k = 3 - i - j;
if (max(w[i], w[j]) + w[k] >= W) {
ans += 2;
}
}
}
cout << ans % mod << endl;
return;
}
if (cnt == 2) {
int p1 = -1, p2 = -1;
for (int i = 0; i < 3; i++) {
if (h[i] == H) {
if (p1 == -1) {
p1 = i;
}
else {
p2 = i;
}
}
}
int p3 = 3 - p1 - p2;
if (w[p1] + w[p2] >= W) {
cout << 2 * (W - w[p3] + 1) * (H - h[p3] + 1) % mod << endl;
}
else {
cout << 0 << endl;
}
return;
}
swap(H, W);
swap(h, w);
for (int i = 0; i < 3; i++) {
if (h[i] == H) {
ll mn = inf, s = 0;
for (int j = 0; j < 3; j++) {
if (j == i) continue;
s += h[j];
mn = min(mn, w[j]);
}
if (s >= H && mn + w[i] >= W) {
cout << 4 << endl;
return;
}
}
}
swap(H, W);
swap(h, w);
for (int i = 0; i < 3; i++) {
if (h[i] == H) {
ll mn = inf, s = 0;
for (int j = 0; j < 3; j++) {
if (j == i) continue;
s += h[j];
mn = min(mn, w[j]);
}
if (s >= H && mn + w[i] >= W) {
cout << 4 << endl;
return;
}
}
}
cout << 0 << endl;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int tt = 1;
cin >> tt;
while (tt--) solve();
return 0;
}
B. Turning Permutation
题意:
令
### input
3 2
output
2 1 3
数据范围
分析
我们发现转折序列和第
在做这些之前,我们分析一下题目本身的性质。
- 给你一个长度为
dp[0] = dp[1] = 2;
for (int i = 2; i <= 23; i++) {
ll x = 0;
for (int p = 1; p <= i; p++) {
int l = p - 1, r = i - p;
x += C(i - 1, l) * dp[l] / 2 * dp[r] / 2;
}
dp[i] = x;
}
发现
接下来把 “转折序列” 和 “第
我们考虑把第
这下思路就清晰了。我们从小到大枚举每个数放在哪个位置。注意除了
接下来,如果我们已经确定的一些位置,那么会产生多少种情况呢?我们对于每个区间考虑。找到两个相邻