Logo FiraCode 的博客

博客

标签
暂无

题解:CF2045H Missing Separators

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-01-12 10:06:08

考虑说直接贪心不太能做,所以考虑 DP。

考虑说我们选后缀比前缀容易,所以我们倒着 DP。

考虑说我们如果设 $f_{i,j}$ 表示分了后 $i$ 个 $j$ 段是否可行,我们发现 $i,j$ 从 $f_{k,j - 1}$ 转移,但我们需要枚举 $i,j,k$ 还有 $k$ 最后一段的长度,感觉不太好优化。

考虑到原来的 DP 主要关心最后一段多长,所以我们考虑设 $f_{i, j}$ 表示 $[i, n]$ 这段后缀,最后一段为 $[i,j]$ 最长的段数是多少。

然后我们枚举 $i, j$,然后考虑用 SA 求出来 $[i, n]$ 和 $[j + 1, n]$ 的最长公共前缀设为 $s$,那么就看如果 $[i,i + \min(lcp, j - i + 1)]$ 这段是否小于 $[j + 1, j + 1 + \min(lcp, j - i + 1)]$,如果是的话,那么 $f_{i,j}$ 就是 $\max_{k=j + 1 + \min(lcp, j - i + 1)} ^ {n} f_{j + 1, k}$,然后发现是后缀 $\max$,于是再开一个数组维护即可。

#include <bits\/stdc++.h>

using namespace std;

const int N = 10010;

int n, m, p;
char s[5010];
pair<int, int> f[5010][5010];
pair<int, int> g[5010][5010];
int st[16][N], log_2[N];
int sa[N], rk[N], ork[N], id[N], cnt[N];

int queryM(int l, int r) {
	int L = log_2[r - l + 1];
	return min(st[L][l], st[L][r - (1 << L) + 1]);
}

int main() {
	scanf("%s", s + 1);
	n = strlen(s + 1);

	m = 255;
	for (int i = 1; i <= n; ++i) cnt[rk[i] = s[i]]++;
	for (int i = 1; i <= m; ++i) cnt[i] += cnt[i - 1];
	for (int i = n; i >= 1; --i) sa[cnt[rk[i]]--] = i;

	for (int w = 1; ; w <<= 1, m = p) {
		int cc = 0;
		for (int i = n - w + 1; i <= n; ++i) id[++cc] = i;
		for (int i = 1; i <= n; ++i)
			if (sa[i] > w) id[++cc] = sa[i] - w;

		memset(cnt, 0, sizeof(cnt));
		for (int i = 1; i <= n; ++i) cnt[rk[i]]++;
		for (int i = 1; i <= m; ++i) cnt[i] += cnt[i - 1];
		for (int i = n; i >= 1; --i) sa[cnt[rk[id[i]]]--] = id[i];

		memcpy(ork, rk, sizeof(ork));
		p = 0;
		for (int i = 1; i <= n; ++i)
			if (ork[sa[i]] == ork[sa[i - 1]] && ork[sa[i] + w] == ork[sa[i - 1] + w])
				rk[sa[i]] = p;
			else
				rk[sa[i]] = ++p;

		if (p == n) break;
	}
	
	for (int i = 1, k = 1; i <= n; ++i) {
		if (!rk[i]) continue;
		if (k) --k;
		\/\/ cout << sa[rk[i] - 1] << ' ' << i << ' ' << rk[i] << ' ' << sa[rk[i]] << ' ' << sa[rk[i] - 1] << ' ' << sa[i] << endl;
		while (s[i + k] == s[sa[rk[i] - 1] + k]) ++k;

		st[0][rk[i]] = k;
	}

	for (int i = 1; i <= 15; ++i)
		for (int j = 1; j <= n; ++j)
			st[i][j] = min(st[i - 1][j], st[i - 1][j + (1 << i - 1)]);
	log_2[1] = 0;
	for (int i = 2; i <= n; ++i) log_2[i] = log_2[i >> 1] + 1;

	for (int i = n; i >= 1; --i) {
		for (int j = i; j <= n; ++j) {
			if (j == n) {
				f[i][j] = {1, n + 1};
				break;
			}

			int l = rk[i], r = rk[j + 1];
			if (l > r) swap(l, r);
			int lcp = min(queryM(l + 1, r), j - i + 1);

			if (lcp == n - j) continue;

			if (lcp >= j - i + 1) {
				if (g[j + 1][j + j - i + 2].first + 1 > f[i][j].first)
					f[i][j] = {g[j + 1][j + j - i + 2].first + 1, g[j + 1][j + j - i + 2].second};
			} else {
				if (s[i + lcp] < s[j + lcp + 1]) {
					if (g[j + 1][j + lcp + 1].first + 1 > f[i][j].first)
						f[i][j] = {g[j + 1][j + lcp + 1].first + 1, g[j + 1][j + lcp + 1].second};
				}
			}

			\/\/ cout << i << ' ' << j << ' ' << lcp << ' ' << f[i][j].first << "----" << ' ' << j + lcp + 1 << ' ' << g[j + 1][j + lcp + 1].first << endl;
		}

		for (int j = n; j >= i; --j) {
			g[i][j] = max(g[i][j + 1], {f[i][j].first, j});
		}
	}

	int ans = 0, id = 0;
	for (int i = 1; i <= n; ++i) {
		if (f[1][i].first > ans) {
			ans = f[1][i].first;
			id = i;
		}
	}

	printf("%d\n", ans);
	int l = 1;
	while (id <= n) {
		for (int i = l; i <= id; ++i) putchar(s[i]);
		putchar('\n');
		int tid = id;
		id = f[l][id].second;
		l = tid + 1;
	}

	return 0;
}

