恩欧挨批模拟-22
水博客太快乐了
考场
先看 \(T1\) ,一看就知道是个贪心。
然后口胡了一个显然错误的贪心。。。
调了将近两个小时才发现是显然错误的。。。
然而看到 \(T2\) ,这显然就是树上某种可持久化数据结构啊。。。
应该可持久化平衡树和主席树都可以。。。然而因为一直在调 \(T1\) ,没时间写了。。。
所以先看 \(T3\) ,写了最暴力的暴力上去。。。。
最后半个小时回头看 \(T1\) ,想到了一个显然的显然正确的贪心。。。
水了,过了,挂了。。。。
分数
预估 : \(t1\ 100pts\ +\ t2\ 0pts\ +\ t3\ 30pts\ =\ 130pts\)
实际 : \(t1\ 80pts\ +\ t2\ 0pts\ +\ t3\ 30pts\ =\ 110pts\)
发现 \(t1\) 有几个点 \(Re\) 了。。。加了一个特判就过了。。。
题解
A. d
有一个显然的贪心,就是对于删掉的那些矩形,显然都应该是长或宽最小的矩形(因为当前矩形的最大交面积显然是有最小的长和最小的宽决定的),所以将长和宽分别排序,先删除长最小的 \(m\) 个矩形,再重新选择被删除的矩形中长最大的,转而删除没有被删除的矩形中宽最小的,直到删除的所有矩形是宽最小的 \(m\) 个。
code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define len first
#define id second
const int N=1e5+10;
inline int read(){
int f=1, x=0; char ch=getchar();
while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
return f*x;
}
int T, n, m, ans;
pair<int, int > a[N], b[N];
int ua[N], ub[N];
bool vis[N];
signed main(void){
T=read();
while(T--){
n=read(), m=read();
memset(vis, 0, (n+1)*sizeof(bool));
for(int i=1; i<=n; ++i){
ua[i]=read(), ub[i]=read();
a[i]=make_pair(ua[i], i), b[i]=make_pair(ub[i], i);
}
sort(a+1, a+1+n); sort(b+1, b+1+n);
int ul=1;
for(int i=1; i<=m; ++i) vis[a[i].id]=1;
while(vis[b[ul].id]&&ul<n) ul++;
ans=a[m+1].len*b[ul].len;
for(int i=m; i; --i){
vis[a[i].id]=0;
if(ub[a[i].id]<b[ul].len) vis[a[i].id]=1;
else{
vis[b[ul].id]=1;
while(vis[b[ul].id]&&ul<n) ++ul;
}
ans=max(ans, a[i].len*b[ul].len);
}
printf("%lld\n", ans);
}
return 0;
}
B. e
乍一看就是树上可持久化数据结构。。。
可以用可持久化平衡树也可以用主席树。。。。
可持久化平衡树又难写又是双 \(log\) 的,那显然要写主席树啊。。。
每个点的主席树保存的是从当前节点到根这一条链上的所有信息(这貌似也是显然的啊。。。
然而权值线段树上怎么求前驱后继着实有点生疏了。。。应该记录一下。。。
求前驱,若要求的权值小于等于当前线段树节点的 \(mid\) 则直接去左区间找;
若大于当前线段树节点的 \(mid\) ,则先去右区间找,若找不到,再在左区间找。
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10, INF=0x7fffffff;
inline int read(){
int f=1, x=0; char ch=getchar();
while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
return f*x;
}
int n, q, op, a[N], b[N], ask[N], ans, maxn;
vector<int> l[N];
int fa[N], siz[N], top[N], son[N], dep[N], rt[N], tot;
struct TRE{
int l, r;
int num;
int ls, rs;
}t[N<<6];
void add(int lst, int l, int r, int x, int &p){
p=++tot; t[p]=t[lst]; t[p].l=l, t[p].r=r; t[p].num++;
if(l==r) return; int mid=(l+r)>>1;
if(x<=mid) add(t[lst].ls, l, mid, x, t[p].ls);
else add(t[lst].rs, mid+1, r, x, t[p].rs);
}
int pre(int lst, int x, int p){
if(t[p].num-t[lst].num==0) return 0;
if(t[p].l==t[p].r) return t[p].l;
int mid=(t[p].l+t[p].r)>>1, rec=0;
if(x>mid){
rec=pre(t[lst].rs, x, t[p].rs);
if(!rec) rec=pre(t[lst].ls, x, t[p].ls);
}else rec=pre(t[lst].ls, x, t[p].ls);
return rec;
}
int nxt(int lst, int x, int p){
if(t[p].num-t[lst].num==0) return 0;
if(t[p].l==t[p].r) return t[p].l;
int mid=(t[p].l+t[p].r)>>1, rec=0;
if(x<=mid){
rec=nxt(t[lst].ls, x, t[p].ls);
if(!rec) rec=nxt(t[lst].rs, x, t[p].rs);
}else rec=nxt(t[lst].rs, x, t[p].rs);
return rec;
}
void dfs1(int u, int f){
fa[u]=f, dep[u]=dep[f]+1, siz[u]=1;
for(int v : l[u]){
if(v==f) continue;
dfs1(v, u); siz[u]+=siz[v];
if(siz[v]>siz[son[u]]) son[u]=v;
}
}
void dfs2(int u, int tp){
top[u]=tp; add(rt[fa[u]], 1, maxn, a[u], rt[u]);
if(son[u]) dfs2(son[u], tp);
for(int v : l[u]) if(v!=fa[u]&&v!=son[u]) dfs2(v, v);
}
inline int lca(int u, int v){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) v^=u, u^=v, v^=u;
u=fa[top[u]];
}
return dep[u]<dep[v] ? u : v;
}
int main(void){
n=read(), q=read(), op=read();
for(int i=1; i<=n; ++i) maxn=max(a[i]=read(), maxn);
int x, y, r, k;
for(int i=1; i<n; ++i){
x=read(), y=read();
l[x].push_back(y);
l[y].push_back(x);
}
dfs1(1, 0); dfs2(1, 1);
while(q--){
r=read(), k=read();
for(int i=1; i<=k; ++i) ask[i]=(read()-1+ans*op)%n+1;
int Lca=ask[1]; ans=INF;
for(int i=2; i<=k; ++i) Lca=lca(Lca, ask[i]);
for(int i=1; i<=k; ++i){
int Pre=pre(rt[fa[Lca]], r, rt[ask[i]]), Nxt=nxt(rt[fa[Lca]], r, rt[ask[i]]);
if(Pre) ans=min(ans, abs(Pre-r));
if(Nxt) ans=min(ans, abs(Nxt-r));
}
printf("%d\n", ans);
}
return 0;
}
C. f
乍一看就不像是一道可做的题。。。
果然是全场最难。。。
考虑先解决第一个问题,如何找到第 \(p\) 大的贡献。
首先有一个显然的事实,对于任意两个数,这两个数是否答案有贡献只与这两个数二进制上不相同的第一位有关。。所以若要异或一个数,这两个数是否对答案有贡献,也只与这个数在这一位上是否是 \(1\) 有关。
由此可以得到一个重要的结论,每一位对答案的贡献是相互独立的。。。
由此可以提前预处理出每一位是 \(1\) 时对答案的贡献(然而每一位为 \(0\) 时也可能对答案有贡献,所以可以提前求出不异或时所有逆序对的数量,再减去这一位位 \(0\) 时逆序对的数量,再加上这一位为 \(1\) 时逆序对的数量。
用 \(trie\) 树即可,边插入边求解。。。
之后考虑二分答案,考虑对于每一个答案如何 \(check\) ,如果直接枚举每一种情况的话,共 \(2^{30}\) 种,惨 \(T\) ,所以考虑将其拆开,即 \(meet\ in\ the\ middle\) 先算出前 \(k/2\) 位的所有情况,再算出后 \(k-k/2\) 位的所有情况,排序后直接双指针即可。。
求出 \(f(res)\) 后再求 \(res\) 就简单了很多,同样用双指针找出所有等于 \(f(res)\) 时的前 \(k/2\) 位和后 \(k-k/2\) 位,大力排序即可。。。
code
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=5e5+5, K=30;
inline int read(){
int f=1, x=0; char ch=getchar();
while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
return f*x;
}
int n, k, p, all, cnt1, cnt2, cnt;
int a[N], val[K], b[N];
int f[1<<16], id[N];
vector<int> st[1<<16];
#define lb(x) x&-x
struct BIT{
int tr[N];
inline int query(int p) { int ans=0; for(; p; p-=lb(p)) ans+=tr[p]; return ans; }
inline void add(int p) { for(; p<=n; p+=lb(p)) tr[p]++; }
}t1;
#undef lb
struct SOL{
int num, id;
friend bool operator < (const SOL &x, const SOL &y) { return x.num < y.num; }
}n1[(1<<16)+2], n2[(1<<16)+2];
struct TRE{
struct trie{
int to[2], num;
}t[N*K];;
int tot=1;
inline void insert(int x){
int p=1;
for(int i=k-1; ~i; --i){
int ul=(x>>i)&1;
if(ul) val[i]+=t[t[p].to[ul^1]].num;
else val[i]-=t[t[p].to[ul^1]].num;
if(!t[p].to[ul]) t[p].to[ul]=++tot;
p=t[p].to[ul]; t[p].num++;
}
}
}t;
inline void init(){
sort(b+1, b+1+n);
int len=unique(b+1, b+1+n)-b-1;
for(int i=n; i; --i){
int ul=lower_bound(b+1, b+1+len, a[i])-b;
all+=t1.query(ul-1); t1.add(ul);
}
}
inline bool check(int x){
int num=0;
for(int l=1, r=cnt2; r; --r){
while(l<=cnt1&&n1[l].num+all+n2[r].num<=x) l++;
num+=l-1;
}
return num>=p;
}
inline int deal(int x){
int ans=0, tmp=0;
for(int l=1, r=cnt2; r; --r){
++cnt; id[f[cnt]=cnt]=n2[r].id;
while(l<=cnt1&&n1[l].num+n2[r].num+all<x) ++l; tmp=0;
while(l<=cnt1&&n1[l].num+n2[r].num+all==x) st[cnt].push_back(n1[l].id), ++l, ++tmp;
ans+=l-tmp-1;
}
return ans;
}
inline bool cmp(int x, int y) { return id[x] < id[y]; }
signed main(void){
n=read(); k=read(); p=read();
for(int i=1; i<=n; ++i) t.insert(b[i]=a[i]=read());
int hk=k>>1, kh=k-hk, ans=0; init();
int s1=(1<<hk)-1, s2=((1<<kh)-1)<<hk, bas=1<<hk;
for(int i=0; i<=s1; ++i){
int tmp=0;
for(int j=0; j<hk; ++j) if((i>>j)&1) tmp+=val[j];
n1[++cnt1].num=tmp; n1[cnt1].id=i;
}
for(int i=0; i<=s2; i+=bas){
int tmp=0;
for(int j=hk; j<k; ++j) if((i>>j)&1) tmp+=val[j];
n2[++cnt2].num=tmp, n2[cnt2].id=i;
}
sort(n1+1, n1+1+cnt1); sort(n2+1, n2+1+cnt2);
int l=0, r=(n*(n-1))>>1;
while(l<r){
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
printf("%lld ", l); p-=deal(l);
sort(f+1, f+1+cnt, cmp);
for(int i=1; i<=cnt; ++i){
int siz=st[f[i]].size();
if(p>siz) p-=siz;
else{
sort(st[f[i]].begin(), st[f[i]].end());
ans=id[f[i]]|st[f[i]][p-1];
break;
}
}
printf("%lld\n", ans);
return 0;
}
反思
一个最近考试中很常见的一个问题,要注意安排好时间,给每道题留足够的思考和代码时间,不要一味个干统一到题,若最后没做出来则得不偿失。