0x57 倍增优化DP

真的是下定了巨大的决心来搞这一讲,果不其然耗了一晚上

开车旅行(真的是NOIP的题吗怎么这么恐怖)

首先,先用set把小A和小B从城市i出发,到达的下一个城市预处理出来。

f[i][j][k]表示走了2^i天,j城市出发,k表示谁开车,到达那个城市。

转移就是f[i][j][k]=f[i-1][f[i-1][j][k]][k] 相信很好理解

特别的i-1==0时,因为k是奇数,开车的人是变化的,所以f[i][j][k]=f[i-1][f[i-1][j][k]][k^1]

令da[i][j][k],db[i][j][k],分别表示小A和小B这个状态下走的路程

对于询问1,枚举所有的城市作为起点,然后基于二进制划分的思想,倒着for一步步在满足走的总路程<=x0的情况下前进,这样可以计算出小A走的路程和小B走的路程。找最大比值即可。

询问二就直接询问小A走的路程和小B走的路程了。同理可求。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<set>
using namespace std;
typedef long long LL;

int h[110000];
struct snode
{
    int id,h;
    snode(){}
    snode(int ID,int H){id=ID;h=H;}
    friend bool operator <(snode s1,snode s2){return s1.h<s2.h;}
};
set<snode>S;
set<snode>::iterator it,lt,rt;
int g[110000],I;
bool cmp(int g1,int g2){return (abs(h[g1]-h[I])<abs(h[g2]-h[I]))||(abs(h[g1]-h[I])==abs(h[g2]-h[I])&&h[g1]<h[g2]);}

int Ago[110000],Bgo[110000];
int f[22][110000][2]; LL da[22][110000][2],db[22][110000][2];

LL A,B;
void solve(int now,int x0)
{
    A=B=0; int k=0;
    for(int i=20;i>=0;i--)
      if(f[i][now][k]!=0&&da[i][now][k]+db[i][now][k]<=x0)
      {
            x0-=da[i][now][k]+db[i][now][k];
            A+=da[i][now][k],B+=db[i][now][k];
            if(i==0)k^=1;
            now=f[i][now][k];
        }
}

int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&h[i]);
    memset(Ago,0,sizeof(Ago));
    memset(Bgo,0,sizeof(Bgo));
    for(int i=n;i>=1;i--)
    {
        S.insert(snode(i,h[i]));
        it=lt=rt=S.find(snode(i,h[i]));
        int m=0;
        if(lt!=S.begin())
        {
            lt--,g[++m]=(*lt).id;
            if(lt!=S.begin())lt--,g[++m]=(*lt).id;
        }
        rt++;
        if(rt!=S.end())
        {
            g[++m]=(*rt).id;
            rt++;if(rt!=S.end())g[++m]=(*rt).id;
        }
        I=i;sort(g+1,g+m+1,cmp);
        if(m>1)Ago[i]=g[2];
        if(m>0)Bgo[i]=g[1];
    }
    
    memset(f,0,sizeof(f));
    for(int i=1;i<=n;i++)
    {
        if(Ago[i]!=0) f[0][i][0]=Ago[i], da[0][i][0]=abs(h[Ago[i]]-h[i]), db[0][i][0]=0;
        if(Bgo[i]!=0) f[0][i][1]=Bgo[i], da[0][i][1]=0, db[0][i][1]=abs(h[Bgo[i]]-h[i]);
    }
    for(int i=1;i<=20;i++)
      for(int j=1;j<=n;j++)
        for(int k=0;k<=1;k++)
            {
                int l=k;if(i==1)l=k^1;
                
                if(f[i-1][j][k]>0)f[i][j][k]=f[i-1][f[i-1][j][k]][l];
                if(f[i][j][k]>0)
                {
                    da[i][j][k]=da[i-1][j][k]+da[i-1][f[i-1][j][k]][l];
                    db[i][j][k]=db[i-1][j][k]+db[i-1][f[i-1][j][k]][l];
                }
            }
        
    int x0,ans;LL ansA=1,ansB=0;
    scanf("%d",&x0);
    for(int i=1;i<=n;i++)
    {
        solve(i,x0); if(B==0)A=1;
        if(A*ansB<ansA*B||(A*ansB==ansA*B&&h[i]>h[ans]))ansA=A,ansB=B,ans=i;
    }
    printf("%d\n",ans);
    
    int Q,x,y;
    scanf("%d",&Q);
    while(Q--)
    {
        scanf("%d%d",&x,&y);
        solve(x,y);
        printf("%lld %lld\n",A,B);
    }
    
    return 0;
}
开车旅行

Count The Repetitions

f[j][i]表示从s1第i个字符开始,能够表示出s2 2^j最少需要多少字符。

就有f[j][i]=f[j-1][i]+f[j-1][((i+f[j-1][i])-1)%s1len+1]

最后就枚举匹配的起始点,同样在不超过s1len*n1的情况下用二进制一步步跳,记录当前起始点的最优解更新答案就好了。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long LL; 

char s1[110],s2[110];
LL f[40][110];
int main()
{
    freopen("2.in","r",stdin);
    freopen("2.out","w",stdout);
    while(1)
    {
        int n2,n1;
        scanf("%s",s2+1);if(s2[1]=='}')break;
        scanf("%d%s%d",&n2,s1+1,&n1);
        int s2len=strlen(s2+1),s1len=strlen(s1+1);
        
        bool bk=false;
        for(int i=1;i<=s1len;i++)
        {
            int p=i;f[0][i]=0;
            for(int j=1;j<=s2len;j++)
            {
                int cc=0;
                while(s1[p]!=s2[j])
                {
                    p=p%s1len+1;
                    cc++;if(cc>=s1len){printf("0\n");bk=true;break;}
                }
                p=p%s1len+1;
                f[0][i]+=cc+1;
                if(bk==true)break;
            }
            if(bk==true)break;
        }
        if(bk==true)continue;
        
        for(int j=1;j<=30;j++)
            for(int i=1;i<=s1len;i++)
                f[j][i]=f[j-1][i]+f[j-1][((i+f[j-1][i])-1)%s1len+1];
        
        LL m=0;
        for(int i=1;i<=s1len;i++)
        {
            int x=i;LL ans=0;
            for(int j=30;j>=0;j--)
                if(x+f[j][(x-1)%s1len+1]-1<=s1len*n1)
                    x+=f[j][(x-1)%s1len+1], ans+=(1<<j);
            m=max(m,ans);
        }
        printf("%lld\n",m/n2);
    }
    return 0;
}
Count The Repetitions

 好像都是先预处理出下一步的状态,然后二进制划分啊

posted @ 2018-08-13 21:52  AKCqhzdy  阅读(428)  评论(0编辑  收藏  举报