题解:AT_abc428_f [ABC428F] Pyramid Alignment

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-10-21 13:28:33

考虑两种操作,左端点要么是变成一个数,要么是变成一个数剪掉长度。

所以考虑设左端点都为 $a + b \cdot len$,其中 $len$ 为线段长度。

然后我们把 $a, b$ 相同的分为一类,然后每一类对应一个区间。

那么每次修改就把在 $1 \sim v$ 中的区间和 $v + 1 \sim n$ 的区间分裂开,然后将在 $1 \sim v$ 的区间都删除,并修改成对应的 $a + b \cdot len$。

然后显然左右端点都有单调性,直接二分即可找出合法区间。

CSP-2025游记

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-11-05 09:41:59

Day -1

在家休息,让 Chatgpt 找了几道分类讨论的题写了写。

Day 0

上午啥也没干,摆摆摆。

中午出发,发现手机没电了,于是乎蜘蛛纸牌接接接。

下午到了酒店,感觉打板子没用,于是复习编译选项以及常见tricks,一车人在我房间耍。途中尝试跟 S08577 联机 MC,发现 S08577 的菜鸡电脑啥都带不动,遂放弃。

发现酒店的饭不如 wfyz 一根,随便吃了点就找 quyuan 点了外卖。

然后去试机,感觉机器特别好用啊,敲个线段树试试。欸怎么重启了,我们的系统跟考试不一样说是。重启之后文件都没了,就懒得再写一遍了,只是把所有按键都试了一遍没问题就离场了。

Day 1

上午爽睡到 7:30,吃完饭回来就稍微复习了一下,然后被 S08577 通知去 tianruihan 的房间,于是随便收拾了一下,然后去玩了会。

吃完午饭也不想睡觉,发了会呆就到点了,然后下去集合,尝试预言必考线段树。

进考场开题,看了眼第一题,欸怎么这么难,先想想没限制不就是全选最大的,那就至多只有一个是 $> \frac n2$ 的,那直接把最大减次大放三个 set 就做完了,于是马上开写,写完很快就过了大样例,此时我同桌还在问压缩包密码是啥。

然后看第二题,怎么题面这么混乱,怎么城市跟乡镇混着用,他本来不就是城市,怎么还要城市化改造。先去看看第三题,看第三题发现只有可能是 $i$ 不相同,不可能是 $x,z$ 不相同,然后就是直接把两个串最长公共前后缀找出来,然后匹配一下,发现暴力有很多分,于是回去看 T2。

先按照乡镇就是前 $k$ 个城市来理解吧,那就是直接 $2^k$ 枚举改不改造,然后把新的边和原图最小生成树上的边混一块再做一遍最小生成树,那就开写。

欸小样例怎么没过,欸他怎么 $1$ 向 $1$ 连边,哦原来乡镇是独立的,那直接把编号改成 $n + i$ 就行,一遍过了大样例,但是我并没有用归并,因为相信 CCF 神机。

吃个士力架,嗯嚎吃。

然后看了看第四题,设了好几个状态都假了,然后发现只会状压,便回去想第三题。

