LibreOJ β Round #2 题解
LibreOJ β Round #2 题解
模拟只会猜题意
题目:
给定一个长为 \(n\) 的序列,有 \(m\) 次询问,每次问所有长度大于 \(x\) 的区间的元素和的最大值.
\(1 \leq x \leq n \leq 10^4,m \leq 10^5,|A_i| \leq 10^4\)
题解:
因为 \(n \leq 10^4\) ,所以暴力.
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
inline void read(int &x){
x=0;static char ch;static bool flag;flag = false;
while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
}
#define rg register int
#define rep(i,a,b) for(rg i=(a);i<=(b);++i)
#define per(i,a,b) for(rg i=(a);i>=(b);--i)
const int maxn = 10010;
int a[maxn],anss[maxn],s[maxn];
int main(){
int n;read(n);
int m;read(m);
rep(i,1,n){
anss[i] = -0x3f3f3f3f;
read(a[i]);s[i] = s[i-1] + a[i];
}
rep(x,1,n){
int y = -0x3f3f3f3f;
rep(i,1,n-x+1){
y = max(y,s[i+x-1]-s[i-1]);
}anss[x] = y;
}
per(i,n-1,1) anss[i] = max(anss[i],anss[i+1]);
while(m--){
int x;read(x);
printf("%d\n",anss[x]);
}
return 0;
}
贪心只能过样例
题目:
一共有 \(n\) 个数,第 \(i\) 个数 \(x_i\) 可以取 \([a_i,b_i]\) 中任意值。
设 \(S = \sum x_i^2\),求 \(S\) 的种类数.
\(1 \leq n,a_i,b_i \leq 100\)
题解:
想了半天。。。
然后 \(gls\) 说它 \(bitset\) 暴力直接过了。。
然后 。。 QAQ ...
#include <cstdio>
#include <bitset>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
inline void read(int &x){
x=0;static char ch;static bool flag;flag = false;
while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
}
#define rg register int
#define rep(i,a,b) for(rg i=(a);i<=(b);++i)
#define per(i,a,b) for(rg i=(a);i>=(b);--i)
const int maxn = 109;
int n,m,x[maxn],y[maxn];
bitset<1000100> s,t;
int main(){
read(n);s[0] = 1;
rep(i,1,n){
read(x[i]);read(y[i]);t = (s << x[i]*x[i]);
rep(j,x[i]+1,y[i]) t |= (s << j*j);
s = t;
}
int ans = 0;
rep(i,1,1000000) if(t[i]) ++ans;
printf("%d\n",ans);
return 0;
}
DP 一般看规律
题目:
一个序列的值定义为相同元素的下标之差的最小值。现在给定一个长度为 \(n\) 的序列,每次修改会将所有的元素 \(x\) 全部变为元素 \(y\).问每次修改后的序列的值。
\(1 \leq n,m \leq 10^5\),元素大小在int
范围内.
题解:
比较直观的就是对于所有的元素开一个 \(Set\) 记录一下出现的位置。
每次处理操作时就直接暴力启发式合并两个集合.
外部用 \(map\) 维护映射即可.
注意 : \(x = y\)的情况以及记得清空合并余下的 \(Set\).
总复杂度 : \(O(n\log^2n)\)
#include <set>
#include <map>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
inline void read(int &x){
x=0;static char ch;static bool flag;flag = false;
while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
}
#define rg register int
#define rep(i,a,b) for(rg i=(a);i<=(b);++i)
#define per(i,a,b) for(rg i=(a);i>=(b);--i)
const int maxn = 100010;
set<int>S[maxn];
map<int,int>mp;
int main(){
int n,m;read(n);read(m);
int cnt = 0,x;
rep(i,1,n){
read(x);
if(!mp.count(x)) mp[x] = ++ cnt;
S[mp[x]].insert(i);
}
int ans = 2147483647;
rep(i,1,cnt){
if(S[i].empty()) continue;
for(set<int>::iterator it = S[i].begin(),t = (it++);it != S[i].end();++ it,++ t){
ans = min(ans,(*it) - (*t));
}
}
while(m--){
int x,y;read(x);read(y);
if(mp.count(x) && mp.count(y)){
int idx = mp[x],idy = mp[y];
if(S[idx].size() > S[idy].size()){
mp[x] = idy;mp[y] = idx;
}
idx = mp[x],idy = mp[y];
if(idx == idy){printf("%d\n",ans);continue;}
for(set<int>::iterator it = S[idx].begin();it != S[idx].end();++ it){
set<int>::iterator tt = S[idy].insert(*it).first;
if(tt != S[idy].begin()) (--tt),ans = min(ans,*it - *tt),(++tt);
(++tt);
if(tt != S[idy].end()) ans = min(ans,*tt - *it);
}S[idx].clear();
}else if(mp.count(x)){
mp[y] = mp[x];mp.erase(x);
}
printf("%d\n",ans);
}
return 0;
}
计算几何瞎暴力
题目:
给定一个长度为 \(n\) 的数组 \(A\)。下标从 \(1\) 开始。有 \(4\) 种共\(m\) 个操作:
- 在数组 \(A\) 的末尾添加一个数 \(x\).
- 求 \(\sum_{i=l}^rA_i\)
- 将数组 \(A\) 中的每个数 \(A_i \to A_i \oplus x\)
- 将数组 \(A\) 从小到大排序.
\(1 \leq n,m \leq 10^5,0 \leq x,A_i \leq 10^9\)
题解:
我们发现难点就在于第 \(4\) 个操作上。
所以先考虑如果没有第 \(4\) 个操作该怎么做。
我们只需要维护一个支持末尾插入,区间查询,区间异或的数据结构.
因为每次异或都是全局操作,所以我们可以记录一下从开始到当前的所有 \(x\) 的异或和.
设为异或和为 \(X\).那么我们可以对每个新插入的数都先异或上 \(X\).
然后我们发现每次要求的就是\(\sum_{i=l}^rA_i\oplus X\).
对于这个式子我们只要分别处理每个二进制位的前缀和.
然后通过讨论 \(X\) 的这一位是否为 \(1\) 即可以求出上面的式子.
那么我们现在考虑如何处理操作 \(4\).
我们发现其实我们不用真的排序,我们只需要一个支持查询前 \(k\) 小的数据结构.
但是这个数据结构同时还要支持区间异或和动态插入.
我们可以采用 \(Trie\) 来做这个事情。
在每个节点上维护所有经过这个点的数中的各个二进制位出现次数和.
这样我们可以完成前 \(k\) 小的贡献值的值的查询
\((\)根据全局的 \(X\) 考虑每个二进制位累加进 \(ans\) 即可\()\).
那么怎么考虑全局异或操作.
全局异或我们直接交换 \(Trie\) 的特定层数所有节点的左右儿子即可.
事实上不用真的去交换,访问的时候判一下就好了.
注意 :
在 \(Trie\) 上打下的全局异或标记只是改变了相对的大小关系.
所以最终统计值的时候还是要按照 \(X\) 来按位处理的。
总复杂度 \(O(n\log^2n)\)
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
inline void read(int &x){
x=0;static char ch;static bool flag;flag = false;
while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
}
#define rg register int
#define rep(i,a,b) for(rg i=(a);i<=(b);++i)
#define per(i,a,b) for(rg i=(a);i>=(b);--i)
const int maxn = 200010;
const int maxV = 31;
struct Node{
Node *ch[2];
int siz,ws[maxV];
}mem[maxn*maxV],*it,*root,*null;
inline Node* newNode(){
Node *p = it++;p->ch[0] = p->ch[1] = null;
p->siz = 0;return p;
}
inline void init(){
it = mem;null = it++;
null->ch[0] = null->ch[1] = null;
null->siz = 0;root = newNode();
}
int sum[maxn][maxV],a[maxn];
int tot_tag,swap_tag,cnt,snum;
inline void insert(int x){
Node *p = root;++ snum;
per(i,maxV-1,0){
if(p->ch[(x >> i)&1] == null) p->ch[(x >> i)&1] = newNode();
p = p->ch[(x >> i)&1];++ p->siz;
rep(j,0,maxV-1) p->ws[j] += (x >> j) & 1;
}return ;
}
inline void push(int x){
a[++cnt] = x;
rep(i,0,maxV-1) sum[cnt][i] = sum[cnt-1][i] + ((x >> i)&1);
}
inline void rebuild(){
while(cnt) insert(a[cnt--]);swap_tag = tot_tag;
}
inline ll find(int k){
Node *p = root;ll res = 0;
per(i,maxV-1,0){
if(k == 0) break;
if(k < p->ch[(swap_tag >> i)&1]->siz){
p = p->ch[(swap_tag >> i)&1];
}else{
Node *q = p->ch[(swap_tag >> i)&1];k -= q->siz;
rep(j,0,maxV-1){
if((tot_tag >> j)&1) res += (ll)(q->siz - q->ws[j]) << j;
else res += (ll)q->ws[j] << j;
}p = p->ch[(swap_tag >> i)&1^1];
}
}
rep(i,0,maxV-1){
if( ((tot_tag >> i)&1) && p->ws[i] == 0) res += (ll)k << i;
if( ((tot_tag >> i)&1) == 0 && p->ws[i]) res += (ll)k << i;
}return res;
}
inline ll query(int x){
if(x <= snum) return find(x);
ll res = find(snum);x -= snum;
rep(i,0,maxV-1){
if((tot_tag >> i) & 1) res += (ll)(x - sum[x][i]) << i;
else res += (ll)sum[x][i] << i;
}return res;
}
int main(){
init();int n,m,x;read(n);
rep(i,1,n) read(x),push(x);
read(m);int op,y;
while(m--){
read(op);
if(op == 1) read(x),push(x^tot_tag);
else if(op == 2){
read(x);read(y);
printf("%lld\n",query(y) - query(x-1));
}else if(op == 3) read(x),tot_tag ^= x;
else rebuild();
}
return 0;
}
数论只会 GCD
题目:
定义一个序列的权值为不同数字的个数。例如 \([1,2,3,3]\) 的权值为 \(3\)。
现在有 \(n\) 个序列,我们在每个序列里面选一个连续非空子串,拼接起来,求所有选法得到的序列的权值之和。
如果一个序列能通过多种方法被选择出来,那么计算多次。
本题带修改操作,格式请参考输入格式。
由于结果可能过大,请输出答案 \(\bmod\space \space 19260817\) 的结果。
\(1 \leq n,m \leq 100000\),所有的元素在int
范围之内, \(\sum len_i \leq 100000\)
题解:
首先我们考虑如果既没有修改又只有一个序列该怎么做。
我们考虑分别统计每种元素的贡献.
由于无法简单的找出所有包含了某种元素的子段的数目,所以考虑补集转化.
我们定义 :
- \(f(n) = \frac{n(n+1)}{n}\) , 即长度为 \(n\) 的序列的子段个数.
- \(a_i\) 表示该元素第 \(i\) 次出现的位置并假设第一次出现于 \(0\) 最后一次出现于 \(n+1\).
那么对于多重序列的不带修改版我们依然可以套用这个做法。
只需要考虑维护作为减数的 \(\sum\) 即可.
只是考虑每个元素时要在每个序列都做一次。
然后将在每个序列得到的值再计算乘积。
那么对于带修改的?
我们发现每次只修改一个序列的一个值。
就是说只有一种元素在一个序列上的贡献发生了改变。
我们对每一种元素都开一个线段树维护一下该种元素的在每个序列上的贡献值的乘积.
那么每次修改时我们可以开一个 set
来记录一下该种元素的出现位置就可以快速计算出这种元素在这个序列上的贡献改变了多少。
然后在线段树上进行单点修改,区间之积即可。
然后把线段树可持久化掉就不炸空间啦 >_<
总复杂度 \(O(n\log^2n)\)
#include <set>
#include <cstdio>
#include <cstring>
#include <cassert>
#include <algorithm>
using namespace std;
typedef long long ll;
inline void read(int &x){
x=0;static char ch;static bool flag;flag = false;
while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
}
#define rg register int
#define rep(i,a,b) for(rg i=(a);i<=(b);++i)
#define per(i,a,b) for(rg i=(a);i>=(b);--i)
typedef pair<int,int> pa;
const int maxn = 200010;
const int mod = 19260817;
inline int F(int L){return (int)(1LL*L*(L+1)/2LL%mod);}
int S[maxn],T[maxn],cnt,a[maxn];
int tmp[maxn];
struct save{int x,y,z;}c[maxn];
struct Node{
Node *ch[2];
int w;
}*root[maxn],mem[maxn*30],*it,*null;
inline void init(){
it = mem;null = it++;
null->ch[0] = null->ch[1] = null;
null->w = 0;root[0] = null;
}
inline Node* newNode(){
Node *p = it++;
p->ch[0] = p->ch[1] = null;
p->w = 0;return p;
}
Node* build(int l,int r){
Node *p = newNode();
if(l == r){
p->w = F(T[l] - S[l] + 1);
return p;
}int mid = l+r >> 1;
p->ch[0] = build(l,mid);
p->ch[1] = build(mid+1,r);
p->w = 1LL*p->ch[0]->w*p->ch[1]->w % mod;
return p;
}
Node* modify(Node *rt,int l,int r,int pos,int v){
Node *p = it++;*p = *rt;
if(l == r){
p->w += v;
if(p->w >= mod) p->w -= mod;
return p;
}int mid = l+r >> 1;
if(pos <= mid) p->ch[0] = modify(p->ch[0],l,mid,pos,v);
else p->ch[1] = modify(p->ch[1],mid+1,r,pos,v);
p->w = 1LL*p->ch[0]->w*p->ch[1]->w % mod;
return p;
}
set<int>s[maxn];int ans = 0,n;
inline void insert(int x,int y,int v,int f){
set<int>::iterator it;
if(f == 0) it = s[v].find(S[x]+y-1),assert(it != s[v].end());
else it = s[v].insert(S[x]+y-1).first;
int l = 0,r = T[x] - S[x] + 2;
if(it != s[v].begin()){
if(*(--it) >= S[x]) l = *it - S[x] + 1;
++ it;
}
if((++ it) != s[v].end()){
if(*it <= T[x]) r = *it - S[x] + 1;
}-- it;
int val = ((ll)F(y-l-1) + F(r-y-1) - F(r-l-1)) % mod;
if(val < 0) val += mod;
if(f == 0) val = mod - val,s[v].erase(it);
ans += root[v]->w;if(ans >= mod) ans -= mod;
root[v] = modify(root[v],1,n,x,val);
ans -= root[v]->w;if(ans < 0) ans += mod;
return ;
}
int main(){
init();int m,x,y,v;read(n);read(m);
rep(i,1,n){
read(x);S[i] = T[i-1] + 1;
T[i] = S[i] + x - 1;
}
rep(i,1,T[n]) read(a[i]),tmp[++cnt] = a[i];
rep(i,1,m){
read(c[i].x);read(c[i].y);read(c[i].z);
tmp[++cnt] = c[i].z;
}sort(tmp+1,tmp+cnt+1);
cnt = unique(tmp+1,tmp+cnt+1) - tmp - 1;
root[0] = build(1,n);rep(i,1,cnt) root[i] = root[i-1];
rep(i,1,n) rep(j,S[i],T[i]){
a[j] = lower_bound(tmp+1,tmp+cnt+1,a[j]) - tmp;
insert(i,j-S[i]+1,a[j],1);
}printf("%d\n",ans);
rep(i,1,m){
x = c[i].x;y = c[i].y;v = c[i].z;
v = lower_bound(tmp+1,tmp+cnt+1,v) - tmp;
insert(x,y,a[S[x]+y-1],0);
insert(x,y,a[S[x]+y-1]=v,1);
printf("%d\n",ans);
}
return 0;
}
数学上来先打表
题目:
给你一个图,每个点有点权,最开始没有边。
有一些操作:
- 添加一条 \(x\) 与 \(y\) 之间的双向边。
- 回到第 \(x\) 次操作后的状态。(注意这里的 \(x\) 可以是 $ 0$,即回到初始状态)
- 查询 \(x\) 所在联通块能到的点中点权第 \(y\) 小的值,如果不存在,那么输出 \(-1\)
\(n,m \leq 10^5\) 时限 \(2.3s\) .内存 \(50MB\)
题解:
可持久化启发式合并平衡树 !
题解说用动态内存 \(bitset\) 暴力就行了...
建出操作树后瞎搞就行了。
思想还是比较简单的,忽略掉全部都是零的位置,对于剩下的手动维护。
用一个链表就行了。
给出题人跪了 QAQ.
#include <vector>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int unt;
inline void read(int &x){
x=0;static char ch;static bool flag;flag = false;
while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
}
#define rg register int
#define rep(i,a,b) for(rg i=(a);i<=(b);++i)
#define per(i,a,b) for(rg i=(a);i>=(b);--i)
const int maxn = 100010;
struct Node{
ull s;int id,next;
void clear(){
s = 0;id = next = 0;
}
}G[maxn*6];
int head[maxn],cnt,ws[maxn*6],top;
inline void Setit(int u,int v){
G[++cnt].s = 1ULL << (v&63);
G[cnt].id = v >> 6;
G[cnt].next = head[u];
head[u] = cnt;
}
inline void insert(int p,Node x){
int id = top ? ws[top--] : ++ cnt;
G[id].clear();G[id] = x;
int q = G[p].next;
G[p].next = id;G[id].next = q;
}
int fa[maxn],siz[maxn],a[maxn],b[maxn],w[maxn];
inline int find(int x){
while(x != fa[x]) x = fa[x];
return x;
}
char f[65536];
int count_it(ll x){
return f[x&65535] + f[(x>>16)&65535] + f[(x>>32)&65535] + f[(x>>48)&65535];
}
inline int query(int x,int k){
x = find(x);if(k > siz[x]) return -1;
for(rg i = head[x],v;i;i = G[i].next){
v = count_it(G[i].s);
if(k > v) k -= v;
else{
rep(j,0,63) if((G[i].s >> j)&1 && (--k == 0))
return (G[i].id << 6) | j;
}
}
}
inline void Union(int x,int y){
if(x == y) return ;
fa[x] = y;siz[y] += siz[x];
rg i = head[x];
while(G[i].id <= G[head[y]].id){
if(i == 0) break;
if(G[i].id == G[head[y]].id) G[head[y]].s |= G[i].s;
else{
int id = top ? ws[top--] : ++ cnt;
G[id].clear();G[id] = G[i];
G[id].next = head[y];
head[y] = id;
}i = G[i].next;
}
for(rg j = head[y],x;i;i = G[i].next){
while((x = G[j].next) && G[x].id < G[i].id) j = x;
if(x == 0) insert(j,G[i]);
else if(G[x].id == G[i].id) G[x].s |= G[i].s;
else insert(j,G[i]);
}
return ;
}
inline void split(int x,int y){
if(x == y) return ;
fa[x] = x;siz[y] -= siz[x];
rg i = head[x];
while(G[i].id == G[head[y]].id){
if(i == 0) break;
G[head[y]].s ^= G[i].s;
i = G[i].next;
if(G[head[y]].s == 0){
ws[++top] = head[y];
head[y] = G[head[y]].next;
}else break;
}
bool flag = false;
for(rg j = head[y];i;i=G[i].next){
while((x = G[j].next) && G[x].id < G[i].id) j = x;
G[x].s ^= G[i].s;
if(G[x].s == 0){
ws[++top] = x;
G[j].next = G[x].next;
}
}
return ;
}
struct save{
int op,x,y,ans;
}zs[maxn];
vector<int> qer[maxn],lnk[maxn];
int nodecnt,pos[maxn];
void dfs(int u){
int tmp;
for(vector<int>::iterator it = qer[u].begin();it != qer[u].end();++ it)
zs[*it].ans = (tmp = query(zs[*it].x,zs[*it].y)) == -1 ? -1 : b[tmp];
for(vector<int>::iterator it = lnk[u].begin();it != lnk[u].end();++ it){
rg x = find(zs[*it].x),y = find(zs[*it].y);
if(siz[x] > siz[y]) swap(x,y);
Union(x,y);dfs(zs[*it].ans);split(x,y);
}
}
int main(){
rep(i,1,65535) f[i] = f[i>>1] + (i&1);
int n,m;read(n);read(m);
rep(i,1,n){
read(a[i]),b[i] = a[i];
w[i] = i;fa[i] = i;siz[i] = 1;
}sort(b+1,b+n+1);
rep(i,1,n){
a[i] = w[lower_bound(b+1,b+n+1,a[i])-b] ++ ;
Setit(i,a[i]);
}
int nw = 0;
rep(i,1,m){
read(zs[i].op);read(zs[i].x);
if(zs[i].op == 1){
read(zs[i].y);zs[i].ans = ++ nodecnt;
lnk[nw].push_back(i);
nw = nodecnt;
}else if(zs[i].op == 2) nw = pos[zs[i].x];
else{
read(zs[i].y);
qer[nw].push_back(i);
}
pos[i] = nw;
}
dfs(0);
rep(i,1,m) if(zs[i].op == 3){
printf("%d\r\n",zs[i].ans);
}
return 0;
}