noip31
T1
关于我考场上乱冲平衡树这件sb事
很快就冲了出来
然后手抖打错样例,把我hack了
sb字典序
正解:
先不考虑字典序问题,先将最大分数找出来,然后按照顺序考虑每一个位置填什么那个数能让分数尽可能的大,题解说显然,这样的数是具有单调性的,所以可以二分来求。
然后就可以用线段树来维护最大分数,然后对每一位二分,来找出字典序大的方案即可。
代码缩进可能有亿一点点怪,不要在意。
附带精美调试信息
Code
#include<set>
#include<cstdio>
#include<algorithm>
#define MAX 100010
#define re register
#define INF INT_MAX
using std::multiset;
namespace OMA
{
multiset<int>my;
multiset<int>::iterator it;
int n,a[MAX],b[MAX],ans;
struct stream
{
template<typename type>inline stream &operator >>(type &s)
{
int w=1; s=0; 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,*this;
}
}cin;
struct Segment_Tree
{
struct TREE
{
int ans;
int l,r;
int vl,vr;
}st[MAX<<2];
inline int ls(int p)
{ return p<<1; }
inline int rs(int p)
{ return p<<1|1; }
inline int min(int a,int b)
{ return a<b?a:b; }
inline void Push_up(int p)
{
int nim = min(st[ls(p)].vr,st[rs(p)].vl);
st[p].ans = st[ls(p)].ans+st[rs(p)].ans+nim;
st[p].vl = st[ls(p)].vl+st[rs(p)].vl-nim;
st[p].vr = st[ls(p)].vr+st[rs(p)].vr-nim;
}
inline void build(int p,int l,int r)
{
st[p].l = l,st[p].r = r;
if(l==r)
{ return ; }
int mid = (l+r)>>1;
build(ls(p),l,mid),build(rs(p),mid+1,r);
}
inline void update(int p,int pos,int vl,int vr)
{
if(st[p].l==st[p].r)
{ st[p]. vl += vl,st[p]. vr += vr; return ; }
int mid = (st[p].l+st[p].r)>>1;
if(pos<=mid)
{ update(ls(p),pos,vl,vr); }
else
{ update(rs(p),pos,vl,vr); }
Push_up(p);
}
}Tree;
signed main()
{
cin >> n;
Tree.build(1,1,MAX);
for(re int i=1; i<=n; i++)
{ cin >> b[i],Tree.update(1,b[i],0,1); /*printf("b[%d]=%d ",i,b[i]);*/ }
//printf("\n");
for(re int i=1; i<=n; i++)
{ cin >> a[i],Tree.update(1,a[i],1,0),my.insert(a[i]); /*printf("a[%d]=%d ",i,a[i]);*/ }
//printf("\n");
//printf("marks=%d\n",Tree.st[1].ans);
ans = Tree.st[1].ans;
for(re int i=1; i<=n; i++)
{
//printf("i=%d\n",i);
Tree.update(1,b[i],0,-1);
int l = b[i]+1,r = *--my.end();
while(l<r)
{
int mid = (l+r+1)>>1;
Tree.update(1,mid,-1,0);
if(Tree.st[1].ans+1==ans)
{ l = mid; }
else
{ r = mid-1; }
Tree.update(1,mid,1,0);
}
Tree.update(1,l,-1,0);
if(l<=r&&Tree.st[1].ans+1==ans)
{
printf("%d ",l),ans--;
if(((it = my.find(l))!=my.end()))
{ my.erase(it); }
}
else
{
Tree.update(1,l,1,0);
l = 1,r = b[i];
while(l<r)
{
int mid = (l+r+1)>>1;
Tree.update(1,mid,-1,0);
if(Tree.st[1].ans==ans)
{ l = mid; }
else
{ r = mid-1; }
Tree.update(1,mid,1,0);
}
Tree.update(1,l,-1,0);
printf("%d ",l);
if(((it = my.find(l))!=my.end()))
{ my.erase(it); }
}
}
return 0;
}
}
signed main()
{ return OMA::main(); }
T2
直接逆序对,30pts。
正解:
发现,序列中的最小值一定是在序列的最左侧或最右侧,显然,将最小值挪到这两端中的一个后,不会产生逆序对,所以可以将最小值挪到,最左/右中更优的位置。
用 \(deque\) 将每个数出现的位置存下来,直接枚举值域,因为根据上面的贪心策略,要优先更新值更小的,每次更新肯定是从当前这个数出现的位置最左端或最右端开始进行,用 \(BIT\) 维护一下移动对答案造成的影响即可。
发现STL里的 \(deque\) 太慢,可以手写一个为了竞争最快,那么手写的位置数组需要开多大呢? 在WYZG的怂恿下经过我的一波测试疯狂提交,发现最多不超过十个。
考试时请不要这么做。
旁边的WYZG:开到8试试
Code
#include<cstdio>
#define MAX 100010
#define re register
namespace OMA
{
int n;
int ans,xam,nim;
//deque<int>q[MAX];
struct deque
{
int q[10],l,r;
deque()
{ l = 1,r = 0; }
inline void push_back(int val)
{ q[++r] = val; }
inline int front()
{ return q[l]; }
inline int back()
{ return q[r]; }
inline void pop_front()
{ l++; }
inline void pop_back()
{ r--; }
inline bool empty()
{ return l>r; }
}q[MAX];
struct BIT
{
int tree[MAX];
inline int lowbit(int x)
{ return x&-x; }
inline void update(int x,int cnt)
{
for(re int i=x; i<=n; i+=lowbit(i))
{ tree[i] += cnt; }
}
inline int query(int x)
{
int res = 0;
for(re int i=x; i; i-=lowbit(i))
{ res += tree[i]; }
return res;
}
}BIT;
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;
}
inline int max(int a,int b)
{ return a>b?a:b; }
inline int min(int a,int b)
{ return a<b?a:b; }
signed main()
{
n = read();
for(re int i=1,a; i<=n; i++)
{ xam = max(xam,a = read()),nim = min(nim,a),q[a].push_back(i),BIT.update(i,1); }
for(re int i=nim; i<=xam; i++)
{
while(!q[i].empty())
{
int p1 = q[i].front(),p2 = q[i].back();
int cnt1 = BIT.query(p1-1),cnt2 = BIT.query(n)-BIT.query(p2);
if(cnt1<cnt2)
{ ans += cnt1,BIT.update(p1,-1),q[i].pop_front(); }
else
{ ans += cnt2,BIT.update(p2,-1),q[i].pop_back(); }
}
}
printf("%d\n",ans);
return 0;
}
}
signed main()
{ return OMA::main(); }
T3
区间的包含和不相交关系,显然是可以建一颗树来表示这种关系的,然后就可以愉快的用树形dp来解决此题。
然而考场上并没有写出来,丢了个sb暴力,样例还一下子就过了,导致我就去看前边的题了。
正解:
仍然是上边的建树表示区间之间的关系,仍然是dp。
设 \(dp_{i,j}\) 表示在以 \(i\) 为根的子树中点被覆盖的次数最多为 \(j\) 时的最大美观程度,转移时,首先将所有子树的答案合并,然后考虑点 \(i\) 的贡献
然后就有一个比较显然的dp方程:
然后就可以维护 \(dp_{i}\) 的差分表,子树合并对应将差分表相加,点 \(i\) 的贡献则相当于将 \(a_{i}\) 按照大小顺序插入到差分表中。
所以考虑用优先队列/大根堆来实现。
以及,最后输出答案不换行!!!
请使用C++11提交,然而我不知道为什么交C++会MLE
好吧,根堆的 \(swap\) 只有在C++11里是 \(O(1)\) 的。
附带精美调试信息
Code
#include<set>
#include<queue>
#include<cstdio>
#define MAX 300010
#define re register
//#define int long long
#define int64_t long long
using std::multiset;
using std::priority_queue;
namespace OMA
{
int n,m,a[MAX];
int64_t ans[MAX];
priority_queue<int64_t>q[MAX];
struct node
{
int l,r,id;
inline friend bool operator <(const node &a,const node &b)
{ return a.l==b.l?(a.r==b.r?a.id<b.id:a.r>b.r):a.l<b.l; }
};
multiset<node>set;
multiset<node>::iterator it;
struct graph
{
int next;
int to;
}edge[MAX<<1];
int cnt=1,head[MAX];
inline void add(int u,int v)
{ edge[++cnt] = (graph){head[u],v},head[u] = cnt; }
struct stream
{
template<typename type>inline stream &operator >>(type &s)
{
int w=1; s=0; 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,*this;
}
}cin;
inline void build(node p1)
{
while(!set.empty())
{
it = set.lower_bound(p1);
//printf("1\n");
if(it==set.end())
{ break ; }
//printf("2\n");
node p2 = *it;
//printf("%d %d %d %d\n",p1.l,p1.r,p2.l,p2.r);
if(p2.l>=p1.l&&p2.r<=p1.r)
{
set.erase(it);
add(p1.id,p2.id);
//printf("CNMD\n");
build(p2);
}
else
{ break ; }
}
}
inline void dfs(int u)
{
int son = m+1;
for(re int i=head[u],v; i; i=edge[i].next)
{
v = edge[i].to; dfs(v);
if(q[v].size()>q[son].size())
{ son = v; }
}
swap(q[u],q[son]);
for(re int i=head[u],v; i; i=edge[i].next)
{
v = edge[i].to;
if(v!=son)
{
cnt = q[v].size();
for(re int j=1; j<=cnt; j++)
{
ans[j] = q[u].top()+q[v].top();
//printf("%d %d\n",q[u].top(),q[v].top());
q[u].pop(),q[v].pop()/*,q[u].push(ans[j])*/;
}
for(re int j=1; j<=cnt; j++)
{ q[u].push(ans[j]); }
}
}
q[u].push(a[u]);
}
signed main()
{
cin >> n >> m;
for(re int i=1,l,r; i<=m; i++)
{ set.insert((node){(cin >> l,l),(cin >> r,r),i}),cin >> a[i]; }
build((node){1,n,0}); dfs(0);
/*for(re int i=1; i<=m; i++)
{
printf("u=%d\n",i);
for(re int j=head[i]; j; j=edge[j].next)
{
printf("v=%d\n",edge[i].to);
}
}*/
cnt = q[0].size();
for(re int i=1; i<=cnt; i++)
{ ans[i] = q[0].top(); q[0].pop(); }
for(re int i=1; i<=m; i++)
{ /*printf("1:%d %d\n",ans[i-1],ans[i]);*/ ans[i] += ans[i-1],printf("%lld ",ans[i]); }
return 0;
}
}
signed main()
{ return OMA::main(); }