想想,欸我之前怎么会的是 $O(L^2)$,$L^2$ 怎么分这么少,想想还能怎么做,哦那我只把公共后缀取出来,然后枚举前缀的长度,那左端点就固定了,然后只需要使得公共后缀相同就行,然后总共只有 $n$ 个,所以就是 $O(nq + L)$,写写吧。欸不对,样例二怎么有个长度不相同的,这是啥阴,长度不同不是一定不行吗,让我想想,哦对啊,这不是直接输出 $0$ 就行了,为啥放这种没用的数据啊。

吃个士力架,嗯嚎吃。

写写写,要用哈希懒得双了,就用单哈希吧不会卡的,写写写怎么还要用 map<pair<int, int>, int> 要不要离散化一下然后少一维,算了反正是暴力,写写写,好前两个样例过了,欸第三个怎么寄了,这怎么调,自己写一个比较程序吧。

然后找到是第 101 个询问不对,输出了一下,发现很多串都有这个子串,这我怎么找,欸我怎么输出 $0$,哦我后缀怎么求得是询问串的后缀,应该从前缀往后延伸的,改完对了,欸怎么跑这么快,不管了应该过不去。

吃个士力架,嗯嚎吃。

现在还剩 1.5h,想想还是先写特殊性质吧,第三题只有特殊性质 B 没写,发现这个只需要满足前缀且后缀长度比询问的串小就行,那我直接提前排好序然后二分就是喽,写写写,怎么挂了,再次使用比较程序。

哦原来是没有元素的时候会一直 continue,不会触发 break 导致寄了,那就先 break,好过了,怎么跑的没暴力快,不管了。

吃个士力架,嗯嚎吃。

现在还剩 1h,先写第四题暴力吧,没怎么调就过样例了,此时还剩 40m。

继续尝试思考第三题,发现可以直接把中间不同的那一段删了,然后把相同前后缀拼起来,中间隔开,然后把中间相同的分成若干等价类,然后每个等价类就相当于统计有多少串是询问串的子串,这不就是AC自动机板子。但是感觉写不完了,于是看了眼第四题把 $n=m$ 写了,然后就摆了,开始肉眼调试四道题。

发现第三题特殊性质判错了,改了改,然后到最后也没看出来啥其他的。

出考场没碰到熟人,然后就直接往巴车方向走,也没看到老师,然后看到了 Dtw,他说第二题不归并会被卡,感觉有点可能不过还是相信 CCF 神机,然后又碰到了 xuyunao,他说 xz001 因为运气不好,导致电脑炸缸写不了代码,浪费了几个小时,可能去不了 WC 了,默哀。

然后去到大巴随便吃了点东西就摆了,发现大家有点炸缸,而且好像有人每判第三题长度不相同。

后记

早上起来一看,哟云斗这么速度,发现自己没挂分,很开心。

在其他平台上测了测我的码,发现第二题很可能就是会被卡到 $80$,第三题得分在 $[70, 90]$ 之间。

然后跟 S08577 聊了聊发现他第一题挂成了 $5$ 分,感觉非常神秘,仔细一看发现他第一题判特殊性质判的是只要有非 $0$ 的就执行特殊性质,全是 $0$ 才是 $n^3$ 的暴力,然后挂飞了,只有 $n \le 200$ 并且 $b_i, c_i$ 全等于 $0$ 的这一个点有分。

发现很多人第二题读错题之后数组开小了忘改了。

NOIP 加油吧

题解:CF1980F1 Field Division (easy version)

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-06-17 06:14:55

这能绿?

由于 Alice 的路线不能往回走,所以他走的路线一定是这样的(红色是喷泉):

那么我们发现,只有移走拐点且最小值变小后才会增加。

那么我们考虑维护后缀 $\min$,然后对于 $x$ 相同的点,若哪一个 $y$ 会使当前 $\min$ 变小,那么移走这个点;若有多个 $y$ 会使 $\min$ 变小,那么取使 $\min$ 变小最多的那个 $y$。

然后这一段路程就是当前 $\min$ 乘这段路程的长度 $x_i-x_{i-1}(x_i \not= x_{i-1})$。

#include <bits\/stdc++.h>

using namespace std;

typedef long long ll;

int T;
int n, m, k;
array<int, 3> a[200010];
int is[200010];

