CSP-S模拟15

A. 网格图

这题肯定要用并查集,然后暴力搞好像是 \(n^4\)

仔细思考一下发现我们其实只需要绕着选定矩形的边跑一遍即可

那么这是 \(n^3\) 可过?

但是你发现无法处理完全包含在矩形中的

于是我有了两种思路,

  1. 矩形大小减去外侧联通

这玩意根本没法搞

  1. 统计完全包含的贡献

这个好像可行

进一步思考,发现完全包含的可以看成一个小矩形,他的四界分别是其中横纵坐标的最值

于是包含他的矩形的位置就可以确定下来了,这些位置也是一个矩形,于是可以维护二维差分

由于我比较菜,所以实现比较复杂,尤其是绕着矩形边跑那里,前两天做了个可撤销并查集,然后就写了一个。。。。

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

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
inline int read(){
    int x = 0; char c = getchar();
    while(c < '0' || c > '9')c = getchar();
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return x;   
}
const int maxn = 505;
int n, k;
char c[maxn][maxn];
int f[maxn * maxn], size[maxn * maxn], l[maxn * maxn], r[maxn * maxn], u[maxn * maxn], d[maxn * maxn];
int z[maxn][maxn], cf[maxn][maxn];
int id(int x, int y){return (x - 1) * n + y;}
int fa(int x){return f[x] == x ? x : fa(f[x]);}
void merge1(int x, int y){
	x = fa(x), y = fa(y);
	if(x == y)return;
	if(size[x] < size[y])swap(x, y);
	size[x] += size[y];
	l[x] = min(l[x], l[y]);
	r[x] = max(r[x], r[y]);
	u[x] = min(u[x], u[y]);
	d[x] = max(d[x], d[y]);
	f[y] = x;
}
bool vis[maxn * maxn];
vector<int>v;
void meg(int x, int y){
	x = fa(x), y = fa(y);
	if(x == y)return;
	if(size[x] < size[y])swap(x, y);
	size[x] += size[y];
	f[y] = x; v.push_back(y);
}
int solve(int i, int j){
	int ans = 0;
	ans += cf[i][j];
	int ii = i + k - 1, jj = j + k - 1;
	ans += z[ii][jj] - z[ii][j - 1] - z[i - 1][jj] + z[i - 1][j - 1];
	v.clear();
	int nid = n * n + 2;
	f[nid] = nid;
	if(i > 1){
		for(int nj = j; nj <= jj; ++nj)meg(id(i - 1, nj), nid);
	}
	if(ii < n){
		for(int nj = j; nj <= jj; ++nj)meg(id(ii + 1, nj), nid);
	}
	if(j > 1){
		for(int ni = i; ni <= ii; ++ni)meg(id(ni, j - 1), nid);
	}
	if(jj < n){
		for(int ni = i; ni <= ii; ++ni)meg(id(ni, jj + 1), nid);
	}
	nid = fa(nid);
	ans += size[nid];
	int s = v.size();
	for(int del = s - 1; del >= 0; --del){
		int now = v[del];
		size[f[now]] -= size[now];
		f[now] = now;
	}
	return ans;
}

int main(){
	scanf("%d%d",&n,&k);
	for(int i = 1; i <= n; ++i)scanf("%s", c[i] + 1);
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= n; ++j){
			if(c[i][j] == 'X')continue;
			int now = id(i, j);
			f[now] = now; size[now] = 1; l[now] = r[now] = i; u[now] = d[now] = j;
			if(i > 1 && c[i - 1][j] == '.')merge1(id(i - 1, j), now);
			if(j > 1 && c[i][j - 1] == '.')merge1(id(i, j - 1), now);
		}
	for(int i = 1; i <= n; ++i){
		for(int j = 1; j <= n; ++j){
			if(c[i][j] == 'X')continue;
			int now = fa(id(i, j));
			if(vis[now])continue;
			vis[now] = 1;
			if(r[now] - l[now] + 1 > k || d[now] - u[now] + 1 > k)continue;
			int l1 = max(1, r[now] - k + 1), l2 = l[now];
			int l3 = max(1, d[now] - k + 1), l4 = u[now];
			cf[l1][l3] += size[now];
			cf[l1][l4 + 1] -= size[now];
			cf[l2 + 1][l3] -= size[now];
			cf[l2 + 1][l4 + 1] += size[now];
		}
	}
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= n; ++j)
			if(c[i][j] == 'X')z[i][j] = 1;
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= n; ++j)
			z[i][j] = z[i][j] + z[i - 1][j] + z[i][j - 1] - z[i - 1][j - 1];
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= n; ++j)
			cf[i][j] = cf[i][j] + cf[i - 1][j] + cf[i][j - 1] - cf[i - 1][j - 1];
	int ans = 0;
	for(int i = 1; i <= n - k + 1; ++i)
		for(int j = 1; j <= n - k + 1; ++j)
			ans = max(ans, solve(i, j));
	printf("%d\n",ans);
    return 0;
}

