NOI2009 诗人小G

题目链接:戳我

30pts做法:
我们设\(dp[i]\)表示前i个句子最小的代价,然后再记录一个当前的最小值是从哪里转移过来的。
注意输出的行末不带空格,以及要用long double(double和long long都会炸)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#define MAXN 100010
#define mp make_pair
#define ll long long
#define double long double
#define INF 1e18
using namespace std;
int T,n,l,p;
char s[MAXN][60];
double sum[MAXN];
inline double calc(double x)
{
    double cur_ans=1,xx=1.0*x;
    for(int i=1;i<=p;i++) cur_ans*=xx;
    return cur_ans;
}
namespace subtask1
{
    int last[MAXN];
    double dp[MAXN];
    vector<pair<int,int> >vec;
    inline void solve()
    {
        vec.clear();
        for(int i=1;i<=n;i++) sum[i]+=sum[i-1];
        for(int i=0;i<=n;i++) dp[i]=INF*8;
        dp[0]=0;
        for(int i=1;i<=n;i++)
        {
            for(int j=0;j<i;j++)
            {
                double cur_ans=dp[j]+calc(fabs(1.0*l-(1.0*i-j-1+sum[i]-sum[j])));
                if(cur_ans<dp[i]) 
                {
                    last[i]=j+1;
                    dp[i]=cur_ans;
                }
            }
        }
        if(dp[n]>INF)
        {
            printf("Too hard to arrange\n");
            return;
        }
        printf("%.0Lf\n",dp[n]);
        int now=n;
        while(now>=1)
        {
            vec.push_back(mp(last[now],now));
            now=last[now]-1;
        }
        for(int i=vec.size()-1;i>=0;i--)
        {
            for(int j=vec[i].first;j<vec[i].second;j++)
                printf("%s ",s[j]+1);
            printf("%s",s[vec[i].second]+1);
            puts("");
        }
    }
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("ce.in","r",stdin);
    freopen("ce.out","w",stdout);
    #endif
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%d",&n,&l,&p);
        for(int i=1;i<=n;i++) scanf("%s",s[i]+1),sum[i]=1.0*strlen(s[i]+1);
        subtask1::solve();
        printf("--------------------\n");
    }
    return 0;
}

100分做法需要决策单调性
我们考虑记录s数组表示前缀和,\(f[i]\)表示前i个的最小代价。
\(f[i]=min{f[j]+|s[i]-s[j]-1-L|}\)
其中\(j\in [0,i-1]\)
显然对于两个点j,k,且\(j<k\)。如果j能够转移到i,k能转移到i+1,那么j一定不能转移到其它大于i的点。
那么这就存在一个决策单调的性质。
我们可以维护一下么个点的作用范围。用一个单调栈加三元组记录就可以了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#define MAXN 100010
#define double long double
using namespace std;
int n,L,P,T,head,tail,top;
int g[MAXN],S[MAXN];
char ch[MAXN][35];
long long s[MAXN];
double f[MAXN];
struct Node{int j,l,r;}q[MAXN];
inline double fpow(double x,int y)
{
    double cur_ans=1;
    while(y)
    {
        if(y&1) cur_ans=cur_ans*x;
        x=x*x;
        y>>=1;
    }
    return cur_ans;
}
inline double calc(int i,int j)
{
    return f[j]+fpow(fabs(1.0*s[i]-s[j]-1-L),P);
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("ce.in","r",stdin);
    freopen("ce.out","w",stdout);
    #endif
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%d",&n,&L,&P);
        for(int i=1;i<=n;i++) 
        {
            scanf("%s",ch[i]+1);
            s[i]=1ll*strlen(ch[i]+1);
        }
        for(int i=1;i<=n;i++) s[i]+=s[i-1]+1;
        memset(q,0,sizeof(q));
        q[head=tail=1]=(Node){0,1,n};
        for(int i=1;i<=n;i++)
        {
            while(head<tail&&q[head].r<i) ++head;
            int j=q[head].j;
            q[head].l++;
            f[i]=calc(i,j);
            g[i]=j;
            while(head<tail&&calc(q[tail].l,q[tail].j)>=calc(q[tail].l,i)) tail--;
            int l=q[tail].l,r=q[tail].r,cur_ans=q[tail].r+1;
            while(l<=r)
            {
                int mid=(l+r)>>1;
                if(calc(mid,i)<=calc(mid,q[tail].j)) cur_ans=mid,r=mid-1;
                else l=mid+1;
            }
            if(cur_ans!=q[tail].l) q[tail].r=cur_ans-1;
            else --tail;
            if(cur_ans<=n) q[++tail]=(Node){i,cur_ans,n};
        }
        if(f[n]>1e18) printf("Too hard to arrange\n");
        else
        {
            printf("%.0Lf\n",f[n]);
            top=0;
            for(int i=n;i;i=g[i])S[++top]=i;S[top+1]=0;
            for(int i=top;i;--i)
                for(int j=S[i+1]+1;j<=S[i];++j)
                {
                    printf("%s",ch[j]+1);
                    if(j!=S[i]) printf(" ");
                    else printf("\n");
                }
        }
        printf("--------------------\n");
    }
    return 0;
}
posted @ 2019-07-05 17:19  风浔凌  阅读(171)  评论(0编辑  收藏  举报