P1912 [NOI2009] 詩人小G

storms11發表於2024-10-02

題目連結
題解:
定義
算上空格的字首和\(sum[i]=\sum_{j=1}^{i}len[j]+1\)
\(dp[i]=min_{j<i}(dp[j]+|sum[i]-sum[j]-1+L|^p)\)
相當於列舉上一行的結尾在哪。
可以感性理解一下,i越靠後,最優決策點j一定會往後移。
所以決策點具有單調性。我有一個簡單的證明,就是列個式子,證明i向後移一位,用j轉移的式子一定小於用j-1轉移。
然後,維護決策單調性,這道題不能直接用分治演算法(好像可以cdq,但我不會),所以我們用二分佇列來做。
首先遍歷到一個點先計算它的\(dp[i]\),考慮佇列中維護的是一個從小到大沒有交集的一段區間的最優決策點。所以找到區間包含i的元素,轉移即可。
然後更新轉移點,顯然我們可以透過二分確定(只考慮前面i個點)這點作為最佳決策點的區間,記錄左端點,右端點直接當作\(n+1\)。將被完全覆蓋的區間的最佳決策點彈出去(一定不如i)。最後入棧即可。
注意事項:\(1e18\)\(long long\)很危險,所以用\(long\) $double $來存犧牲一點精度。

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int t,n,L,P,l[N],sum[N],q[N],ans[N],opt[N];
long double dp[N];
string s[N]; 
long double ksm(long double x,int y)
{
	long double an=1;
	while(y)
	{
		if(y&1)an=an*x;
		y>>=1;
		x=x*x;
	}
	return an;
}
long double js(int j,int i)
{
	return dp[j]+ksm((long double)abs(sum[i]-sum[j]-1-L),P);
}
int find(int x,int y)
{
	int l=x,r=n+1,an=n+1;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(js(x,mid)<js(y,mid))l=mid+1;
		else an=mid,r=mid-1;
	}
	return an;
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>t;
	while(t--)
	{
		cin>>n>>L>>P;
		for(int i=1;i<=n;i++)
		{
			sum[i]=0;
			cin>>s[i]; 
			sum[i]=sum[i-1]+s[i].size()+1;
		}
		int head=1,tail=1;q[1]=0;
		for(int i=1;i<=n;i++)
		{
			while(head<tail&&l[head]<=i)head++;//l記錄下一區間的左端點即當前區間的最大值
			opt[i]=q[head];
			dp[i]=js(q[head],i);
			while(head<tail&&l[tail-1]>=find(q[tail],i))tail--;
			l[tail]=find(q[tail],i);
			q[++tail]=i; 
			//printf("%d %lld %d %d\n",opt[i],(long long)dp[i],l[tail-1],q[tail]);
		}
		if(dp[n]>1e18)cout<<"Too hard to arrang"<<'\n';
		else
		{
			cout<<(long long)dp[n]<<'\n'; 
			int x=n,cnt=0;
			ans[++cnt]=n;
			while(x!=0)
			{
				x=opt[x];
				ans[++cnt]=x;
			}
			for(int i=cnt;i;i--)
			{
				for(int j=ans[i]+1;j<=ans[i-1];j++)
				{
					cout<<s[j];
					if(j!=ans[i-1])cout<<' ';
				}		
				if(i!=1)cout<<'\n';					
			}				
		}
		cout<<"--------------------";if(t)cout<<'\n';
	}
	return 0;
 } 

相關文章