省选模拟测试18
\(T1\) 题目太长,懒得看了。
\(T2\) 只会打 \(O(n^2)\) 的暴力 \(dp\),正解好像要用到笛卡尔树上二分,没学过溜了溜了。
\(T3\) 不太会线性基的删除操作,就只能打每次都重构的线性基,好像加上 \(bitset\) 就过了。
T1 集合比较
题意描述
形式上,我们对一类大小为 \(2\) 的有序集合定义偏序关系。这里的每一个集合 \(S\) 都是一个包含两个集合的有序可重集合,记其包含的两个集合依次分别为 \(S_1,S_2\)。我们定义小于和等于判则如下:
- 集合 \(A<B\) 当且仅当以下两个条件之一成立:
- \(A_1<B_1\)
- \(A_1=B_1\) 且 \(A_2<B_2\)
- 集合 \(A=B\) 当且仅当 \(A_1=B_1\) 且 \(A_2=B_2\) 。
需要注意的是,这里的判则是递归定义的。
我们再定义两个初始集合 \(S\) 和 \(T\):
- \(S\) 形式上表示唯一的最小的集合;
- \(T\) 形式上表示唯一的最大的集合;
- 并且 \(S_1=S_2=S\),\(T_1=T_2=T\),且 \(S\neq T\)。
容易验证在由 \(S\) 和 \(T\) 生成的集合类上,\(\leq\) 构成偏序关系。在这一题里,我们需要你做的就是模拟这个集合类内的比较操作。
具体地说,一开始我们有初始集合 \(S\) 和 \(T\),每次我们通过将已有的两个集合当作元素按顺序组成一个新的有序集,询问这个新的集合在已有的所有的集合中是第几小的,即已有的所有集合中所有小于或等于该集合的集合个数。
数据范围:\(1\leq n\leq 50000\)
solution
splay/替罪羊树。
考虑用一棵平衡树来维护所有的集合,每个节点记录其包含的两个集合(相同的集合记在同一个节点上)。
每测将新加入集合包含的两个集合和平衡树上节点包含的两个集合的排名进行比较,从而得到新加入的集合与平衡树上节点所代表的集合的大小关系,从而确定新插入的集合的位置和排名。
拿平衡树维护一下就好了。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 9;
int ch[N][2],fa[N],rt,id[N];
struct TRee {int le,ri,siz,cnt;} tr[N];
inline int read()
{
int x = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -1);
for(x = ch ^ 48 ; isdigit(ch = getchar()) ; x = (x << 3) + (x << 1) + (ch ^ 48));
return x * f;
}
#define ls(o) ch[o][0]
#define rs(o) ch[o][1]
void up(int o){tr[o].siz = tr[ls(o)].siz + tr[rs(o)].siz + tr[o].cnt;}
bool isr(int x)
{
return ch[fa[x]][1] == x;
}
void rotate(int x)
{
int y = fa[x], z = fa[y];
int k = isr(x), w = ch[x][!k];
if(y != rt) ch[z][isr(y)] = x; else rt = x;
ch[x][!k] = y, ch[y][k] = w;
fa[x] = z, fa[y] = x;
if(w) fa[w] = y;
up(y), up(x);
}
void splay(int x)
{
while(x != rt)
{
if(fa[x] != rt) rotate(isr(fa[x]) == isr(x) ? fa[x] : x);
rotate(x);
}
}
int ronk(int x)
{
int res = 0, op = 1;
while(x)
{
if(op) res += tr[ls(x)].siz + tr[x].cnt;
op = isr(x), x = fa[x];
}
return res;
}
int cmp(int a,int b)
{
int ra = ronk(tr[a].le), rb = ronk(tr[b].le);
if(rb > ra) return 1;
if(ra > rb) return 0;
ra = ronk(tr[a].ri), rb = ronk(tr[b].ri);
if(rb > ra) return 1;
if(ra > rb) return 0;
return -1;
}
void insert(int x)
{
int o = rt, las = o, k;
while(o)
{
las = o;
k = cmp(o,x);
if(k == -1) {tr[o].cnt ++, id[x] = o, splay(o); return ;}
o = ch[o][k];
}
id[x] = x, tr[x].siz = tr[x].cnt = 1, fa[x] = las, ch[las][k] = x;
splay(x);
}
int main(){
freopen("comparison.in","r",stdin);
freopen("comparison.out","w",stdout);
int n = read();
ch[n + 2][1] = n + 1, fa[n + 1] = n + 2; rt = n + 2;
tr[n + 2].le = tr[n + 2].ri = n + 2, tr[n + 1].le = tr[n + 1].ri = n + 1;
tr[n + 2].siz = tr[n + 2].cnt = tr[n + 1].siz = tr[n + 1].cnt = 1;
up(n + 1), up(n + 2); id[0] = n + 2, id[n + 1] = n + 1;
for(int i = 1 ; i <= n ; i++)
{
tr[i].le = id[read()], tr[i].ri = id[read()];
insert(i);
cout<<ronk(id[i])<<"\n";
}
return 0;
}
T2 最优分组
题意描述
有 \(n\) 个人按照从 \(1\) 到 \(n\) 的编号顺序排成一排,你希望把他们分成若干组,每一组的人编号都连续。但编号为 \(i\) 的人要求自己所在的组人数在 \([c_i,d_i]\) 之间。
你希望知道在满足所有人的要求下能够分出最多的组数,还想知道在分出组数最多的前提下有多少种分组方案。
数据范围:\(1\leq n\leq 10^6,1\leq c_i,d_i\leq n\)
solution
笛卡尔树优化 \(dp\) 。
这题太神仙了,还是看题解吧。
先考虑如何求最大分组数。设 \(f(i)\) 为 \(1\) 到 \(𝑖\) 的最大的分组数。
转移显然是考虑以 \(i\) 结尾的最后一段划分在哪。
如果只有 \(𝑑_i\) 的限制,那么能从 \(f(j)\) 转移过来(即最后一段划分在 \([j+1,i]\) 当且仅当 \(\forall j+1\leq k\leq i, d_k\geq i-j+1\) 。即 \(\displaystyle \min_{j+1\leq k\leq i} \{d_k\} \geq i-j+1\) 。
注意到 \(𝑗\) 减小的时候,\(\min_{j+1\leq k\leq i} \{d_k\}\) 不上升,\(i-j+1\) 增大,所以一定能找到一个最小的 \(left_i\) 使得 \(\forall left_i<j\leq i, d_j\geq i-left_i+1\) 那么 \(𝑖\) 的状态只能从 \(j\in[left_i,i)\) 的状态转移过来。显然 \(i<i'\Leftrightarrow left_i < left_{i'}\),所以可直接用一个 \(𝑑\) 递增的单调队列维护。
再考虑 \(c_i\),显然每组的人数只会被该分组中最大的 \(𝑐_i\)_ 所限制,我们可以按照当前区间中最大的 \(c_i\) 来分治求解。
注意到这就相当于在笛卡尔树上求解,即每次找到当前区间 \([l,r]\) 中 \(c_i\) 最大的位置 \(𝑚𝑖𝑑\),然后先递归求解区间 \([𝑙,𝑚𝑖𝑑)\) 的 \(𝑓\) 值,接着用 \([𝑙,𝑚𝑖𝑑)\) 的 \(𝑓\) 值转移到 \([𝑚𝑖𝑑,𝑟]\),那么划分的这段区间一定包含了 \(c_{mid}\) ,\(𝑐_{mid}\) 就是划分区间里最大的 𝑐 值。处理完之后再处理 \([mid,r]\)。
记函数 \(𝑠𝑜𝑙𝑣𝑒(l,r)\) 表示处理到区间 \([l,r]\),且此时 \([0,l)\) 的 \(𝑓\) 对这个区间的转移都已经处理了。那么 \(𝑓(l)\) 就已知,需要求编号 \(l+1\) 到 \(r\) 的 \(f(i)\), (显然初始函数为 \(slove(0,n)\))。
记 \([l,r]\) 中 \(c_i\) 最大的人编号为 \(𝑚𝑖𝑑\),那么我们需要做的就是:
- 若 \(𝑙 =r\),直接在线段树上查询最终的 \(𝑓(l)\) 即可(下文会用到线段树)
- 通过 \(𝑠𝑜𝑙𝑣𝑒(l,mid-1)\) 确定 \([l,mid-1]\) 的状态。
- 用 \([l,mid-1]\) 的状态更新 \([𝑚𝑖𝑑,𝑟]\)。
- 通过 \(solve(mid,r)\) 确定 \([mid,r]\) 的状态。
现在的问题只剩下如何用 \([l,mid-1]\) 的状态更新 \([mid,r]\)。显然只有\(𝑖 \in[\max(mid,l+c_mid),r]\) 能被 \(𝑗\in[l,mid-1]\) 更新。我们来讨论 \(𝑖\in[\max(mid,l+c_mid),r]\) 该如何被更新(更新过程中,我们用线段树来维护 \(f(i)\) 值)。
-
\(left_i< l 并且 i < mid + c _{mid}\):
满足条件的 \(𝑖\) 一定连续(在区间 \([\max(mid,l+c_mid),\min(r,mid+c_mid-1)]\) 中,且要满足 \(left_i<l\),并且能够更新相邻两个 \(𝑖\) 的 \(𝑗\) 区间满足区间左端点相同,区间长度相差 \(1\)(\(j\leq i-c_{mid}\))。
由于 \(𝑓(l...mid-1)\) 已经被确定,我们只需用 \(O(logn)\) 的复杂度求能够更新第一个 \(𝑖\) 的最大的 \(f_m = f(j)_{max}\) ,并且扫一遍其余的 \(𝑖\),\(O(1)\) 修改 \(f_m\) (因为 \(𝑖\) 增加 \(1\) 的时候最多增加一个可选择的 \(𝑗\))并更新 \(f(i)\) 。
不难发现,这一步的时间复杂度是 \(O(\min(r-mid,mid-l))\) ,那么处理区间 \([𝑙,𝑟]\) 以及子区间的时间复杂度就是 \(T[l,r] = T[l,mid-1] + T[mid,r] + O(\min(r-mid,mid-i))\),这相当于一个启发式合并 启发式合并的过程,总时间复杂度 \(𝑂(nlogn)\)。
-
\(left_i<l且 mid+c_{mid}\leq i\):
满足条件的𝑖也一定连续,并且能够更新这些 \(𝑓(i)\) 的区间都是 \([l,mid-1]\),只需用二分在 \(O(log[r-(mid+c_{mid})])\) 内找到 \(𝑖\) 最大值,并在 \(O(logn)\) 内实现区间询问、区间修改操作即可。该情况的复杂度为 \(O(nlogn)\)。
-
\(l\leq left_i<mid\):
这部分 \(𝑖\) 只能 \(O(logn)\)暴力查询能更新\(𝑓(i)\) 的 \(𝑗\) 区间,由于每个 \(𝑖\) 在整个过程中至多被这样更新一次,所以该情况下的复杂度同样为 \(O(nlogn)\)。
-
\(mid\leq left_i\):显然 \(𝑖\) 不会被 \([l,mid-1]\) 所更新。
对于在最大分组数的前提下的分组方案数,可以将 \(𝑓(i)\) 改为一个二元组,更新最大分组数时同步更改方案数即可(因为我们把每个合法决策点都考虑了。所以转移的时候,最大值相同就累加方案数即可)。
//咕咕咕
T3 最大的割
题意描述
考虑一张 \(n\) 个点的边带权无向图,点从 \(1\sim n\) 编号。对于图中的任意一个点集(可以为空集或是全集),称所有那些恰好有一个端点在这个点集中的边所组成的边集为割。我们再定义一个割的权值为:这个割中所含的所有边边权的异或和。
现在初始时给定一张 \(n\) 个点的空图,接下来会有若干次加(无向)边操作,每次加边后请你求出当前图中权值最大的割的权值。
数据范围:\(1\leq n\leq 500,1\leq m\leq 1000,0\leq L\leq 1000,1\leq x,y\leq n\)
solution
带删除线性基/线段树分治维护线性基。
设 \(w(i)\) 表示与 \(i\) 相连的边的异或和。
不难发现,如果我们选了一条边的两个端点,那么两个端点的 \(w(i)\) 异或起来就消去了这条边的·贡献。
然后问题就转化为了每次将两个点的点权异或上一个数,在询问点集的异或最大值。
看到异或最大值,不难想到用线性基求解。
对于线性基的修改操作比较麻烦,所以用线段树分治优化一下即可。
具体来说就是把相同的点权插入到以时间为下标的线段树当中,当我们访问到区间 \([l,r]\) 的时候,把这个节点的点权插入到线性基中,在递归儿子节点,回溯的时候把线性基回溯到访问这个节点之前的状态。
最后叶子节点对应的线性基就是每一时刻的线性基。
复杂度:\(O({mLlogn\over w} + {mL^2\over w})\) 。
update:这个题好像暴力重构在套上 \(\text{bitset}\) 也可以过。
暴力重构代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<bitset>
using namespace std;
const int N = 1010;
int n,m,u,v,k,maxn;
int p[N],Q[N];
char s[N];
bool vis[N];
bitset<1010>sum[N],d[N],ans,t;
inline int read()
{
int s = 0,w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
return s * w;
}
void insert(int id)
{
t = sum[id];
for(int i = 1000; i >= 0; i--)
{
if(t[i] == 1)
{
if(!p[i])
{
d[i] = t;
p[i] = 1;
break;
}
else t ^= d[i];
}
}
}
void get()
{
for(int i = 1000; i >= 0; i--)
{
if(p[i])
{
bool flag = 0;
for(int j = 1000; j >= 0; j--)
{
if((ans[j]^d[i][j]) > ans[j]){flag = 1; break;}
if((ans[j]^d[i][j]) < ans[j]){flag = 0; break;}
}
if(flag) ans ^= d[i];
}
}
int k = maxn;
while(k >= 0 && ans[k] == 0) k--;
if(k == -1) printf("%d",0);
else for(int i = k; i >= 0; i--) cout<<ans[i];
printf("\n");
}
void QK()
{
for(int i = 0; i <= 1000; i++) p[i] = 0;
ans.reset();
}
int main()
{
freopen("cut.in","r",stdin);
freopen("cut.out","w",stdout);
n = read(); m = read();
for(int i = 1; i <= m; i++)
{
u = read(); v = read(); scanf("%s",s); QK();
k = strlen(s); reverse(s,s+k); maxn = max(maxn,k);
for(int j = 0; j < k; j++) if(s[j] == '1') sum[u][j].flip(), sum[v][j].flip();
vis[u] = 1; vis[v] = 1;
for(int j = 1; j <= n; j++) if(vis[j]) insert(j);
get();
}
fclose(stdin); fclose(stdout);
return 0;
}
线段树分治代码:
#include <bits/stdc++.h>
using namespace std;
int read() {
int x = 0, f = 1;
char ch;
while (!isdigit(ch = getchar())) (ch == '-') && (f = -1);
for (x = ch ^ 48; isdigit(ch = getchar()); x = (x << 3) + (x << 1) + (ch ^ 48))
;
return x * f;
}
const int N = 5e3 + 9, R = 1000;
#define Bit bitset<1001>
#define ls(o) (o << 1)
#define rs(o) (o << 1 | 1)
#define mid ((l + r) >> 1)
vector<Bit> vec[N];
Bit w[N], d[N], ans;
int las[N], vis[N], sta[N], top;
char s[N];
void modi(int o, int l, int r, int x, int y, int p) {
if (l >= x && r <= y)
return (void)(vec[o].push_back(w[p]));
if (x <= mid)
modi(ls(o), l, mid, x, y, p);
if (y > mid)
modi(rs(o), mid + 1, r, x, y, p);
}
void add(Bit a) {
for (int i = R; i >= 0; i--) {
if (a[i] == 0)
continue;
if (vis[i])
a ^= d[i];
else {
sta[++top] = i;
d[i] = a, vis[i] = 1;
break;
}
}
}
void get() {
ans.reset();
for (int i = R; i >= 0; i--) {
if (vis[i] && !ans[i])
ans ^= d[i];
}
int tmp = 0;
for (int i = 0; i <= R; i++)
if (ans[i])
tmp = i;
for (int i = tmp; i >= 0; i--) cout << ans[i];
cout << "\n";
}
void revoke(int now) {
while (top > now) {
d[sta[top]] = 0, vis[sta[top]] = 0;
top--;
}
}
void query(int o, int l, int r) {
int shl = top;
for (int i = 0; i < (int)vec[o].size(); i++) add(vec[o][i]);
if (l == r)
return (void)(get(), revoke(shl));
query(ls(o), l, mid), query(rs(o), mid + 1, r);
revoke(shl);
}
int main() {
freopen("cut.in", "r", stdin);
freopen("cut.out", "w", stdout);
int n = read(), m = read();
for (int i = 1; i <= m; i++) {
int x = read(), y = read();
scanf("%s", s);
int len = strlen(s);
reverse(s + 0, s + len);
if (x == y)
continue;
if (max(las[x], 1) <= i - 1 && las[x] != 0)
modi(1, 1, m, max(las[x], 1), i - 1, x);
if (max(las[y], 1) <= i - 1 && las[y] != 0)
modi(1, 1, m, max(las[y], 1), i - 1, y);
for (int j = 0; j < len; j++) {
if (s[j] == '1')
w[x][j].flip(), w[y][j].flip();
}
las[x] = las[y] = i;
}
for (int i = 1; i <= n; i++)
if (max(las[i], 1) <= m && las[i] != 0)
modi(1, 1, m, max(las[i], 1), m, i);
query(1, 1, m);
return 0;
}

浙公网安备 33010602011771号