[BZOJ2006 [NOI2010]超级钢琴]【RMQ】【二叉堆】【区间裂解】

【题目链接】http://61.187.179.132:8080/JudgeOnline/showproblem?problem_id=2006
【算法分析】
就是搞成前缀和数组以后写一个rmq。然后在外面套一个二叉堆每次取最大值。然后把取出的区间裂解成两半,去掉中间那个最小值元素,继续放进二叉堆里,这样就可以AC。
T_T时间排在第3名。前面两个都是JZP神牛。
【时间复杂度】O((n+m) lg (n+m))
【空间复杂度】O(n lg n + n+m)
【吐槽】
NOI的时候写得二分答案+平衡树被卡得只剩30分T_T。。。
现在高3一个月只能回一次家。。。本次是因为配眼镜为由请假回家,就找些题练练手= =,准备我巨重要的NOIP。
【CODE】
#include #include #include #include using namespace std;
typedef __int64 lld;
const int N=500005;
int n,m,low,high;
int a[N];
int lg[N];
int zz[19][N];
lld ST[19][N];

int Get(int l,int r){
int k=lg[r-l+1];
if (ST[k][l] else return zz[k][r-(1<}

struct Heap_t{
struct Node{int l,r,i;lld key;}Q[N+N];
int tot;

void up(int k){
while (k>1 && Q[k>>1].key swap(Q[k>>1],Q[k]);
k>>=1;
}
}

void down(int k){
int p;
while (k<<1<=tot){
p=k<<1;
if (p if (Q[p].key>Q[k].key){
swap(Q[p],Q[k]);
k=p;
}
else break;
}
}

void Insert(int l,int r,int i){
if (l>r) return;
tot++;
Q[tot].l=l; Q[tot].r=r; Q[tot].i=i; Q[tot].key=ST[0][i]-ST[0][Get(l,r)];
up(tot);
}

void Del(){
Node tmp=Q[1];
Q[1]=Q[tot–];
down(1);
int pos=Get(tmp.l,tmp.r);
Insert(tmp.l,pos-1,tmp.i);
Insert(pos+1,tmp.r,tmp.i);
}

void init(){
int i;
for (tot=0,i=1;i<=n;i++)
Insert(max(0,i-high),i-low,i);
}

void solve(){
lld ans=0;
while (m){
ans+=Q[1].key;
Del();
m–;
}
printf("%I64dn",ans);
}
}Heap;

void init(){
scanf("%d%d%d%d",&n,&m,&low,&high);
int k,s,i;
for (i=1;i<=n;i++) scanf("%d",&a[i]);
for (i=1;i<=n;i++) {ST[0][i]=ST[0][i-1]+a[i]; zz[0][i]=i;}
for (k=1;1< s=1< for (i=0;i+s+s-1<=n;i++)
if (ST[k-1][i] ST[k][i]=ST[k-1][i];
zz[k][i]=zz[k-1][i];
}
else{
ST[k][i]=ST[k-1][i+s];
zz[k][i]=zz[k-1][i+s];
}
}
for (i=1,k=0;i<=n;i++)
lg[i]=k+=(i==(1<}

int main(){
init();
Heap.init();
Heap.solve();
}

[ZOJ3385 Hanami Party]【贪心】【栈】★★

【题目大意】
一开始主角有一个能力值L,然后有n天,每天他可以执行下列两个操作中的其中一个:
1:自身能力值+1
2:制造与自己能力值相等个单位的食物。
另外,每天食物都会被饿鬼吃掉一些,但是主角又不能惹怒这个饿鬼。
求在不惹怒这个饿鬼的情况下,最后剩余食物最多能是多少?
如果必须惹怒,那么输出Myon。
【算法分析】
下面的 1 2分别表示操作1和操作2。
本题可以利用贪心来解决。
我们的策略应该是尽量升级,然后必须造食物才造。就是升级的尽量前推。
因为如果出现 2,1这种情况,而1和2可以交换而使得没有出现饿鬼被惹怒的情况,那么显然交换以后能造多一个单位的食物。所以升级是应该被尽量前推的。
可以发现其实我们是将这个操作分组,分成这样的数段:(1,2,2…2),(1,2,2…..2),(1,2,2….2)
(一开始在0天时我们看做操作1)
现在我们只关心操作1的划分。
为了实现我们的策略,那么我们对于前i天,都求出最多所能进行操作1多少次。
这可以用一个栈来解决。
这个栈里面存的是每次操作1所发生的时间。
然后每次把今天是操作1放进栈内。
如果栈尾的元素到今天不能满足饿鬼的需求,那么退栈,
这样我们维护的一个栈是所有升级操作都尽量前推好的了。并且到了第i天所能升级最多次的。
当然,如果栈里连第0天那个虚拟的升级都被退了,那么就无解了。




现在可能还有一些疑问,那就是升级多了固然会把饿鬼惹怒,但是有时候升级少了我们也会把饿鬼惹怒的。
比如说一开始能力值为0,然后你造食物多少天也没用。这样还是会惹怒饿鬼。
就是说,不能保证退栈以后依然是可行解。




现在我们再来看看一个东西。
假如说,我们退栈导致某个地方饿鬼不爽了。
那么我们从这个饿鬼不爽的地方分开来看。
假设饿鬼不爽这一天为j。
对于我们现在的第i天来讲,我们在第j天时,退栈前,能力强,剩余的食物也多。
(因为栈的大小代表升级的次数,并且原来饿鬼在第j天时爽的,退栈以后不爽,可以说明剩余食物变少了)
那么既然这样,如果会使得该解退栈后成为非可行解的,对于第i天来讲,是有害无利。
但是退栈的条件是升级这么多不行了。
所以,其实如果走出了这一步退栈,实际就是无解了。
我们会一直退到栈空为止。判为无解。




做完了第n天以后,我们还要把所有元素退栈,因为到了后面可能造食物更为划算。
然后这里退栈造成的非可行解问题,我们上面已经说明,如果是造成了非可行解,那么答案只会更差。
而我们取的最优值,所以一起判断也没关系。
然后,我们就得出了这个O(n)的贪心算法。
【其它】
泪流满面,3Y。原因是这个ZOJ的int64。。。居然这么标准,和NOI一样用long long+%lld。。。一开始就搞%I64d。搞挂了。
其实一开始我是看了一下月赛的Ranklist,发现最后一题好像每个人都A了。。。
然后想去切一下准备NOIP。。。
切了好一会儿终于切掉该题,感觉怎么现在的人都这么强T_T,这种题成为众人秒杀类题目。
颓然发现,由于题目过多,他那个Ranklist在我的浏览器下,下面还有横条。原来我看到众人秒杀的是倒数第二题。。。而最后一题只有navi大神AC。。。
【CODE】
#include #include #include #include using namespace std;
typedef long long lld;
const lld N=100005;
lld n,init_L,Qt;
lld a[N],Q[N],Rest_Food[N];
lld Sum[N];

int main(){
while (scanf("%lld%lld",&n,&init_L)!=EOF){
lld i,j,ans;
for (i=1;i<=n;i++) scanf("%lld",&a[i]);
for (Sum[0]=0,i=1;i<=n;i++) Sum[i]=Sum[i-1]+a[i];
Qt=Rest_Food[0]=Q[0]=0;
for (i=1;i<=n;i++){
Qt++;
Q[Qt]=i;
Rest_Food[Qt]=Rest_Food[Qt-1]+(init_L+Qt-1)*(i-Q[Qt-1]-1)-(Sum[i]-Sum[Q[Qt-1]]);
while (Qt>=0 && Rest_Food[Qt]+(i-Q[Qt])*(init_L+Qt) if (Qt<0) break;
}
if (Qt<0) puts("Myon");
else{
ans=0;
while (Qt>=0){
if (Rest_Food[Qt]+(n-Q[Qt])*(init_L+Qt)-(Sum[n]-Sum[Q[Qt]])>ans)
ans=Rest_Food[Qt]+(n-Q[Qt])*(init_L+Qt)-(Sum[n]-Sum[Q[Qt]]);
Qt–;
}
printf("%lldn",ans);
}
}
}

[ZOJ3365 Integer Numbers]【排序】【二分】

【题目大意】
给定一个序列,让你改变最少的数,使之变成形如这样的模式x,x+1,x+2,x+3….x+n。
【算法分析】
不考虑改变什么数,只考虑不用改变什么数,如果不用改变的话,那么当且仅当i-a[i]为同样的值。
而且最终结果不可能全都改变,所以可以以i-a[i]把他们分类,个数最多的一类就可以作解,最后输出即可。
这里面用到类似离散化的思想,中途用二分找位置。当然,hash也是可以的。
【其它】1Y
【CODE】
#include #include #include #include using namespace std;
const int N=50000;
int n;
int a[N];
int F[N],L[N];

void solve(){
int l,r,mid,i,ans=0,Maxi;
for (i=0;i l=0; r=n-1;
while (l mid=l+r>>1;
if (L[mid] else r=mid;
}
F[l]++;
}
for (i=0;i if (F[i]>ans){
ans=F[i];
Maxi=i;
}
printf("%dn",n-ans);
for (i=0;i if (i!=0) putchar(‘ ‘);
printf("%d",i-L[Maxi]);
}
putchar(‘n’);
}

int main(){
while (scanf("%d",&n)+1){
int i;
for (i=0;i scanf("%d",&a[i]);
L[i]=i-a[i];
F[i]=0;
}
sort(L,L+n);
solve();
}
}

[ZOJ3362 Beer Problem]【最大费用流】【Not 最大费用最大流】【流中无向边与有向边的详细YY】

【题目大意】
1是源点,其它的n-1个点都可以流出。每滴水所得的费用w[i]。
然后给出一些无向边,流走过这些边,每滴水要消耗费用,并且只能有c滴水通过这条路。
就是比如给定(x,y),容量为c。
从x到y流过f1滴水,从y到x流过f2滴水,那么f1+f2<=c。
求怎样得到最大利润。
【算法分析】
把那n-1个点汇到一个超级汇中作最大费用最大流就行了。
至于无向边的问题,我们只要加两条有向边就行了。
大牛可以无视下面这个入门问题。。。= =
那么我们现在看一下为啥加两条有向边是对的。
假设一条无向边(x,y),他允许通过流量为c。
我们现在搞成两个有向边。
1、x->y,容量为c
2、y->x,容量为c
我们再假设最终完成一个流以后,x->y的流为f1,y->x的流为f2,由于对称,不妨设f1>=f2。
那么本质可以变成x->y的流为f1-f2,y->x的流为0。
我们这样考虑:
本来有f2的流S->y->x->T。
同时有f1的流S->x->y->T,并有f1>=f2。
那么现在S->y上的流有f2这么多被分到y->x上了,我们现在把他扯回来。存在y结点上。
于是y点存的水e(y)=f2
再把S->x->y的流扯回来,存在x结点上。
于是x点存的水e(x)=f1
然后现在观察发现。
x->T部分和原来相比,缺了f2,需要补充f2的流来满足流量守恒。
y->T部分和原来相比,缺了f1,需要补充f1的流。
然后我们把e(x)中f1的流分配给x->T这条路,那么分完以后e(x)=f1-f2。
然后再把e(y)中f2的流分配给y->T这条路,那么分完以后e(y)=0。并且y->T这条路还缺少f1-f2的流补充。
于是最后,把e(x)中的流,通过x->y,补给到y->T这条路上。
就完成了这个转化。
所以,凡是x->y中有流,且y->x中也有流的结果,我们都可以转化为只有一条有流的结果。
并且边上的流都小于等于原来的。
这样,就证明了这样加边最后必然与合法解所对应,如果要输出方案的话,把所有这种对应边的流量相互抵消以后输出,就可以得到满足条件的解。
/
/
/
/
/
但是,我们只是证明了无费用的情况。
如果按照这样转化总费用是会变化的!
对于(x,y)这条边而言,一开始取了f1+f2个单位的费用,而转化以后只取了f1-f2的费用。
我们现在再分情况仔细YY一下:
1、求最大费用流,如果该无向边权值>0,显然无法求。。。就像有正权环的最长路一样。
但是如果该无向边的权值<0,显然按照上面的推导,f1+f2>f1-f2。(假设f1>=f2 && f1,f2>0)。
我们的费用流不会这么笨去取那个更差的解。
2、求最小费用流,基本上面的词取反义词就是了T_T。

还有,本题求的是最大费用流,流不一定要最大,只要流满足限制即可。我们找增广路如果找到增广费用小于等于0,就可以马上停止了。
如果是最大费用最大流,找增广路的话,要找到找不到为止。
【其它】1Y。呃~希望有同样困惑的童鞋们看完可以清楚一点。
【CODE】
#include #include #include #include #include using namespace std;
const int N=105;
const int E=40000;
const int INF=0x7FFFFFFF;
struct edge{int x,y,c,w;edge *next,*op;}g[E],*ls[N],*fa[N];
int m,n,S,T,e,cost;
int d[N],v[N],L[N];

void addedge(int x,int y,int c,int w){
g[++e].x=x; g[e].y=y; g[e].c=c; g[e].w= w; g[e].next=ls[x]; ls[x]=&g[e]; g[e].op=&g[e+1];
g[++e].x=y; g[e].y=x; g[e].c=0; g[e].w=-w; g[e].next=ls[y]; ls[y]=&g[e]; g[e].op=&g[e-1];
}

void init(){
for (int i=1;i<=n+1;i++) ls[i]=NULL;
e=-1; S=1; T=n+1;
for (int w,i=2;i<=n;i++){
cin >> w;
addedge(i,T,INF,w);
}
for (int x,y,c,w,i=1;i<=m;i++){
cin >> x >> y >> c >> w;
addedge(x,y,c,-w);
addedge(y,x,c,-w);
}
}

bool spfa(){
for (int i=S;i<=T;i++){
d[i]=-INF;
v[i]=0;
}
d[S]=0; v[S]=1; L[1]=S;
int head=0,tail=1;
while (head!=tail){
head=(head+1)%N;
for (edge *t=ls[L[head]];t;t=t->next)
if (t->c && d[t->x]+t->w>d[t->y]){
d[t->y]=d[t->x]+t->w;
fa[t->y]=t;
if (!v[t->y]){
v[t->y]=1;
L[tail=(tail+1)%N]=t->y;
}
}
v[L[head]]=0;
}
return d[T]>0;
}

void change(){
int nf=INF;
for (int i=T;i!=S;i=fa[i]->x)
nf=min(nf,fa[i]->c);
cost+=nf*d[T];
for (int i=T;i!=S;i=fa[i]->x){
fa[i]->c-=nf;
fa[i]->op->c+=nf;
}
}

void MCMF(){
cost=0;
while (spfa()) change();
cout << cost << 'n';
}

int main(){
ios::sync_with_stdio(false);
while (cin >> n >> m){
init();
MCMF();
}
}

[RQNOJ469 喜鹊 (HDOJ3552 I can do it!加强版)]【叉姐NOIP模拟题】【排序】【平衡树】

【题目大意】

给定n个物品,第i个物品有三个属性Ai,Bi,Ci。然后把这个n个物品分别分到3个集合中(集合可以为空),使得A集合中的物品的Max{Ai}+B集合中的物品的Max{Bi}+C集合中物品的Max{Ci}最小。(空集算作0)

【算法分析】

HDOJ3552是只有两个属性,那么按Ai排序,然后枚举Max{Ai}。然后剩下的另一边的Max{Bi}加起来就是当前值。

然后对于本题。依然按Ai排序,仍然从大到小枚举Max{Ai}。然后对剩下的另一边维护上面那个问题。

显然,若存在Bi<=Bj Ci<=Cj,那么i这个物品就没用了。那么我们可以维护一个线性表,使得他关于Bi递增。同时,删掉多余的物品以后,关于Ci递减。那么很显然,我们取某一个物品截开,那么这边取的值就是Bi+Ci+1,然后这种动态线性表显然就可以用平衡树维护。然后为了比较方便找前驱和后继,我用了Splay。

【其它】1Y。这次把Splay函数和rotate函数压了代码,Splay只有两个rotate,rotate里不用分类讨论。

【CODE】

#include

#include

#include

#include

#include

using namespace std;

const int N=105555;

const int INF=1000000000;

int n,root;

struct Node{int x,y,z;}a[N];

int pre[N],best[N],w[N],ch[N][2];

bool cmp(const Node &A,const Node &B){

     return A.x

}

void init(){

     int i;

     scanf("%d",&n);

     for (i=1;i<=n;i++) scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);

     sort(a+1,a+n+1,cmp);

     memset(ch,0,sizeof(ch));

     memset(pre,0,sizeof(pre));

     root=n+1;

     ch[n+1][0]=n+2;

     pre[n+2]=n+1;

    

     a[n+1].y=INF; a[n+1].z=0;

     a[n+2].y= 0; a[n+2].z=INF;

    

     w[n+1]=INF; best[n+1]=0;

     w[n+2]=best[n+2]=0;

}

void update(int x){

     best[x]=INF;

     if (ch[x][0]) best[x]=min(best[ch[x][0]],best[x]);

     if (ch[x][1]) best[x]=min(best[ch[x][1]],best[x]);

     best[x]=min(w[x],best[x]);

}

void rotate(int x,int T){

     int y=pre[x];

     ch[y][!T]=ch[x][T];

     ch[x][T]=y;

     pre[x]=pre[y];

     pre[y]=x;

     pre[ch[y][!T]]=y;

     if (ch[pre[x]][0]==y) ch[pre[x]][0]=x;

                      else ch[pre[x]][1]=x;

     update(y);

     update(x);

}

void Splay(int x,int goal){

     int y,z;

     if (x==0 || x==goal) return;

     if (goal==0) root=x;

     while (pre[x]!=goal){

           y=pre[x]; z=pre[y];

           if (z!=goal) rotate(ch[z][0]==y^ch[y][0]==x?x:y,ch[y][0]==x);

           rotate(x,ch[pre[x]][0]==x);

     }

}

void Get_next(){

     int p=ch[root][1],q=root;

     while (p){

           q=p;

           p=ch[p][0];

     }

     Splay(q,0);

}

void Get_pre(){

     int p=ch[root][0],q=root;

     while (p){

           q=p;

           p=ch[p][1];

     }

     Splay(q,0);

}

bool Can_Insert(int i){

     int p=root,q;

     while (p){

           q=p;

           p=ch[p][a[p].y

     }

     Splay(q,0);

     if (a[q].y

     if (a[root].y>=a[i].y && a[root].z>=a[i].z) return false;

                                            else return true;

}

void (){

     int p=ch[root][0];

     while (ch[p][1]) p=ch[p][1];

     Splay(p,root);

     ch[p][1]=ch[root][1];

     pre[ch[root][1]]=p;

     pre[p]=0;

     root=p;

}

void Delete_Useless_Node(int i){

     while (1){

       if (a[root].y>a[i].y) Get_pre();

       if (a[root].y<=a[i].y && a[root].z<=a[i].z)

         ();

       else

         break;

     }

}

void Insert(int i){

     int p=root,q;

     while (p){

           q=p;

           p=ch[p][a[p].y

     }

     pre[i]=q;

     ch[q][a[q].y

     Splay(i,0);

     p=ch[i][1];

     while (ch[p][0]) p=ch[p][0];

     w[i]=a[i].y+a[p].z;

     Splay(p,i);

     update(i);

     Get_pre();

     w[root]=a[root].y+a[i].z;

     update(root);

}

void Try_Insert(int i){

     if (!Can_Insert(i)) return;

     Delete_Useless_Node(i);

     Insert(i);

}

    

void work(){

     int i,ans=INF,now;

     a[0].x=0;

     for (i=n;i>=0;i–){

         now=a[i].x;

         now+=best[root];

         if (now

         Try_Insert(i);

     }

     printf("%dn",ans);

}

int main(){

    init();

    work();

    return 0;

}