20171206校内训练

我们可以发现,所有数要么被删,要么+1(-1,0)是某个质数的倍数。

由于整段序列不能全部被删,所以第一个数或者最后一个数一定会被保留。

这里有6种情况c1-1,c1,c1+1,cn-1,cn,cn+1。

考虑对于每一种情况分别处理。设这个数为x

那么其中的每个数都必须被删或者是x的质因数的倍数。

枚举每个x的质因数(记为y)

于是我们可以dp

dp[i][0/1/2]表示前i个还没开始删除/正在删除/删完了的最小花费。

dp[i][0/1/2]==INF表示无法把前i个数做为y的倍数。显然dp[i][1]和dp[i][2]不可能==INF

dp[i][0]=B(x==c1-1,c1+1,cn-1,cn+1恐怖操作)或dp[i][0]=0(x==c1,cn不进行恐怖操作)

dp[i][0]=dp[i-1][0](c[i]%y==0,当前第i个数不用任何花费)dp[i][0]=dp[i-1][0]+b(c[i]±1%y==0,当前第i个数需要进行恐怖操作)

dp[i][1]=min(dp[i-1][0],dp[i-1][1])(当前第i个数可以从还没开始删除的第i-1个数或者正在删除的第i-1个数删除第i个数而来)

dp[i][2]显然和dp[i][0]是一样的,把dp[i-1][0]换成min(dp[i-1][1],dp[i-1][2])(第i-1个数可以正在删除或者本来就结束了)

把所有有关c1的处理好后翻转序列继续处理即可。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
long long dp[1001000][3];int c[1010000];
int prime[1000000],tot=0;bool isprime[1000000];
int q[1000000],top=0;
int n;long long A,B;
long long INF=2893606913523066920ll;
long long ans=2893606913523066920ll;
void Prime()
{
    for(int i=2;i<=100000;i++)
    {
        if(!isprime[i])prime[++tot]=i;
        for(int j=1;j<=tot&&prime[j]*i<=100000;j++)
        {
            isprime[prime[j]*i]=1;if(i%prime[j]==0)break;
        }
    }
}
void fj(int x)
{
    for(int i=1;prime[i]*prime[i]<=x;i++)
    {
        if(x%prime[i]==0)q[++top]=prime[i];
        while(x%prime[i]==0)x/=prime[i];
    }
    if(x!=1)q[++top]=x;
} 
void Solve(int x,int ok)
{
    top=0;fj(x);
    for(int i=1;i<=top;i++)
    {
        memset(dp,40,sizeof(dp));
        dp[1][0]=ok;
        for(int j=2;j<=n;j++)
        {
            if(c[j]%q[i]==0)dp[j][0]=dp[j-1][0];
            else if(dp[j-1][0]!=INF&&((c[j]+1)%q[i]==0||(c[j]-1)%q[i]==0))dp[j][0]=dp[j-1][0]+B;
            dp[j][1]=min(dp[j-1][1],dp[j-1][0])+A;
            if(c[j]%q[i]==0)dp[j][2]=min(dp[j-1][2],dp[j-1][1]);
            else if((c[j]+1)%q[i]==0||(c[j]-1)%q[i]==0)dp[j][2]=min(dp[j-1][2],dp[j-1][1])+B;
        }
        ans=min(ans,min(dp[n][0],min(dp[n][1],dp[n][2])));
    }
}
int main()
{
//    freopen("seq.in","r",stdin);freopen("seq.out","w",stdout);
    Prime();
    scanf("%d%lld%lld",&n,&A,&B);
    for(int i=1;i<=n;i++)scanf("%d",&c[i]);
    Solve(c[1]-1,B);Solve(c[1],0);Solve(c[1]+1,B);
    for(int i=1;i<=n/2;i++)swap(c[i],c[n-i+1]);
    Solve(c[1]-1,B);Solve(c[1],0);Solve(c[1]+1,B);
    printf("%lld",ans);
    return 0;
} 
View Code

显然需要排序。

考虑枚举有多少个高度最大的墙(显然是把高的墙垫高),剩下的墙的最小高度的最大值可以二分求出。

如何二分?

找到一块墙(记为x),当前剩余砖块能把x以前的墙全部叠到墙x的高度以上而又叠不到墙x+1的高度。

然后我们就能求出高度达到墙x的高度所需的砖块(总数-前缀和),然后能计算出可以达到的最小高度(最后剩下的砖块/x)

细节注意一下:二分右端点不能超过你枚举的多少个高度最大的墙。要保证你的砖块能够把你枚举的墙给搭到H。可以不把任何一块墙给搭到H。