B. 保险箱

首先,如果一个数 \(x\) 合法, 那么 \(ax - bn\) 一定合法

根据斐蜀定理可知我们能表示的最小数为 \(\gcd(x, n)\)

那么所有读入的数都可以缩小为 \(\gcd(m_i, n)\)

我们找的答案也可以转化为 \(n / p\) 的形式

因为 \(m_k\) 合法,所以我们要找的 \(p\) 一定是 \(m_k\) 的因子

因为 \(m_i (i < k)\) 不合法,所以 \(p\) 一定不是 \(m_i\) 的因子

于是我们就有了一个暴力,枚举 \(m_k\) 的因子,然后去 \(check\)

简单优化一下,就是令 \(m_i = \gcd(m_i, m_k)\) 因为我们只关注 \(m_k\) 的因子,这样显然不影响答案

这样仍然不能通过本题(题库数据水可以过),所以我们考虑换一种思路

注意到 \(m_k\) 的质因子不是很多,于是我们考虑用 \(m_i\) 去标记非法的因子

具体做法就是记忆化一下,对于每个非法因子,去掉一个 \(m_k\) 的质因子后继续递归下去标记,用 \(map\) 维护标记即可

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

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
inline ll read(){
    ll x = 0; char c = getchar();
    while(c < '0' || c > '9')c = getchar();
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return x;   
}
const ll maxn = 250005;
ll n, k, a[maxn];
vector<ll>p;
int main(){
	n = read(), k = read();
	if(k == 1){
		printf("%lld\n",n);
		return 0;
	}
	for(int i = 1; i <= k; ++i)a[i] = read();
	for(int i = 1; i <= k; ++i)a[i] = __gcd(a[i], n);
	for(int i = 1; i < k; ++i)a[i] = __gcd(a[i], a[k]);
	sort(a + 1, a + k);
	int cnt = unique(a + 1, a + k) - a - 1;
	ll mi = 0;
	for(ll i = 2; i * i <= a[k]; ++i){
		if(a[k] % i)continue;
		p.push_back(i);
		p.push_back(a[k] / i);
	}
	p.push_back(a[k]);
	sort(p.begin(), p.end());
	for(ll i : p){
		if(a[k] % i)continue;
		bool fl = 1;
		for(int j = 1; j <= cnt; ++j){
			if(a[j] % i == 0){
				fl = 0;
				break;
			}
		}
		if(fl){
			mi = i; break;
		}
	}
	printf("%lld\n",n / mi);
	return 0;
}
ac_code
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
inline ll read(){
    ll x = 0; char c = getchar();
    while(c < '0' || c > '9')c = getchar();
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return x;   
}
const ll maxn = 250005;
ll n, k, a[maxn];
int p[1005], pr;
int cnt;
unordered_map<ll, bool>mp;
void dfs(ll now){
	if(mp[now])return;
	mp[now] = 1;
	for(int i = 1; i <= pr; ++i)if(now % p[i] == 0)dfs(now / p[i]);
}
int main(){
	n = read(), k = read();
	if(k == 1){
		printf("%lld\n",n);
		return 0;
	}
	for(int i = 1; i <= k; ++i)a[i] = read();
	a[k] = __gcd(a[k], n);
	for(int i = 1; i < k; ++i)a[i] = __gcd(a[i], a[k]);
	sort(a + 1, a + k);  cnt = unique(a + 1, a + k) - a - 1;
	ll now = a[k];
	for(ll i = 2; i * i <= now; ++i)if(now % i == 0){
		p[++pr] = i; 
		while(now % i == 0)now /= i;
	}
	if(now > 1)p[++pr] = now;
	
	for(int i = 1; i <= cnt; ++i)dfs(a[i]);
	ll i = 1;
	for(i = 1; i * i <= a[k]; ++i)if(a[k] % i == 0 && !mp[i]){
		printf("%lld\n", n / i);
		return 0;
	}
	for(--i; i; i--)if(a[k] % i == 0 && !mp[a[k] / i]){
		printf("%lld\n", n / (a[k] / i));
		return 0;
	}
	return 0;
}

