zoukankan      html  css  js  c++  java
  • 2.24 T3 P1912 [NOI2009] 诗人小G

    思路:

    转移方程: f[i]=f[j]+∣sum[i]−sum[j]−L−1∣ ^P
    如果只是暴力枚举, O(N^2) 的复杂度,期望的分:30

    考虑怎么优化,打表发现,如果每次把转移i的最优j存储下来,那么输出发现j是单增的,证明不太好证,有兴趣可以去洛谷康一康,下面是感性理解:

    设j到i后面的句子组成了新的一行,如果j不单调上升,那么后面的句子有可能会变得很长很长,这时候不协调度将会几何数形式上升,唯一的解决方案就是长句断句,所以j就会变大

    如果知道j是单调上升的,那么我们每次转移的时候就不需要往前找了,只需要往后找(可以采用单调队列优化)

    维护一个队列,队列里面有三个变量l r c ,其中l r表示c这个决策的适用范围,并且在这个范围中c是最优的j

    如果现在有了一个新的i,考虑如何维护这个队列

    可以先找到对头的元素,如果发现对头元素已经不包括i了,那么直接弹出

    之后直接取对头元素O(1)转移

    现在考虑将i加入队列,或许这个i会对后面的元素产生贡献

    我们检查队尾元素,如果在队尾元素的范围之内,i更新l比c更新l更优,由于满足决策单调性,所以l后面的元素从i转移比从j转移更优,所以弹出队尾

    那么假设现在碰到了一个队尾,其中i更新r更优,c更新l更优,也就是说这个元素中分成两半,前一半更新c更优,后一半i更新更优,那么这显然要拆成两个队列元素,我们怎么知道这个位置呢?二分处理

    这个时候我们就可以知道答案了,但是输出怎么办呢?

    转移的时候记录一下转移自哪里,这就是分行,然后输出即可

    最后是精度问题:

    如果在dp是用long long会GG,所以我们使用long double,(整数部分和小数部分一共19位)精度更高

    代码:

    #include<bits/stdc++.h>
    #define ll long long
    #define ld long double
    using namespace std;
    
    const int NS=1e5+2;
    const int inf=1e9+9;
    
    int t,n,l,p;
    int head,tail;
    int last[NS],ans[NS],next[NS];
    struct node{
    	int c,l,r;
    }q[NS];
    char s[NS][35];
    ld sum[NS],f[NS];
    
    inline ll read()//快读 
    {
        char c=getchar();
        ll x=0,f=1;
        while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}//不需要判负删掉来简化 
        while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
        return x*f;
    }
    
    void clear(){
    	memset(last,0,sizeof(last));
    	memset(sum,0,sizeof(sum));
        memset(q,0,sizeof(q));
        memset(s,0,sizeof(s));
        memset(f,0,sizeof(f));
    }
    
    
    ld pows(ld x,int y)//快速幂 
    {
    	ld ans=1;
    	for(;y;y>>=1,x*=x) if(y&1) ans*=x;
    	return ans;
    }
    
    ld val(int j,int i)//dp
    {
    	return f[j]+pows(abs(sum[i]-sum[j]-l-1),p);
    }
    
    void half(int i)//二分 
    {
    	int now=q[tail].c ,ls=q[tail].l ,rs=q[tail].r ;//队尾元素
    	int ret=q[tail].r +1;
    	while(ls<=rs)
    	{
    		int mid=(ls+rs)>>1;
    		if(val(i,mid)<=val(now,mid)) rs=mid-1,ret=mid;//如果i更优
    		//那么我们就知道对于mid~rs都是用i更新更优,所以c更新更优的左限制更新到mid-1 
    		else ls=mid+1;//c更优,那么 c更新更优的右限制更新到mid+1 
    	} 
    	
    	if(ret!=q[tail].l ) q[tail].r =ret-1;//如果发现用i更新只能使tail区间内的一部分
    	//更优,那么就讲tail分成两半
    	else --tail;//不然,就说明整个元素都比不过i
    	if(ret<=n) q[++tail]=(node){i,ret,n};//i分了一个新区间
    	 
    }
    
    void putout()
    {
    	if(f[n]>1e18) puts("Too hard to arrange");
    	else
    	{
    		printf("%lld
    ",(ll)(f[n]+0.5));
    		for(int i=n;i;i=last[i]) next[last[i]]=i;
    		int now=0;
    		for(int i=1;i<=n;i++)
    		{
    			now=next[now];
    			for(int j=i;j<now;j++) printf("%s ",s[j]);
    			printf("%s
    ",s[now]);
    			i=now;
    		}
    		
    	}
    	puts("--------------------");
    	return;
    	
    }
    
    
    int main()
    {
    	t=read();
    	while(t--)
    	{
    		 clear();
    		 n=read(),l=read(),p=read();
    		 for(int i=1;i<=n;i++)
    		 {
    		 	scanf("%s",s[i]);
    		 	sum[i]=sum[i-1]+strlen(s[i])+1;//前缀和
    		 	/*输出有空格,+1*/
    		 }
    		 
    		 q[head=tail=1]=(node){0,1,n};//初始元素,0表示最优决策,1~n表示决策范围
    		 
    		 for(int i=1;i<=n;i++)
    		 {
    		 	while(head<tail&&q[head].r <i) ++head;
    		 	//如果对头元素不包括i,弹出队头
    			 ++q[head].l ;//  ???
    			 f[i]=val(q[head].c,i);//o(1)转移
    			 last[i]=q[head].c ;//记录从哪里转移
    			 while(head<tail&&val(i,q[tail].l )<=val(q[tail].c ,q[tail].l )) tail--;
    			 half(i); 
    		  }
    		  
    		  putout();	
    	}
    	
    	return 0;	
    }
    
  • 相关阅读:
    ASP.NET身份验证机制membership入门——API篇
    测试SQL语句的执行时间
    ASP.NET身份验证机制membership入门——配置篇(2)
    ASP.NET身份验证机制membership入门——配置篇(1)
    ASP.NET身份验证机制membership入门——项目
    ASP.NET用户个性化设置Profile——配置1
    POJ 2762 强连通分量中存在单相连通边 【tarjan+toposort+缩点】.cpp
    POJ 2516 【最小费用最大流】.cpp
    POJ 1904 【强连通分量】.cpp
    POJ 1236 【强连通图+缩点】.cpp
  • 原文地址:https://www.cnblogs.com/yxr001002/p/14443683.html
Copyright © 2011-2022 走看看