[haoi2008]木棍分割
有n根木棍, 第i根木棍的长度为Li, n根木棍依次连结在一起, 总共有n-1个连接处. 现在允许你最多砍断m个连接处, 砍完后n根木棍被分成了很多段,要求满足总长度最大的一段长度最小, 并且输出有多少种砍木棍的方法使得总长度最大的一段长度最小。
第一问:明显的二分答案;
第二问:状态转移方程很容易搞出来:f[i][j]=Σf[k][j-1] sum[i]-sum[k]<=ans1
看起来是个O(n2m)的dp,但实际上,k的取值只可能是i之前连续的一段,用个q[j-1]表示计算f[i][j]时前面的合法的f[k][j-1]的和,然后类似单调队列的搞一搞即可;
#include<iostream> #include<cstdio> #include<cstring> #include<string> #include<ctime> #include<algorithm> #include<cstdlib> #include<map> #include<set> #include<vector> #include<queue> #include<cmath> using namespace std; #define FILE "1" #define LL long long #define up(i,j,n) for(int i=j;i<=n;i++) #define pii pair<int,int> #define piii pair<int,pair<int,int> > template<typename T> inline bool chkmin(T &a,T b){return a>b?a=b,true:false;} template<typename T> inline bool chkmax(T &a,T b){return a<b?a=b,true:false;} namespace IO{ char *fs,*ft,buf[1<<15]; inline char gc(){return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;} inline int read(){ int x=0,ch=gc();bool f=0; while(ch<'0'||ch>'9'){if(ch=='-')f=1;ch=gc();} while(ch<='9'&&ch>='0'){x=(x<<1)+(x<<3)+ch-'0';ch=gc();} return f?-x:x; } }using namespace IO; namespace OI{ const int maxn(51000),inf(100000000),mod(10007); int n,m; int a[maxn],sum[maxn]; bool check(int mid){ int sum=0,ans=1; for(int i=1;i<=n;i++){ sum+=a[i]; if(sum>mid)ans++,sum=a[i]; } if(ans>m)return 0; else return 1; } int q[maxn],head; short int f[maxn][1010]; void slove(){ n=read(),m=read();m++; int maxx=0; up(i,1,n)a[i]=read(),chkmax(maxx,a[i]); int left=maxx,right=inf,mid,ans; while(left<=right){ mid=(left+right)>>1; if(check(mid))right=mid-1,ans=mid; else left=mid+1; } up(i,1,n)sum[i]=sum[i-1]+a[i]; head=0; f[0][0]=1; q[0]=1; for(int i=1;i<=n;i++){ while(sum[i]-sum[head]>ans){ for(int j=1;j<=m&&j<=i;j++)q[j-1]=(q[j-1]-f[head][j-1]+mod)%mod; head++; } for(int j=1;j<=m&&j<=i;j++)f[i][j]=q[j-1]; for(int j=min(i,m);j>=1;j--)q[j]=(q[j]+q[j-1])%mod; } int y=0; for(int i=1;i<=m;i++)y=(y+f[n][i])%mod; printf("%d %d\n",ans,y); } } int main(){ using namespace OI; slove(); return 0; }