总结
考场梦游,T1,T2 交错了,T1 忘记关注释和 Hash 值了,T2 也忘记关注释了…
T1.回文串
题意:翻转 \([l, r]\) 使序列变为回文串。
1. 考虑 \([l, r]\) 全在左半边或者右半边,举例全在左半边。
这时我们可以通过右半边知道整个序列的样子,而我们只能改变连续一段,那么对比翻转前后不一样的地方,看看翻转一次能不能达到就行了。
2. 考虑 \([l, r]\) 分居两侧。
先说结论,找到一个最大的整数 \(x\),使 \(s[1, x] = s[n, n - x + 1]\),这时我们只能翻转 \([x + 1, y] (y \in [x + 1, n - x])\) 或者 \([y, n - x] (y \in [x + 1, n - x])\)
证明,为什么可以抛弃掉首尾回文的部分。
考虑答案是翻转 \([l, r] (l \leq x)\)
由于首尾回文,那么 \(s[l, x] = s[n - l + 1, n - x + 1]\),由于 \([l, r]\) 翻转后序列为回文,那么 \(len = x - l + 1, s[r - len + 1, r] = s[x, l]\),所以说 \([l, r]\) 首尾长度小于等于 \(x - l + 1\) 的部分回文,因此去掉这一段也有正确答案。
证明,为什么翻转区间必须包含 \(l\) 或者 \(r\)。
因为 \(s[l] \neq s[r]\),所以必须要有一个位置被修改。
有了这个结论代码还是比较好写的,指针去找 \(x\),然后然后枚举 \(y\),用 \(Hash\) \(\mathcal{O}(1)\) 判断 序列是否回文。
参考代码
// ubsan: undefined
// accoders
#include <set>
#include <map>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <cstdio>
#include <vector>
#include <random>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define fi first
#define se second
#define db double
#define LL long long
//#define int long long
#define PII pair<int, int>
#define ULL unsigned long long
#define MP(x, y) make_pair(x, y)
#define rep(i, j, k) for (int i = (j); i <= (k); i++)
#define per(i, j, k) for (int i = (j); i >= (k); i--)
template <typename T>
void read(T &x) {
x = 0;
T f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + ch - '0';
ch = getchar();
}
x *= f;
}
template <typename T, typename... Args>
void read(T &x, Args &... Arg) {
read(x), read(Arg...);
}
const int MaxPrint = 1000;
int Poi_For_Print, Tmp_For_Print[MaxPrint + 5];
template <typename T>
void write(T x) {
if (x == 0) {
putchar('0');
return;
}
bool flag = (x < 0 ? 1 : 0);
x = (x < 0 ? -x : x);
while (x) Tmp_For_Print[++Poi_For_Print] = x % 10, x /= 10;
if (flag)
putchar('-');
while (Poi_For_Print) putchar(Tmp_For_Print[Poi_For_Print--] + '0');
}
template <typename T, typename... Args>
void write(T x, Args... Arg) {
write(x);
putchar(' ');
write(Arg...);
}
template <typename T, typename... Args>
void print(T x, char ch) {
write(x);
putchar(ch);
}
template <typename T>
T Max(T x, T y) {
return x > y ? x : y;
}
template <typename T>
T Min(T x, T y) {
return x < y ? x : y;
}
template <typename T>
T Abs(T x) {
return x > 0 ? x : -x;
}
const int Maxn = 1e5;
const ULL P = 13331;
int t, n;
char s[Maxn + 5], b[Maxn + 5];
ULL pre[Maxn + 5], fall[Maxn + 5], w[Maxn + 5];
signed main() {
// freopen ("1.in", "r", stdin);
// freopen ("1.out", "w", stdout);
freopen("palindrome.in", "r", stdin);
freopen("palindrome.out", "w", stdout);
w[0] = 1;
rep(i, 1, Maxn) w[i] = w[i - 1] * P;
read(t);
// printf ("%d\n", t);
while (t--) {
read(n);
scanf("%s", s + 1);
// print (n, '\n');
// printf ("%s\n", s + 1);
bool ck = 1;
for (int i = 1, j = n; i <= j; i++, j--)
if (s[i] != s[j])
ck = 0;
if (ck) {
printf("1 1\n");
continue;
}
int li1 = n / 2, li2 = n & 1 ? (n / 2 + 2) : (n / 2 + 1);
pre[0] = fall[n + 1] = 0;
rep(i, 1, n) {
b[n - i + 1] = s[i];
pre[i] = pre[i - 1] * P + s[i] - 'a' + 1;
}
per(i, n, 1) fall[i] = fall[i + 1] * P + s[i] - 'a' + 1;
// half left part
int l = 1, r = li1;
while (l <= n && b[l] == s[l]) l++;
while (r >= 1 && b[r] == s[r]) r--;
for (int i = l, j = r; i <= j; i++, j--) swap(b[i], b[j]);
bool fl = 1;
rep(i, 1, li1) if (b[i] != s[i]) fl = 0;
if (fl) {
print(l, ' '), print(r, '\n');
continue;
}
// half right part
rep(i, 1, n) b[n - i + 1] = s[i];
l = li2, r = n;
while (l <= n && b[l] == s[l]) l++;
while (r >= 1 && b[r] == s[r]) r--;
for (int i = l, j = r; i <= j; i++, j--) swap(b[i], b[j]);
fl = 1;
rep(i, li2, n) if (b[i] != s[i]) fl = 0;
if (fl) {
print(l, ' '), print(r, '\n');
continue;
}
// others
l = 1, r = n;
while (l <= r && s[l] == s[r]) l++, r--;
PII ans = MP(-1, -1);
// printf ("---%d %d\n", l, r);
rep(i, l, r) {
// swap (i, r)
ULL tmp1 =
(pre[i - 1] - pre[l - 1] * w[i - l]) * w[r - i + 1] + (fall[i] - fall[r + 1] * w[r - i + 1]);
ULL tmp2 = (pre[r] - pre[i - 1] * w[r - i + 1]) * w[i - l] + (fall[l] - fall[i] * w[i - l]);
if (tmp1 == tmp2) {
ans = MP(i, r);
break;
}
// swap (l, i)
tmp1 = (fall[l] - fall[i + 1] * w[i - l + 1]) * w[r - i] + (pre[r] - pre[i] * w[r - i]);
tmp2 =
(fall[i + 1] - fall[r + 1] * w[r - i]) * w[i - l + 1] + (pre[i] - pre[l - 1] * w[i - l + 1]);
if (tmp1 == tmp2) {
ans = MP(l, i);
break;
}
}
print(ans.fi, ' '), print(ans.se, '\n');
}
return 0;
}
T2.国际象棋
不是 \(std\) 的做法
\(n * m\) 的棋盘能摆放多少对互相能吃到的马。
打表容易发现若 \(n, m \geq 3,nm \leq 10000\) 的时候有方式把他铺满(ps:std 就是证明了去掉 \(nm \leq 1000\) 结论也成立),那么考虑通过构造缩小 \(n, m\),使 \(nm \leq 10000\)。
如果有 \(n ~ or ~ m \leq 2\),容易得出结论对数最多的方案数长这样。
怎么缩小 \(n, m\)?考虑四格四格缩小,以缩小 \(m\) 为例,判断 \(n\) 的奇偶性。
\(n\) 为奇数:先涂一个 \(3 * 4\),然后涂 \(2 * 4\)。
\(n\) 为偶数:涂 \(2 * 4\)
通过手玩或者暴力等方法找到占满 \(3 * 4, 2 * 4\) 的方法。
我比较懒,发现缩小不可能使 \(n ~ or ~ m \leq 2\),因为要满足 \(nm \geq 1e4, n, m \leq 1e3\)。所以只缩小 \(n\) 或者 \(m\) 就行了。
有点卡常,调调参数就过了 /se,如果用 \(n, m\) 都缩的方法,那么可以缩的 \(3 \leq n, m \leq 7\),这个时候 \(Dinic\) 就飞快了(当然也可以打表)
// ubsan: undefined
// accoders
#include <set>
#include <map>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <cstdio>
#include <vector>
#include <random>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define fi first
#define se second
#define db double
#define LL long long
//#define int long long
#define PII pair<int, int>
#define ULL unsigned long long
#define MP(x, y) make_pair(x, y)
#define rep(i, j, k) for (int i = (j); i <= (k); i++)
#define per(i, j, k) for (int i = (j); i >= (k); i--)
template <typename T>
void read(T &x) {
x = 0;
T f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 3) + (x << 1) + ch - '0';
ch = getchar();
}
x *= f;
}
template <typename T, typename... Args>
void read(T &x, Args &... Arg) {
read(x), read(Arg...);
}
const int MaxPrint = 1000;
int Poi_For_Print, Tmp_For_Print[MaxPrint + 5];
template <typename T>
void write(T x) {
if (x == 0) {
putchar('0');
return;
}
bool flag = (x < 0 ? 1 : 0);
x = (x < 0 ? -x : x);
while (x) Tmp_For_Print[++Poi_For_Print] = x % 10, x /= 10;
if (flag)
putchar('-');
while (Poi_For_Print) putchar(Tmp_For_Print[Poi_For_Print--] + '0');
}
template <typename T, typename... Args>
void write(T x, Args... Arg) {
write(x);
putchar(' ');
write(Arg...);
}
template <typename T, typename... Args>
void print(T x, char ch) {
write(x);
putchar(ch);
}
template <typename T>
T Max(T x, T y) {
return x > y ? x : y;
}
template <typename T>
T Min(T x, T y) {
return x < y ? x : y;
}
template <typename T>
T Abs(T x) {
return x > 0 ? x : -x;
}
const int Inf = 0x3f3f3f3f;
int T, n, m, sn, sm;
const int Maxflown = 7 * 1e3 + 2;
const int Maxflowm = 1e5;
struct edge {
int to[Maxflowm * 2 + 5], Next[Maxflowm * 2 + 5];
LL flux[Maxflowm * 2 + 5];
int len, Head[Maxflown + 5];
edge() {
len = 1;
memset(Head, 0, sizeof Head);
}
void Init() {
len = 1;
memset(Head, 0, sizeof Head);
}
void plus(int x, int y, LL w) {
to[++len] = y;
Next[len] = Head[x];
flux[len] = w;
Head[x] = len;
}
void add(int x, int y, LL w) {
plus(x, y, w);
plus(y, x, 0);
}
void rev_add(int x, int y, LL w) {
plus(y, x, w);
plus(x, y, 0);
}
};
struct Max_Flow {
int S, T;
edge mp;
int hh, tt, q[Maxflown + 5];
int depth[Maxflown + 5], cur[Maxflown + 5];
void add(int x, int y, int _flux) { mp.plus(x, y, _flux); }
void Init() {
S = Maxflown - 1, T = Maxflown;
mp.Init();
}
bool BFS() {
rep(i, 1, tt) depth[q[i]] = 0;
hh = 1;
tt = 0;
q[++tt] = S;
depth[S] = 1;
cur[S] = mp.Head[S];
while (hh <= tt) {
int u = q[hh++];
for (int i = mp.Head[u]; i; i = mp.Next[i]) {
int v = mp.to[i];
LL w = mp.flux[i];
if (w == 0)
continue;
if (depth[v])
continue;
depth[v] = depth[u] + 1;
cur[v] = mp.Head[v];
q[++tt] = v;
if (v == T)
return 1;
}
}
return 0;
}
LL DFS(int u, LL Up) {
if (u == T)
return Up;
if (Up == 0)
return 0;
LL flow = 0;
while (cur[u] && flow < Up) {
int i = cur[u];
cur[u] = mp.Next[cur[u]];
int v = mp.to[i];
LL w = mp.flux[i];
if (w == 0)
continue;
if (depth[v] != depth[u] + 1)
continue;
LL tmp = DFS(v, Min(Up - flow, w));
if (tmp == 0)
depth[v] = -1;
flow += tmp;
mp.flux[i] -= tmp;
mp.flux[i ^ 1] += tmp;
if (mp.flux[i] && flow >= Up)
cur[u] = i;
}
return flow;
}
LL Dinic() {
LL flow = 0;
while (BFS()) {
flow += DFS(S, Inf);
}
return flow;
}
} G;
int tox[10] = { 0, -2, -2, -1, -1, 1, 1, 2, 2 };
int toy[10] = { 0, 1, -1, 2, -2, 2, -2, 1, -1 };
int Hash(int x, int y) { return (x - 1) * sm + y; }
int Hash_Now(int x, int y) { return (x - 1) * m + y; }
int Getx(int x) { return (x - 1) / sm + 1; }
int GetNowx(int x) { return (x - 1) / m + 1; }
int Gety(int x) { return (x - 1) % sm + 1; }
int GetNowy(int x) { return (x - 1) % m + 1; }
signed main() {
// freopen ("1.in", "r", stdin);
// freopen ("1.out", "w", stdout);
freopen("knight.in", "r", stdin);
freopen("knight.out", "w", stdout);
read(T);
// printf ("%d\n", T);
while (T--) {
G.Init();
read(n, m), sn = n, sm = m;
// printf ("%d %d\n", n, m);
vector<PII> ans;
while (n * m >= Maxflown - 2) {
if (n & 1) {
ans.push_back(MP(Hash(1, m - 3), Hash(3, m - 2)));
ans.push_back(MP(Hash(1, m - 2), Hash(2, m)));
ans.push_back(MP(Hash(1, m - 1), Hash(3, m)));
ans.push_back(MP(Hash(1, m), Hash(2, m - 2)));
ans.push_back(MP(Hash(2, m - 3), Hash(3, m - 1)));
ans.push_back(MP(Hash(2, m - 1), Hash(3, m - 3)));
for (int i = 4; i <= n; i += 2) {
ans.push_back(MP(Hash(i, m - 3), Hash(i + 1, m - 1)));
ans.push_back(MP(Hash(i + 1, m - 3), Hash(i, m - 1)));
ans.push_back(MP(Hash(i, m - 2), Hash(i + 1, m)));
ans.push_back(MP(Hash(i + 1, m - 2), Hash(i, m)));
}
} else {
for (int i = 1; i <= n; i += 2) {
ans.push_back(MP(Hash(i, m - 3), Hash(i + 1, m - 1)));
ans.push_back(MP(Hash(i + 1, m - 3), Hash(i, m - 1)));
ans.push_back(MP(Hash(i, m - 2), Hash(i + 1, m)));
ans.push_back(MP(Hash(i + 1, m - 2), Hash(i, m)));
}
}
m -= 4;
}
rep(i, 1, n) {
rep(j, 1, m) {
if ((i + j) & 1) {
G.add(Hash_Now(i, j), G.T, 1);
G.add(G.T, Hash_Now(i, j), 0);
continue;
}
G.add(G.S, Hash_Now(i, j), 1);
G.add(Hash_Now(i, j), G.S, 0);
rep(op, 1, 8) {
int x = i + tox[op], y = j + toy[op];
if (x < 1 || x > n)
continue;
if (y < 1 || y > m)
continue;
// printf ("(%d, %d) -> (%d, %d)\n", i, j, x, y);
G.add(Hash_Now(i, j), Hash_Now(x, y), 1);
G.add(Hash_Now(x, y), Hash_Now(i, j), 0);
}
}
}
G.Dinic();
for (int i = 2; i <= G.mp.len; i += 2) {
int x = G.mp.to[i ^ 1], y = G.mp.to[i], _flux = G.mp.flux[i];
// printf ("x = %d, y = %d\n", x, y);
if (!_flux && 1 <= x && x <= n * m && 1 <= y && y <= n * m) {
ans.push_back(MP(Hash(GetNowx(x), GetNowy(x)), Hash(GetNowx(y), GetNowy(y))));
}
}
print(ans.size(), '\n');
for (auto it : ans) {
print(Getx(it.fi), ' '), print(Gety(it.fi), ' '), print(Getx(it.se), ' '),
print(Gety(it.se), '\n');
}
}
return 0;
}
T3.嘉心糖
题意:\(x, y\) 贴贴就连一条无向边,求图的最大团。
随机化可过,略了(\(DAY^{-1}\))。
正解:
春春的结论题(😀)
结论一:最大团点的数量=补图中最大独立集点的数量
那么转化为求补图的最大独立集。
结论二:最大独立集点的数量=最小可重路径覆盖(闭包情况下)
考虑人 \(x, y, z\) 之间的贴贴关系,容易知道 \(x, y\) 没有贴贴且 \(y, z\) 没有贴贴,那么 \(x, z\) 肯定没有贴贴,因为贴贴过程中一定 \(x < y < z\),体现在补图上就是给边定向(小指向大,大指向小都一样),这个图就是一个闭包。
结论三:最小不可重路径覆盖=总点数-拆点后二分图最大匹配数
照做即可。
感性证明:
结论一,最大独立集两两之间不相连,在原图上即为两两相连,那么就是一个完全子图,由于是最大独立集,那么就是团。
结论二,不知
结论三,
小结
T1 T2 几乎拿到就想到正解了,但是打+调试花了 \(2h\) 多,实际上代码也不是很复杂 🤮,代码实现加油中。