打完后突然发现不用二分,根据枚举的高度最大的墙越多,剩下的墙的最小高度越小的单调性来解题即可(虽然排序还是O(nlogn))

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
long long h[100100],s[100100];
int n;long long H;int a,b;long long m;
long long ans=-1;
int main()
{
//  freopen("wall.in","r",stdin);freopen("wall.out","w",stdout);
    scanf("%d%lld%d%d%lld",&n,&H,&a,&b,&m);
    for(int i=1;i<=n;i++)scanf("%lld",&h[i]);
    sort(h+1,h+n+1);
    for(int i=1;i<=n;i++)s[i]=s[i-1]+h[i];
    if(H*n-s[n]<=m){printf("%d",n*(a+b));return 0;}
    for(int i=2;i<=n+1;i++)
    {
        if((n-i+1)*H-(s[n]-s[i-1])>m)continue;
        long long left=m-((n-i+1)*H-(s[n]-s[i-1]));
        int l=1,r=i;
        while(l<r)
        {
            int mid=(l+r)>>1;
            if(h[mid]*mid-s[mid]<=left)l=mid+1;
            else r=mid;
        }
        long long Min=(left-(h[l-1]*(l-1)-s[l-1]))/(l-1)+h[l-1];
        ans=max(ans,Min*b+(n-i+1)*a);
    }
    printf("%lld",ans);
    return 0;
}
View Code

显然能切就切是最优的,考虑线段树维护,每个节点(每天)记一下这个区间的答案(data.ans),剩下多少实力(data.str),剩下多少题目(data.pro)。

由于D大爷的三种阶段的实力不同。所以我们对于线段树的每一个节点,我们开3个data表示当前区间内的每个叶子节点分别拥有b,0,a的实力。

cut表示切题。用当天获得的实力去切当天的题目。

pushup表示用左半部分的实力去切右半部分的题目。在此之前,左半部分的实力已经切完左半部分的题目,右半部分的实力已经右完左半部分的题目。

如何求答案?

对于每段状态,我们分别求一下这段的答案(这就是我们开3个data的意图)。然后,我们用b状态的剩下的实力去切0状态的题。用b状态和0状态总共剩下的实力去切a状态的题。这样就能求出答案。

#include<iostream>
#include<cstdio>
using namespace std;
struct data{
    int str,pro,ans;
};
struct Tree{
    int l,r;data s[3];
}tree[400100];
int A,B;
data cut(data a,int k)
{
    a.pro+=k;
    int add=min(a.str,a.pro);
    a.ans+=add;a.pro-=add;a.str-=add;
    return a;
}            
data pushup(data a,data b)
{
    data c;
    int add=min(a.str,b.pro);
    c.ans=a.ans+b.ans+add;
    c.str=a.str+b.str-add;
    c.pro=a.pro+b.pro-add;
    return c;
}
void build(int l,int r,int x)
{
    if((tree[x].l=l)==(tree[x].r=r))
    {
        tree[x].s[0]={B,0,0};
        tree[x].s[1]={0,0,0};
        tree[x].s[2]={A,0,0};
        return;
    }
    int mid=(l+r)>>1;
    build(l,mid,x<<1);build(mid+1,r,x<<1|1);
    for(int i=0;i<3;i++)tree[x].s[i]=pushup(tree[x<<1].s[i],tree[x<<1|1].s[i]); 
}
void add(int x,int k,int a)
{
    if(tree[x].l==tree[x].r&&tree[x].l==k)
    {
        for(int i=0;i<3;i++)tree[x].s[i]=cut(tree[x].s[i],a);
        return;
    }
    int mid=(tree[x].l+tree[x].r)>>1;
    if(k<=mid)add(x<<1,k,a);else add(x<<1|1,k,a);
    for(int i=0;i<3;i++)tree[x].s[i]=pushup(tree[x<<1].s[i],tree[x<<1|1].s[i]); 
}
data query(int x,int l,int r,int v)
{
    if(l>r)return data{0,0,0};
    if(tree[x].l==l&&tree[x].r==r)return tree[x].s[v];
    int mid=(tree[x].l+tree[x].r)>>1;
    if(r<=mid)return query(x<<1,l,r,v);
    else if(l>mid)return query(x<<1|1,l,r,v);
    else return pushup(query(x<<1,l,mid,v),query(x<<1|1,mid+1,r,v));
}
int main()
{
    int n,k,q;scanf("%d%d%d%d%d",&n,&k,&A,&B,&q);
    build(1,n,1);
    for(int i=1;i<=q;i++)
    {
        int type,x,y;scanf("%d",&type);
        if(type==1){scanf("%d%d",&x,&y);add(1,x,y);}
        if(type==2){scanf("%d",&x);printf("%d\n",pushup(pushup(query(1,1,x-1,0),query(1,x,x+k-1,1)),query(1,x+k,n,2)).ans);}
    }
    return 0;
}
View Code

 

posted @ 2017-12-07 21:55  lher  阅读(166)  评论(0编辑  收藏  举报