int main() {
	scanf("%d", &T);
	while (T--) {
		scanf("%d%d%d", &n, &m, &k);
		for (int i = 1; i <= k; ++i) scanf("%d%d", &a[i][0], &a[i][1]), a[i][2] = i;

		sort(a + 1, a + 1 + k);
		ll minn = m + 1;
		ll sum = (minn - 1) * (n - a[k][0]);
		for (int i = k; i >= 1; --i) {
			int id = 0;
			if (a[i][1] < minn) id = a[i][2];
			minn = min(minn, 1ll * a[i][1]);
			while (i - 1 >= 1 && a[i - 1][0] == a[i][0]) {
				if (a[i - 1][1] < minn) id = a[i - 1][2];
				minn = min(minn, 1ll * a[i - 1][1]);
				--i;
			}

			sum += 1ll * (minn - 1) * (a[i][0] - a[i - 1][0]);

			is[id] = true;
		}

		printf("%lld\n", sum);
		for (int i = 1; i <= k; ++i) printf("%d ", (int)is[i]);
		puts("");
		for (int i = 1; i <= k; ++i) is[i] = 0;
	}
	return 0;
}

题解:CF1678B2 Tokitsukaze and Good 01-String (hard version)

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-08-15 09:07:59

提供一种线段树做法。

设 $f_{i,0/1}$ 表示前 $i$ 个数,当前这一段的值为 $0/1$。

对于 $f_{i, j}$ 那么转移就是枚举 $k$,然后加上 $(k,i]$ 这一段中与 $j$ 不同的数的个数,记为 $w(j, k, i)$。

而我们发现将 $i$ 往后移动的过程中,所有 $j$ 相同的 $w(j, k, i)$,要么都增加 $1$,要么不变。

那么无脑维护线段树即可,就是维护区间 $\min$,以及区间 $+$,统计段数就在维护区间 $\min$ 时直接将段数作为第二关键字即可。

#include <bits\/stdc++.h>

using namespace std;

int T;
int n;
char s[200010];
struct Node {
    int maxn, d;
    int add;
};

struct SGT {
    Node tr[800010];

    void pushup(int u) {
        tr[u].maxn = min(tr[u << 1].maxn, tr[u << 1 | 1].maxn);
        if (tr[u << 1].maxn < tr[u << 1 | 1].maxn) tr[u].d = tr[u << 1].d;
        else if (tr[u << 1].maxn > tr[u << 1 | 1].maxn) tr[u].d = tr[u << 1 | 1].d;
        else tr[u].d = min(tr[u << 1].d, tr[u << 1 | 1].d);
    }

    void pushdown(int u) {
        tr[u << 1].maxn += tr[u].add;
        tr[u << 1 | 1].maxn += tr[u].add;

        tr[u << 1].add += tr[u].add;
        tr[u << 1 | 1].add += tr[u].add;
    }

    void modify(int u, int l, int r, int x, Node d) {
        if (l == r) {
            tr[u] = d;
            return;
        }

        pushdown(u);

        int mid = (l + r) >> 1;

        if (x <= mid) modify(u << 1, l, mid, x, d);
        else modify(u << 1 | 1, mid + 1, r, x, d);

        pushup(u);
    }

    void modify(int u, int l, int r, int ql, int qr, int d) {
        if (l >= ql && r <= qr) {
            tr[u].add += d;
            tr[u].maxn += d;

            return;
        }

        pushdown(u);
        int mid = (l + r) >> 1;

        if (ql <= mid) modify(u << 1, l, mid, ql, qr, d);
        if (qr > mid) modify(u << 1 | 1, mid + 1, r, ql, qr, d);

        pushup(u);
    }

    Node query(int u, int l, int r, int ql, int qr) {
        if (l >= ql && r <= qr) return tr[u];

        pushdown(u);
        int mid = (l + r) >> 1;
        Node res = {0, 0, 0};
        bool is_l = false;

        if (ql <= mid) res = query(u << 1, l, mid, ql, qr), is_l = true;
        if (qr > mid) {
            if (!is_l) res = query(u << 1 | 1, mid + 1, r, ql, qr);
            else {
                auto t = query(u << 1 | 1, mid + 1, r, ql, qr);
                if (t.maxn == res.maxn) res.d = min(res.d, t.d);
                if (t.maxn < res.maxn) res = t;
            }
        }

        return res;
    }
} Tr[2];

