并不对劲的复健训练-bzoj5249:loj2472:p4364[2018多省联考]IIIDX
题目大意
给出\(n,k,d_1,...,d_n\)(\(n\leq 5\times 10^5,1<k\leq 10^9,d\leq 10^9,k\in R\))。有一个满足 对于每个点\(i\)它的父亲是\(\lfloor\frac{i}{d}\rfloor\)(若为0则没父亲)的森林,将\(d_1,...,d_n\)分配给森林中的每个点,设第\(i\)号点分配的权值为\(w_i\),满足\(w_i\)不超过子树中所有点的点权,且使\(w_1\)尽量大,\(w_1\)相同时使\(w_2\)尽量大……以此类推。
题解
subtask 1/2:\(d\)互不相同,55pts
先将\(d\)从大到小排序,把树建出来,设点\(i\)的字数大小为\(siz_i\)。
从1号点考虑:\(w_1\)越大越好,而有\(siz_1\)个\(w\)是必须大于等于\(w_1\)的,那么\(w_1\)取\(d_{siz_1}\)最好。
考虑完一号点之后,发现一号点子树里的点只能取\(d_i,i\in[1,siz_1-1]\)。
就可以对于每个点维护\([l_i,r_i]\)表示当前这个点的子树中的点只能在该区间中取。
每考虑一个点\(x\),就令\(w_x=d_{l_{fa_x}+siz_x-1}\),并将\([l_x,r_x]\)更新为\([l_{fa_x},l_{fa_x}+siz_x-2]\),将\([l_{fa_x},r_{fa_x}]\)更新为\([l_{fa_x}+siz_x,r_{fa_x}]\)。
subtask 2/2:无特殊限制,45pts
发现上一个subtask的方法的问题在于,当\(d\)排序后长成“!!!!!@@@@@##”(相同的符号表示相同的数)时,如果 \(siz_1=7\),那么分配给1号点的子树的数就是“!!!!!@@”,如果2号点不在1号点的子树中,把“!!@@@@@”给1号点的子树,2号点就有机会分到“!”。
也就是说,这个时候考虑每一个点时,选的位置应满足:1.左边的可用的数的个数大于等于\(siz_i\);2.在去重后的序列中尽可能靠左;3.在同色段中尽可能靠右。
这时还有一个小问题:因为选的点在同色段中尽可能靠右,所以它左边可用的数的个数可能会大于\(siz_i\),这个时候不好规定它的子树中的点具体取哪个区间(或哪些)数。
但是可以把它转化成一些形如“对于\(i\in[1,x]\),有\(siz_p\)个\(d\)已经被取了”的事件,和形如“求还可以取\(siz_q\)个的、在去重后的序列中最靠左、在同色段中最靠右的位置”的询问。
可以维护\(a_j\)表示对于\(i\in[1,j]\)还有\(a_j\)个\(d_i\)没有被取。
对于一个“事件”,\(j\in[x,n]\)的部分肯定都有\(a_j-=siz_p\)。
另一部分之所以难求,是因为不知道具体取了哪\(siz_p\)个数。这部分可以这样处理:按编号从小到大考虑每个点,这样先考虑的点一定比后考虑的点更需要更大的\(d\)。这样这部分的\(a_j\)就变成了“当每个事件都尽可能往后取时,对于\(i\in[1,j]\)还有\(a_j\)个\(d_i\)没有被取”。记每个事件只处理\(j\in[x,n]\)得到的数为\(a'_j\),那么就有\(a_j=min\{a‘_c|c\in[j,n]\}\)。
直接维护\(a\)很麻烦,考虑维护\(a'\):这样每个查询就变成了求最靠前的不少于\(siz_q\)后缀最小值,可以在线段树上二分;每个事件就变成了把\([x,n]\)区间减\(siz_p\)。
还要注意一个小问题:考虑到一个点时,应将它父亲为它父亲的子树留出的(不包括它父亲本身)部分去掉,即将\([x,n]\)加\(siz_p-1\)。这样也不用担心考虑别的子树时算到这部分,因为点\(i\)的父亲是\(\lfloor\frac{i}{d}\rfloor\),所以同层(即父亲相同)的点是连着考虑的,在考虑到别的子树中的点时已经把这部分又减回去了。
代码
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<iomanip>
#include<iostream>
#include<map>
#include<queue>
#include<set>
#include<stack>
#include<vector>
#define rep(i,x,y) for(int i=(x);i<=(y);++i)
#define dwn(i,x,y) for(int i=(x);i>=(y);--i)
#define maxn 500007
#define maxm 1000007
#define D double
#define ls (u<<1)
#define rs (u<<1|1)
#define mi (l+r>>1)
using namespace std;
int read()
{
int x=0,f=1;char ch=getchar();while(!isdigit(ch)&&ch!='-')ch=getchar();if(ch=='-')f=-1,ch=getchar();
while(isdigit(ch))x=(x<<1)+(x<<3)+ch-'0',ch=getchar();return x*f;
}
void write(int x)
{
if(x==0){putchar('0'),putchar(' ');return;}int f=0;char ch[20];if(x<0)putchar('-'),x=-x;
while(x)ch[++f]=x%10+'0',x/=10;while(f)putchar(ch[f--]);putchar(' ');return;
}
D K;
int n,d[maxn],sz[maxn],now,anc[maxn],ans[maxn];
int cntnd,ed[maxn],used[maxn],tr[maxn<<2],mk[maxn<<2];
void pu(int u){tr[u]=min(tr[ls],tr[rs]);}
void mark(int u,int k){tr[u]+=k,mk[u]+=k;}
void pd(int u){if(mk[u]){mark(ls,mk[u]),mark(rs,mk[u]),mk[u]=0;}}
void build(int u,int l,int r)
{
if(l==r){tr[u]=l;return;}
build(ls,l,mi),build(rs,mi+1,r),pu(u);return;
}
void add(int u,int l,int r,int x,int y,int k)
{
if(x<=l&&r<=y){mark(u,k);return;}
pd(u);
if(x<=mi)add(ls,l,mi,x,y,k);
if(y>mi)add(rs,mi+1,r,x,y,k);
pu(u);return;
}
int ask(int u,int l,int r,int k)
{
if(l==r){if(tr[u]<k)return l+1;return l;}
pd(u);
if(tr[rs]>=k)return ask(ls,l,mi,k);
else return ask(rs,mi+1,r,k);
}
int num(int u,int l,int r,int x)
{
if(x<=l&&r<=x)return tr[u];
pd(u);
if(x<=mi)return num(ls,l,mi,x);
else return num(rs,mi+1,r,x);
}
int main()
{
scanf("%d%lf",&n,&K);
rep(i,1,n)d[i]=read(),anc[i]=(D)i/K;
dwn(i,n,1)sz[i]++,sz[anc[i]]+=sz[i];
sort(d+1,d+n+1);reverse(d+1,d+n+1);int now=0;
dwn(i,n,1)
{
if(i==n||d[i]!=d[i+1])ed[i]=i,used[i]=0;
else ed[i]=ed[i+1];
}
build(1,1,n);
rep(i,1,n)
{
if(anc[i]&&anc[i]!=anc[i-1])add(1,1,n,ans[anc[i]],n,sz[anc[i]]-1);
int pos=ed[ask(1,1,n,sz[i])];used[pos]++,pos-=used[pos]-1;
ans[i]=pos,add(1,1,n,ans[i],n,-sz[i]);
}
rep(i,1,n)write(d[ans[i]]);
return 0;
}
一些感想
上面有一句话是“这时还有一个小问题:因为选的点在同色段中尽可能靠右,所以它左边可用的数的个数可能会大于\(siz_i\),这个时候不好规定它的子树中的点具体取哪个区间(或哪些)数。”
然而在做题的时候只想到了上面举的例子和样例,就觉得应该把\(i\)子树中的数无脑选为可选范围内最大的一段。
于是就用平衡树维护安排给每个点的数,每考虑一个点,就是从它的父亲中先找出第\(siz_i\)大的数,然后找一个数\(d_x\)使\(d_x\)在\(fa_i\)中,且\(fa_i\)的平衡树中在区间\([第siz_i大的数,d_x]\)的个数大于\(siz_i\),且\(d_x\)尽可能小。
然后把\(fa_i\)的平衡树在\([第siz_i大的数,d_x]\)这个区间的数都给\(i\)的平衡树,注意\(d_x\)有多个时可能只需要给一部分。
令\(w_i=\)点\(i\)平衡树中最大的数,并从平衡树中删掉这个数。
这样并不是正确的,因为当\(d\)排序后一部分为“!!!!!@@@@@”时,如果\(siz_1=6,siz_2=4\),分给1的子树的是“!@@@@@”,分给2的子树的是“!!!!”,那么\(w_1=\)@,\(w_2=\)!,但是如果把“!!@@@@”给1号点的子树,“!!!@”给2号点的子树,就能有\(w_1=\)@,\(w_2=\)@。
不过先留着代码吧,万一哪天出题用上了?
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<iomanip>
#include<iostream>
#include<map>
#include<queue>
#include<set>
#include<stack>
#include<vector>
#define rep(i,x,y) for(int i=(x);i<=(y);++i)
#define dwn(i,x,y) for(int i=(x);i>=(y);--i)
#define maxn 500007
#define maxm 1000007
#define D double
#define ls son[u][0]
#define rs son[u][1]
using namespace std;
int read()
{
int x=0,f=1;char ch=getchar();while(!isdigit(ch)&&ch!='-')ch=getchar();if(ch=='-')f=-1,ch=getchar();
while(isdigit(ch))x=(x<<1)+(x<<3)+ch-'0',ch=getchar();return x*f;
}
void write(int x)
{
if(x==0){putchar('0'),putchar(' ');return;}int f=0;char ch[20];if(x<0)putchar('-'),x=-x;
while(x)ch[++f]=x%10+'0',x/=10;while(f)putchar(ch[f--]);putchar(' ');return;
}
D K;
int n,d[maxn],sz[maxn],now,anc[maxn];
int son[maxm][2],fa[maxm],cntnd,siz[maxm],key[maxm],num[maxm],rt[maxm];
void pu(int u){if(!u)return;siz[u]=num[u];if(ls)siz[u]+=siz[ls];if(rs)siz[u]+=siz[rs];}
int getso(int u){return son[fa[u]][0]!=u;}
void rot(int u)
{
int fu=fa[u],ffu=fa[fu],l=getso(u),fl=getso(fu),r=l^1,rson=son[u][r];
son[u][r]=fu,son[fu][l]=rson;if(ffu)son[ffu][fl]=u;if(rson)fa[rson]=fu;fa[fu]=u,fa[u]=ffu;
pu(fu),pu(u);
}
void splay(int u,int k,int & root)
{
while(fa[u]!=k){if(fa[fa[u]]!=k)rot(getso(u)^getso(fa[u])?u:fa[u]);rot(u);}
if(!k)root=u;
}
void fnd(int k,int & root)
{
int u=root;
while(k!=key[u]&&son[u][k>key[u]])u=son[u][k>key[u]];
splay(u,0,root);
}
int nxt(int k,int f,int & root)
{
fnd(k,root);int u=root;
if((key[u]<k&&!f)||(key[u]>k&&f))return u;
u=son[u][f];if(!u)return u;
while(son[u][f^1])u=son[u][f^1];
return u;
}
int build(int l,int r)
{
int u=l+r>>1;
if(l<=u-1)ls=build(l,u-1),fa[ls]=u;
if(r>=u+1)rs=build(u+1,r),fa[rs]=u;
pu(u);return u;
}
int getp(int k,int & root)
{
int u=root;
while(son[u][1])u=son[u][1];
splay(u,0,root);
while(1)
{
if(siz[rs]>=k)u=rs;
else if(siz[rs]+num[u]<k)k-=siz[rs]+num[u],u=ls;
else {/*cout<<"p:"<<u<<endl;*/return u;}
}
return -1;
}
int getrt(int k,int & root)
{
int u=getp(k,root),lk=nxt(key[u],0,root),tpk=k;
//cout<<"lk:"<<lk<<endl;
if(lk)splay(lk,0,root),u=son[lk][1];
else u=root;//cout<<u<<endl;
while(1)
{
if(siz[ls]>=k)u=ls;
else if(siz[ls]+num[u]<k)k-=siz[ls]+num[u],u=rs;
else break;
}
splay(u,lk,root);k=tpk;
if(siz[ls]+num[u]==k)
{
if(rs)fa[rs]=lk;if(lk)son[lk][1]=rs;else root=rs;
fa[u]=0,rs=0;
pu(u),pu(lk);
return u;
}
int res=ls;ls=0;
int nu=++cntnd;key[nu]=key[u],num[nu]=k-siz[res],num[u]-=k-siz[res],son[nu][key[res]>key[nu]]=res;if(res)fa[res]=nu;
pu(nu),pu(u);if(lk)pu(lk);
return nu;
}
int del(int & root)
{
int u=root;
while(son[u][0])u=son[u][0];
splay(u,0,root);
if(num[u]==1)root=rs,fa[rs]=0;
else num[u]--,pu(u);
return key[u];
}
int main()
{
scanf("%d%lf",&n,&K);
rep(i,1,n)d[i]=read();
sort(d+1,d+n+1);int now=0;
rep(i,1,n)
{
now++;
if(i==n||d[i]!=d[i+1])cntnd++,key[cntnd]=d[i],num[cntnd]=now,now=0;
}
rt[0]=build(1,cntnd);
rep(i,1,n)anc[i]=(D)i/K;
dwn(i,n,1)sz[i]++,sz[anc[i]]+=sz[i];
rep(i,1,n)
{
rt[i]=getrt(sz[i],rt[anc[i]]);
write(del(rt[i]));
}
return 0;
}
并不对劲的复健训练-bzoj5249:loj2472:p4364[2018多省联考]IIIDX的更多相关文章
- 并不对劲的复健训练-bzoj5253:loj2479:p4384:[2018多省联考]制胡窜
题目大意 给出一个字符串\(S\),长度为\(n\)(\(n\leq 10^5\)),\(S[l:r]\)表示\(S_l,S_{l+1}...,S_r\)这个子串.有\(m\)(\(m\leq 3\t ...
- 并不对劲的复健训练-CF1187D
题目大意 有两个长度为\(n\)的序列\(a_1,...,a_n\),\(b_1,...,b_n\)(\(a,b\leq n\leq 3\times 10^5\) ).一次操作是选取 \([l,r]\ ...
- 并不对劲的复健训练-bzoj5250:loj2473:p4365:[九省联考2018]秘密袭击
题目大意 有一棵\(n\)(\(n\leq 1666\))个点的树,有点权\(d_i\),点权最大值为\(w\)(\(w\leq 1666\)).给出\(k\)(\(k\leq n\)),定义一个选择 ...
- 并不对劲的复健训练-bzoj5339:loj2578:p4593:[TJOI2018]教科书般的亵渎
题目大意 题目链接 题解 先将\(a\)排序. \(k\)看上去等于怪的血量连续段的个数,但是要注意当存在\(a_i+1=a_{i+1}\)时,虽然它们之间的连续段为空,但是还要算上:而当\(a_m= ...
- 并不对劲的复健训练-CF1205B Shortest Cycle
题目大意 有\(n\)(\(n\leq 10^5\))个数\(a_1,...,a_n\)(\(a\leq 10^{18}\)).有一个图用这个方法生成:若\(a_i\)按位与\(a_j\)不为0,则在 ...
- 并不对劲的复健训练-p5212 SubString
题目大意 有一个串\(s\),一开始只知道它的一个前缀.有\(q\)(\(q\leq 10^4\))个操作,操作有两种:1.给一个字符串,表示\(s\)(\(s\)总长\(\leq 6\times 1 ...
- 并不对劲的复健训练-bzoj5301:loj2534:p4462 [CQOI2018]异或序列
题目大意 给出一个序列\(a_1,...,a_n\)(\(a,n\leq 10^5\)),一个数\(k\)(\(k\leq 10^5\)),\(m\)(\(m\leq10^5\))次询问,每次询问给\ ...
- 并不对劲的复健训练-p3674
题目大意 给出序列$ a_1,...,a_n $ ( $ n\leq10^5,a\leq 10^5 $ ),有\(m\) ( \(m\leq 10^5\))个以下三类询问: (1)给出\(l,r,k\ ...
- 2019NOIP算法复健+学习
前言: 原本因为kma太弱,很多算法没学学了也不会用,打算设置密码给自己看.后来想了想,觉得也没有必要,既然决定了要学些东西到脑子里,就没什么好丢人的. 注:"×"意为完全没学,& ...
随机推荐
- java程序显示log日志信息的方法
首先需要引入maven依赖 <dependency> <groupId>commons-logging</groupId> <artifactId>co ...
- 后盾网lavarel视频项目---Vue项目使用vue-awesome-swiper轮播插件
后盾网lavarel视频项目---Vue项目使用vue-awesome-swiper轮播插件 一.总结 一句话总结: vue中的插件的使用和js插件的使用一样的简单,只是vue插件的引入过程有些不同 ...
- ThinkPHP空操作与命名空间
命名空间:相当于一个虚拟的目录 正常管理文件使用文件夹--物理区分 TP框架的初始命名空间是:ThinkPHP\Library 在TP框架下命名空间里面使用\代表的是初始命名空间(ThinkPHP\L ...
- 1753 -- Flip Game
Flip Game Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 48663 Accepted: 20724 Descr ...
- 数据库 | SQL 诊断优化套路包,套路用的对,速度升百倍
本文出自头条号老王谈运维,转载请说明出处. 前言 在DBA的日常工作中,调整个别性能较差的SQL语句是一项富有挑战性的工作.面对慢SQL,一些DBA会心烦,会沮丧,会束手无措,也会沉着冷静.斗智斗勇! ...
- ios-tableViewcell展开与收缩动画处理
[前言] 在使用华尔街见闻 app 时,看到它的 tableVeiw 上的 cell 具有很好的展开与收缩功能.于是自己想了一下实现,感觉应该挺简单的,于是心痒痒写个 demo 实现一波.华尔街见闻 ...
- easyU之tabs选项卡
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <hea ...
- java基础内存基础详解
堆区: 1.存储的全部是对象,每个对象都包含一个与之对应的class的信息.(class的目的是得到操作指令) 2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对 ...
- 小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_3-4.动态Sql语句Mybaties SqlProvider
笔记 4.动态Sql语句Mybaties SqlProvider 简介:讲解什么是动态sql,及使用 1. @UpdateProvider(type=VideoSqlP ...
- 用python做线性规划
scipy.optimize.linprog(c, A_ub=None, b_ub=None, A_eq=None, b_eq=None, bounds=None, method='simplex', ...