[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;

}

[HDOJ3553 Just a String]【后缀数组搞的像Treap一样的伪后缀树】★★

【题目大意】
一个串的子串定义为其中连续的某一段。
给定一个串,问他的所有子串中,字典序为m的是哪一个子串?
【算法分析】
这个是典型的后缀树问题。只需深度优先遍历一下后缀树就很容易得到这个解。
现在问题来了。。。我不会O(n)构造后缀树。
好吧。那么退一步,我搞个后缀数组。
然后把height建好。然后可以发现。。。其实我们可以在这个搞好的后缀树上模拟后缀树的DFS遍历。。。
好吧,说说这种从Suffix Array->Suffix Tree的沙茶做法。
考虑现在我们已经建好height的ST表,那么我们每次取一个height最小的地方,按这个点划分成两半。然后两半在各自划分下去。
然后现在搞成了一棵heap的关键字是 height ,平衡树的关键字是 某后缀 的“Treap”。
好了。。我们现在可以在这颗“伪后缀树”上搞了。
显然,如果是真的后缀树的话,我们的做法应当是从小到大判断每个叉加起来的子串数量够不够m。然后每次只需选一个叉前进即可。
当然,有可能不前进,那就是后缀树的这个点所压缩的东西,已经足够m了。
然后现在我们来看看这颗伪后缀树和真后缀树的差别。
其实他们所不同的地方就是,假如当前节点有2个以上分叉,
那么后缀树会搞出相等数量的分叉在弄下去。
而对于现在这颗伪后缀树,我们会选择任意一个叉,然后再将它们划分成两边。
然后这样就会造成本身是同深度的叉,变成深度不同了。其实将这些叉合回来就是真后缀树了。
但是对这个题目,我们并不需要把他合回来。现在的信息已经足够我们执行上面红色的算法了。
具体就是搞一个Len_Sum[i]表示后缀数组中前i个后缀的总长度。然后就可以O(1)判断那个叉是对的。然后跑下去。并用cut这个变量表示已经算好了ans的多少长度。
最后有一些琐碎的问题,就是那个后缀树的压缩连续的冗余结点,在这个伪后缀树里体现为假设每次取的那个height最小值!=cut的情况。这时这个伪后缀树中,height最小值-cut,就是那个压缩值的长度。
【其它】2Y。T_T。那个计算要用int64。读入也要。
【CODE】
#include #include #include #include #include using namespace std;
typedef long long lld;
const int N=105555;
int n;
lld m;
char S[N];
char ans[N];
int sa[N];
int lg[N];
int Sum[N];
int rank[N];
int List[N];
int a[N][3];
int b[N][3];
int height[N];
int rmq[18][N];
int pos[18][N];
lld Len_Sum[N];

bool cmp(int i,int j){return S[i]

void Radix_sort(){
     int i,k;
     for (k=1;k>=0;k–){
         for (i=0;i<=n;i++) Sum[i]=0;
         for (i=1;i<=n;i++) Sum[a[i][k]+1]++;
         for (i=1;i<=n;i++) Sum[i]+=Sum[i-1];
         for (i=1;i<=n;i++) memcpy(b[++Sum[a[i][k]]],a[i],12);
         for (i=1;i<=n;i++) memcpy(a[i],b[i],12);
     }
}

void make_suffix_arr(){
     int i,cnt,k,s;
     for (i=1;i<=n;i++) List[i]=i;
     sort(List+1,List+n+1,cmp);
     for (cnt=0,i=1;i<=n;i++)
       rank[List[i]]=( cnt+=(i==1 || S[List[i]]!=S[List[i-1]]) );
     for (k=1;cnt!=n;k++){
         s=1<<(k-1);
         for (i=1;i<=n;i++){
             a[i][0]=rank[i];
             a[i][1]=(i+s>n)?0:rank[i+s];
             a[i][2]=i;
         }
         Radix_sort();
         for (cnt=0,i=1;i<=n;i++)
           rank[a[i][2]]=( cnt+=(i==1 || a[i][0]!=a[i-1][0] || a[i][1]!=a[i-1][1]) );
     }
     for (i=1;i<=n;i++) sa[rank[i]]=i;
}

void make_height(){
     memset(height,0,sizeof(height));
     int i,j,k,st;
     for (i=1;i<=n;i++){
         if (rank[i]==1) continue;
         st=max(0,height[rank[i-1]]-1);
         j=i+st; k=sa[rank[i]-1]+st;
         while (j<=n && k<=n && S[j]==S[k]){j++; k++; st++;}
         height[rank[i]]=st;
     }
}

void build_rmq(){
     int i,k;
     for (i=1;i<=n;i++){
         rmq[0][i]=height[i];
         pos[0][i]=i;
     }
     for (k=1;1<       for (i=1;i+(1<         if (rmq[k-1][i]<=rmq[k-1][i+(1<           rmq[k][i]=rmq[k-1][i];
           pos[k][i]=pos[k-1][i];
         }
         else{
           rmq[k][i]=rmq[k-1][i+(1<           pos[k][i]=pos[k-1][i+(1<         }
}

int Get_Min_Pos(int l,int r){
    if (l>r) swap(l,r);
    int k=lg[r-l+1];
    return rmq[k][l]<=rmq[k][r-(1<}

void Output(int i,int Len){
     int j;
     for (j=0;j       putchar(S[sa[i]+j]);
     putchar(‘n’);
}

void solve(){
     lld cnt=0,cut=0,l=1,r=n,i,res;
     Len_Sum[0]=0;
     for (i=1;i<=n;i++)
       Len_Sum[i]=Len_Sum[i-1]+(n-sa[i]+1);
     while (l           i=Get_Min_Pos(l+1,r);
           res=height[i]-cut;
           if (cnt+res*(r-l+1)>=m){
             Output(l,(m-cnt-1)/(r-l+1)+1+cut);
             return;
           }
           cnt+=res*(r-l+1);
           cut+=res;
           if (Len_Sum[i-1]-Len_Sum[l-1]-(i-l)*cut+cnt>=m)
             r=i-1;
           else{
             cnt+=Len_Sum[i-1]-Len_Sum[l-1]-(i-l)*cut;
             l=i;
           }
     }
     Output(l,m-cnt+cut);
}

int main(){
    int Tc,i;
    lg[1]=0;
    for (i=2;i      if (i==1<                      else lg[i]=lg[i-1];
    scanf("%d",&Tc);
    for (i=1;i<=Tc;i++){
        scanf("%s %I64d",S+1,&m);
        printf("Case %d: ",i);
        n=strlen(S+1);
        make_suffix_arr();
        make_height();
        build_rmq();
        solve();
    }
}