int main() {
    scanf("%d", &T);
    while (T--) {
        scanf("%d", &n);
        scanf("%s", s + 1);

        Node res = {0, 1000000000};
        for (int i = 1; i <= n; ++i) {
            if (s[i] == '0') Tr[1].modify(1, 0, (n + 1) \/ 2, 0, (i + 1) \/ 2 - 1, 1);
            else Tr[0].modify(1, 0, (n + 1) \/ 2, 0, (i + 1) \/ 2 - 1, 1);

            if (!(i & 1)) {
                auto a1 = Tr[1].query(1, 0, (n + 1) \/ 2, 0, i \/ 2 - 1), a2 = Tr[0].query(1, 0, (n + 1) \/ 2, 0, i \/ 2 - 1);
                a1.d++, a2.d++;
                a1.add = a2.add = 0;

                \/\/ cout << a1.maxn << ' ' << a1.d << ' ' << a2.maxn << ' ' << a2.d << endl;
                
                if (i == n) {
                    res.maxn = min(a1.maxn, a2.maxn);
                    if (a1.maxn == res.maxn) res.d = a1.d;
                    if (a2.maxn == res.maxn) res.d = min(res.d, a2.d);
                }

                Tr[1].modify(1, 0, (n + 1) \/ 2, i \/ 2, a2);
                Tr[0].modify(1, 0, (n + 1) \/ 2, i \/ 2, a1);
            }
        }

        printf("%d %d\n", res.maxn, res.d);

        for (int i = 0; i <= n * 3 + 10; ++i) Tr[0].tr[i] = Tr[1].tr[i] = {0, 0, 0};
    }
    return 0;
}

题解:CF1687B Railway System

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-08-15 10:21:15

看到 $2m$ 次询问,考虑先把所有边的权值从小到大排序。

然后,考虑模拟 Kruskal,那么难点在判断当前边是否是链接了已联通的连通块。

那么我们可以考虑询问当前最小生成森林上的边加上当前边。

如果相比之前(设此时最大生成森林权为 $w$),加上边后的值若是 $w+v_i$,那么这条边就可以加到最大生成森林中。

询问次数为 $2m-1$。

#include <bits\/stdc++.h>

using namespace std;

int n, m;
pair<int, int> v[100010];

int main() {
    cin >> n >> m;
    for (int i = 1; i <= m; ++i) {
        string s;
        for (int j = 1; j <= m; ++j)
            if (j != i) s += '0';
            else s += '1';
        
        cout << "? " << s << endl;
        cin >> v[i].first;
        v[i].second = i;
    }

    sort(v + 1, v + 1 + m);
    int la = v[1].first, sum = v[1].first;
    string s;
    for (int i = 1; i <= m; ++i) 
        if (i != v[1].second) s += '0';
        else s += '1';

    for (int i = 2; i <= m; ++i) {
        string t = s;
        t[v[i].second - 1] = '1';
        cout << "? " << t << endl;
        int val; cin >> val;
        if (val - la != v[i].first) ;
        else la = val, s = t, sum += v[i].first;
    }

    cout << "! " << la << endl;

    return 0;
}

题解:CF1692H Gambling

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-08-15 17:27:02

显然的,对于一个 $a_i$,我们把等于 $a_i$ 的设为 $1$,其他设为 $-1$,那么就转化成了求最大字段和。

考虑顺着枚举 $a_i$,对于当前 $i$ 暴力往后扫求以 $i$ 开始的极长区间 $[i,j]$,使得对于所有区间 $[i,k]$ 其中 $(i \le k \le j)$ 满足这个区间的和 $\ge 0$。

那么对于这个区间 $[i,j]$ 中所有的 $=a_i$ 的 $k$ 打上标记,下一次若遇到标记就不用再扫一遍了。

时间复杂度约为 $O(n \sqrt n)$,因为对于个数 $\le \sqrt n$ 的数字,每个的贡献是 $O(\sqrt n)$。而对于个数 $> \sqrt n$ 的数字,最多也就 $O(\sqrt n)$,所以这部分也就 $O(n \sqrt n)$,所以总时间复杂度为 $O(n \sqrt n)$。

#include <bits\/stdc++.h>

using namespace std;

int T;
int n;
int a[200010];