C. 追逐

比较经典的树形 \(DP\)

如果我们确定了起点,那么每个点被选中他的贡献就是 \(\sum_{v \in son} f_v\)

于是我们就有了 \(n^2\) 爆力

\(dp_{i, j, 1 / 0}\) 表示以 \(i\) 为根的子树中的链,使用了 \(j\) 个磁铁, \(i\) 放 / 没放磁铁

转移的话对其子树的贡献取 \(max\) 贡献到 \(dp_{i, j, 0}\)

\(dp_{i, j , 0}\) 贡献到 \(dp_{i, j + 1, 1}\)

考虑优化这个过程,考场上我有两种思路

  1. 考虑 \(dp\) 出一条向上的链, 在 \(lca\) 处拼接

但是这样做的话比较麻烦,需要记录的状态比较多,合并时比较难搞

于是有了另一种思路

  1. 换根 \(dp\)

这个就比较好搞了

换根时只会影响到 \(x , fa_x\)

考虑在 \(fa_x\) 位置修改他的 \(dp\) 值,使他作为 \(x\) 的子树进行贡献

这个其实比较套路,因为发现其实就是扣掉 \(x\) 作为他的儿子的贡献,那么我们对转移过程维护一个最大值, 一个与最大值不同子树的次大值,那么只要不取到最大值来源的子树, 其 \(dp\) 值仍然为最大值,否则为次大值

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

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
inline int read(){
    int x = 0; char c = getchar();
    while(c < '0' || c > '9')c = getchar();
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return x;   
}
const int maxn = 1e5 + 55;
int n, sum, f[maxn], head[maxn], tot, root;
struct edge{
	int to, net;
}e[maxn << 1 | 1];
void add(int u, int v){
	e[++tot].net = head[u];
	head[u] = tot;
	e[tot].to = v;
}
ll ans;
ll tmp[maxn][105][2], g[maxn];
void solve(int x, int fa){
	g[x] = 0;
	for(int i = 1; i <= sum; ++i)tmp[x][i][0] = 0;
	for(int i = head[x]; i; i = e[i].net){
		int v = e[i].to;
		if(v == fa)continue;
		g[x] += f[v];
		solve(v, x);
		for(int j = 1; j <= sum; ++j)tmp[x][j][0] = max(tmp[x][j][0], max(tmp[v][j][1], tmp[v][j][0]));
	}
	for(int i = sum; i >= 1; --i)tmp[x][i][1] = tmp[x][i - 1][0] + g[x];
}
int rmx[maxn][105];
ll  rval[maxn][105], rcm[maxn][105];
void ch(int x, int fa){
	for(int j = 1; j <= sum; ++j)tmp[x][j][0] = max(tmp[x][j][0] ,max(tmp[fa][j][0], tmp[fa][j][1]));
	for(int j = 1; j <= sum; ++j)tmp[x][j][1] = tmp[x][j - 1][0] + g[x] + f[fa];
	ans = max(ans, max(tmp[x][sum][0], tmp[x][sum][1]));
	for(int j = 1; j <= sum; ++j)tmp[x][j][0] = max(tmp[fa][j][0], tmp[fa][j][1]);
	for(int j = head[x]; j; j = e[j].net){
		int v = e[j].to;
		for(int k = 1; k <= sum; ++k){
			ll val = 0;
			if(v == fa)val = max(tmp[fa][k][0], tmp[fa][k][1]);
			else val = max(tmp[v][k][0], tmp[v][k][1]);
			if(val > rval[x][k]){
				rcm[x][k] = rval[x][k];
				rval[x][k] = val;
				rmx[x][k] = v;
			}else rcm[x][k] = max(rcm[x][k], val);
		}
	}
	for(int i = head[x]; i; i = e[i].net){
		int v = e[i].to;
		if(v == fa)continue;
		for(int j = 1; j <= sum; ++j)tmp[x][j][0] = rmx[x][j] == v ? rcm[x][j] : rval[x][j];
		for(int j = 1; j <= sum; ++j)tmp[x][j][1] = tmp[x][j - 1][0] + g[x] - f[v] + f[fa];
		ch(v, x);	
	}
}
int main(){
	n = read(), sum = read();
	for(int i = 1; i <= n; ++i)f[i] = read();
	for(int i = 1; i < n; ++i){
		int u = read(), v = read();
		add(u, v); add(v, u);
	}
	if(n == 1 || sum == 0){
		printf("0\n");
		return 0;
	}
	if(n == 2){
		printf("%d\n",max(f[1], f[2]));
		return 0;
	}
	solve(1, 0);
	ch(1, 0);
	printf("%lld\n",ans);
    return 0;
}

