CSP-S模拟15
A. 网格图
这题肯定要用并查集,然后暴力搞好像是 \(n^4\)
仔细思考一下发现我们其实只需要绕着选定矩形的边跑一遍即可
那么这是 \(n^3\) 可过?
但是你发现无法处理完全包含在矩形中的
于是我有了两种思路,
- 矩形大小减去外侧联通
这玩意根本没法搞
- 统计完全包含的贡献
这个好像可行
进一步思考,发现完全包含的可以看成一个小矩形,他的四界分别是其中横纵坐标的最值
于是包含他的矩形的位置就可以确定下来了,这些位置也是一个矩形,于是可以维护二维差分
由于我比较菜,所以实现比较复杂,尤其是绕着矩形边跑那里,前两天做了个可撤销并查集,然后就写了一个。。。。
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}\)
考虑优化这个过程,考场上我有两种思路
- 考虑 \(dp\) 出一条向上的链, 在 \(lca\) 处拼接
但是这样做的话比较麻烦,需要记录的状态比较多,合并时比较难搞
于是有了另一种思路
- 换根 \(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;
}