int main() {
    scanf("%d", &T);
    for (int Tc = 1; Tc <= T; ++Tc) {
        unordered_map<int, int> st;

        scanf("%d", &n);
        for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);

        int ans = 0, v, l, r;
        for (int i = 1; i <= n; ++i) {
            if (st.count(i)) continue;

            int s = 0;
            for (int j = i; j <= n; ++j) {
                if (a[j] == a[i]) ++s, st[j] = 1;
                else --s;

                if (s > ans) {
                    ans = s;
                    v = a[i];
                    l = i, r = j;
                }
                if (s < 0) break;
            }
        }

        printf("%d %d %d\n", v, l, r);
    }
    return 0;
}

题解:CF1725H Hot Black Hot White

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-08-16 19:37:34

考虑将 $a$ 和 $b$ 拼起来其实就是 $a \times 10^k + b$,$k$ 是多少不重要,因为在模 $3$ 的意义下,$10^k$ 恒等于 $1$。

所以原式就变成了 $(a_i + a_j)^2 + a_i \times a_j$。

然后由于在模 $3$ 意义下,$a_i$ 只有 $3$ 钟取值。

于是把他们一一列举出来:

$0$ $0$ $0$
$0$ $1$ $1$
$0$ $2$ $1$
1 $1$ $2$
$1$ $2$ $2$
$2$ $2$ $2$

我们发现对于 $Z=0$,那么只要保证所有的 $0$ 都在一组内即可。

设 $cnt_i$ 表示 $i$ 的出现次数。

那么只要 $cnt_0 \le \frac n2$ 即可。

否则对于 $Z=2$,只要让 $1,2$ 在同一组即可。

也就是 $cnt_1 + cnt_2 \le \frac n2$。

又由于 $cnt_0 + cnt_1 + cnt_2 = n$,所以上面那两个式子至少有一个成立,所以不会无解。

#include <bits\/stdc++.h>

using namespace std;

int n;
int a[100010];
bool st[100010];

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
    int cnt = 0;
    for (int i = 1; i <= n; ++i) {
        if (a[i] % 3 == 0)
            st[i] = true, ++cnt;
    }
    
    if (cnt > n \/ 2) {
        memset(st, false, sizeof(st));
        cnt = 0;

        for (int i = 1; i <= n; ++i)
            if (a[i] % 3 == 1 || a[i] % 3 == 2) {
                ++cnt;
                st[i] = true;
            }

        for (int i = 1; i <= n; ++i) if (!st[i] && cnt < n \/ 2) {
            ++cnt;
            st[i] = true;
        }
    
        puts("2");
        for (int i = 1; i <= n; ++i) printf("%d", st[i]);
        return 0;
    }

    for (int i = 1; i <= n; ++i)
        if (!st[i] && cnt < n \/ 2) {
            ++cnt;
            st[i] = true;
        }
    
    puts("0");
    for (int i = 1; i <= n; ++i) printf("%d", st[i]);

    return 0;
}

题解:CF1731D Valiant's New Map

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-08-17 08:01:18

显然的,答案有单调性,可以二分。

然后对于二分的值 $l$,我们把原矩阵所以值 $a_{i,j}$ 变为 $\min(a_{i,j},l)$。

然后就是枚举周长为 $l$ 的正方形的右下角,然后看区间和是不是 $l^3$ 即可。

#include <bits\/stdc++.h>

using namespace std;

int T;
int n, m;
vector<vector<int>> a, s;

bool check(int l) {
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + min(l, a[i][j]);
        }
    }

    for (int i = l; i <= n; ++i) {
        for (int j = l; j <= m; ++j) {
            if (s[i][j] - s[i - l][j] - s[i][j - l] + s[i - l][j - l] >= l * l * l) return true;
        }
    }

    return false;
}

int main() {
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &n, &m);
        a.resize(n + 1);
        s.resize(n + 1);
        for (int i = 0; i <= n; ++i) a[i].resize(m + 1), s[i].resize(m + 1);

        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= m; ++j)
                scanf("%d", &a[i][j]);

        int l = 1, r = min(n, m);
        while (l < r) {
            int mid = (l + r + 1) >> 1;

            if (check(mid)) l = mid;
            else r = mid - 1;
        }

        printf("%d\n", l);
    }

    return 0;
}

题解:CF1971G XOUR

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-08-23 22:17:53

显然的,由于要求 $a_i \oplus a_j < 4$,那么我们对于二进制就不管最后的两位。

那么就让所有的数把后两位去掉,然后排个序,对于此时相同的数可以随便换,所以直接贪心选然后放到原序列中即可。

共 105 篇博客