8.5考试总结(NOIP模拟31)[Game·Time·Cover]
我们总是在注意错过太多,却不注意自己拥有多少。
前言
考场上疯狂搞第一题,终于把人给搞没了。。
T1 Game
解题思路
线段树+二分
总体来讲就是用线段树维护三个值:
-
没有产生贡献的 a(小 B 的牌)
-
没有产生贡献的 b(小 A 的牌)
-
产生了的贡献值
对于上面的三个值建一棵权值线段树。
因为要产生贡献,因此,新合并的区间内产生的贡献一定是左区间的 a 右区间的 b 数量的较小值。
然后,对于每一个 a 查找 一个可以配对的最优的 b (如果存在,一定在 a+1 到目前 b 最大值这个区间内)。
如果两者可以产生贡献的话,删去两者之后就会对答案的贡献值减去一
用 muliset 维护剩余的 b 。
因为保证字典序最大,因此在合法区间内查找符合条件的值越大越好。
对于不会产生贡献的 a 二分区间有一些变化,其他的都大同小异了。。
code
#include<bits/stdc++.h>
//#define int long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
#define ls tre[x].l
#define rs tre[x].r
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
const int N=1e5+10;
int n,all,sum,root,a[N],b[N];
int cnt,lsh[N<<1];
multiset<int> s;
struct Segment_Tree
{
int l,r,a,b,dat;
}tre[N<<2];
void push_up(int x)
{
int tmp=min(tre[rs].b,tre[ls].a);
tre[x].dat=tre[ls].dat+tre[rs].dat+tmp;
tre[x].a=tre[ls].a+tre[rs].a-tmp;
tre[x].b=tre[ls].b+tre[rs].b-tmp;
}
void insert(int &x,int l,int r,int pos,int num1,int num2)
{
if(!x) x=++all;
if(l==r)
{
tre[x].a+=num1;
tre[x].b+=num2;
return ;
}
int mid=(l+r)>>1;
if(pos<=mid) insert(ls,l,mid,pos,num1,num2);
else insert(rs,mid+1,r,pos,num1,num2);
push_up(x);
}
signed main()
{
n=read();
for(int i=1;i<=n;i++)
lsh[++cnt]=a[i]=read();
for(int i=1;i<=n;i++)
lsh[++cnt]=b[i]=read();
sort(lsh+1,lsh+cnt+1);
cnt=unique(lsh+1,lsh+cnt+1)-lsh-1;
for(int i=1;i<=n;i++)
{
a[i]=lower_bound(lsh+1,lsh+cnt+1,a[i])-lsh;
b[i]=lower_bound(lsh+1,lsh+cnt+1,b[i])-lsh;
insert(root,1,cnt,a[i],1,0);
insert(root,1,cnt,b[i],0,1);
s.insert(b[i]);
}
sum=tre[root].dat;
for(int i=1;i<=n;i++)
{
insert(root,1,cnt,a[i],-1,0);
int l=a[i]+1,r=*(--s.end()),ans=-1;
while(l<=r)
{
int mid=(l+r)>>1;
insert(root,1,cnt,mid,0,-1);
if(tre[root].dat+1==sum){ans=mid;l=mid+1;}
else r=mid-1;
insert(root,1,cnt,mid,0,1);
}
if(ans!=-1)
{
sum--;
insert(root,1,cnt,ans,0,-1);
s.erase(s.find(ans));
printf("%d ",lsh[ans]);
continue;
}
l=1;r=a[i];ans=-1;
while(l<=r)
{
int mid=(l+r)>>1;
insert(root,1,cnt,mid,0,-1);
if(tre[root].dat==sum){ans=mid;l=mid+1;}
else r=mid-1;
insert(root,1,cnt,mid,0,1);
}
insert(root,1,cnt,ans,0,-1);
s.erase(s.find(ans));
printf("%d ",lsh[ans]);
}
return 0;
}
T2 Time
解题思路
其实就是选择每一种值中不同位置的数到两侧边界距离最小的值进行统计。
相当与是把 swap 当作了一种距离的操作。。
用双端队列或者线段树进行是维护距离最小的。
用树状数组维护到两边的距离(数字个数)就好了。
OMA 运用强悍的卡常技术发现数据里相同的数不会超过 10 个
于是我的内存直接
不得不说手写队列真香
code
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
const int N=1e5+10;
int n,ans,tre[N];
struct Deque
{
int head,tail,num[15];
bool empty(){return head>tail;}
int size(){return tail-head+1;}
int front(){return num[head];}
int back(){return num[tail];}
void clear(){head=1;tail=0;}
void push(int x){num[++tail]=x;}
void pop_front(){head++;}
void pop_back(){tail--;}
};
Deque q;
struct Node
{
int dat,id;
bool friend operator < (Node x,Node y)
{
if(x.dat==y.dat) return x.id<y.id;
return x.dat<y.dat;
}
}s[N];
int lowbit(int x)
{
return x&(-x);
}
void add(int x,int num)
{
for(int i=x;i<=n;i+=lowbit(i))
tre[i]+=num;
}
int ask(int x)
{
int temp=0;
for(int i=x;i;i-=lowbit(i))
temp+=tre[i];
return temp;
}
signed main()
{
n=read();
for(int i=1;i<=n;i++)
{
s[i].dat=read();
s[i].id=i;
add(i,1);
}
sort(s+1,s+n+1);
q.clear();
for(int i=1;i<=n;i++)
{
q.push(s[i].id);
if(s[i].dat==s[i+1].dat) continue;
while(!q.empty())
{
int x=q.front(),y=q.back();
int dis1=ask(x-1),dis2=ask(n)-ask(y);
ans+=min(dis1,dis2);
if(dis1<dis2) add(x,-1),q.pop_front();
else add(y,-1),q.pop_back();
}
q.clear();
}
printf("%d",ans);
return 0;
}
T3 Cover
解题思路
手写队列真不好
快是快了,就是内存有亿点大(3e5*3e5)直接数组越界到飞起。。
因为区间之间仅有包含和不相交的关系,因此可以转化为树形 DP 进行子树合并。
首先对于所有的区间以左端点为第一关键字,右端点为第二关键字进行排序。
然后用一个栈进行维护就可以完成建边的操作了。
树形 DP 的值用 multiset 维护。
进行子树合并的事后,每次取 set 里最大的值进行合并,保证取某一层里最大的值。
然后就没啥了,思路以及打法确实很妙
code
#include<bits/stdc++.h>
#define int long long
#define ll long long
#define ull unsigned long long
#define f() cout<<"Pass"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
const int N=3e5+10;
int tot,head[N],ver[N],nxt[N];
int n,sta[N],top,ans;
void add(int x,int y)
{
ver[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
struct Node
{
int l,r,dat;
void insert(){l=read();r=read();dat=read();}
bool friend operator < (Node x,Node y)
{
if(x.l==y.l) return x.r>y.r;
return x.l<y.l;
}
}s[N];
multiset<int> f[N];
void merge(multiset<int> &x,multiset<int> &y)
{
if(x.size()<y.size()) swap(x,y);
queue<int> q;
for(auto it=y.begin();it!=y.end();it++)
{
q.push(*it+*x.begin());
x.erase(x.begin());
}
while(!q.empty())
{
x.insert(q.front());
q.pop();
}
}
void dfs(int x)
{
for(int i=head[x];i;i=nxt[i])
{
int to=ver[i];
dfs(to);
merge(f[x],f[to]);
}
f[x].insert(-s[x].dat);
}
signed main()
{
n=read();n=read();
for(int i=1;i<=n;i++)
s[i].insert();
sort(s+1,s+n+1);
sta[++top]=0;
for(int i=1;i<=n;i++)
{
while(sta[top]&&s[sta[top]].r<s[i].r) top--;
add(sta[top],i);
sta[++top]=i;
}
dfs(0);
for(int i=1;i<=n;i++)
{
if(f[0].size())
{
ans-=*f[0].begin();
f[0].erase(f[0].begin());
}
printf("%lld ",ans);
}
return 0;
}