D. 字符串

\(c\) 看做 \(1\)

\(t\) 看做 \(-1\)

那么我们就可以对题意进行转化,变成对 \([l,r]\) 区间,把某些 \(-1\) 变成 \(0\)

使得所有前缀和后缀和都 \(>=0\)

那么有一个比较显然的贪心就是先从左向右扫,每到前缀和为 \(-1\) 就把该位置改为 \(0\)

然后再倒着扫一遍

考虑简化这个过程,发现我们在从左向右扫的过程中操作次数为 \(-min(pre_i)\)

操作位置为 \(pre_i\) 第一次取到 \(-1, -2 ...\) 时的位置

类似的,对从右往左扫的操作次数也是 \(min(suf`_i)\)

根据上面的性质我们可以得到 \(suf`_i = suf_i + min(pre_j) (j < i )\)

那么答案就是 \(max(-pre_i - suf'i )\)

也就是 \(- min_{i < j}(pre_i + suf_j)\)

那么这个其实是区间总和减去区间最大子段和,用线段树可以简单维护

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

using namespace std;

typedef long long ll;
typedef unsigned long long ull;
inline int read(){
    int x = 0; char c = getchar();
    while(c < '0' || c > '9')c = getchar();
	do{x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}while(c <= '9' && c >= '0');
	return x;   
}
const int maxn = 500005;
int n; char c[maxn];

struct node{
	int pre, suf, mx, sum;
}t[maxn << 2 | 1];
struct tree{
	void push_up(int x){
		int ls = x << 1, rs = x << 1 | 1;
		t[x].sum = t[ls].sum + t[rs].sum;
		t[x].pre = max(t[ls].pre, t[ls].sum + t[rs].pre);
		t[x].suf = max(t[rs].suf, t[rs].sum + t[ls].suf);
		t[x].mx = max(max(t[x].pre, t[x].suf), max(t[ls].mx, t[rs].mx));
		t[x].mx = max(t[x].mx, t[ls].suf + t[rs].pre);
	}
	void built(int x, int l, int r){
		if(l == r){
			t[x].pre = t[x].suf = t[x].sum = t[x].mx = (c[l] == 'C') ? 1 : -1; 
			if(t[x].mx < 0)t[x].mx = 0;
			return;
		}
		int mid = (l + r) >> 1;
		built(x << 1, l, mid);
		built(x << 1 | 1, mid + 1, r);
		push_up(x);
	}
	node query(int x, int l, int r, int L, int R){
		if(L <= l && r <= R)return t[x];
		int mid = (l + r) >> 1;
		if(L <= mid && R > mid){
			node p = query(x << 1, l, mid, L, R);
			node s = query(x << 1 | 1, mid + 1, r, L, R);
			node ans;
			ans.sum = p.sum + s.sum;
			ans.pre = max(p.pre, p.sum + s.pre);
			ans.suf = max(s.suf, s.sum + p.suf);
			ans.mx = max(max(ans.pre, ans.suf), max(p.mx, s.mx));
			ans.mx = max(ans.mx, p.suf + s.pre);
			return ans;
		}
		if(L <= mid)return query(x << 1, l, mid, L, R);
		return query(x << 1 | 1, mid + 1, r, L, R);
	}
}T;
int main(){
	n = read();
	scanf("%s", c + 1);
	T.built(1, 1, n);
	int q = read();
	for(int ask = 1; ask <= q; ++ask){
		int l = read(), r = read();
		node ans = T.query(1, 1, n, l, r);
		printf("%d\n",ans.mx - ans.sum);
	}
    return 0;
}
posted @ 2022-09-30 20:50  Chen_jr  阅读(42)  评论(1编辑  收藏  举报