我想先打一个 O(n2m) 的暴力,然后上一个线段树,但是暴力打了不过样例#3,求调。
状态是设 fi,j 表示当前已经删了 i−1 个数,k=j 的最小花费,gi,j 表示方案数。
#include<bits/stdc++.h>
#define int long long
#define mod 1000000007
#pragma GCC optimize("O3")
using namespace std;
struct Data{int l,r,minn,sum;};
int n,m,ans,tot;
void solve()
{
cin>>n>>m;
vector<int>a(n+5,0),b(m+5,0);
vector<vector<int> >f(n+5),g(n+5);
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=m;i++) cin>>b[i];
for(int i=0;i<=n+4;i++) f[i].resize(m+5,1e18),g[i].resize(m+5,0);
f[1][1]=0,g[1][1]=1;
for(int i=1;i<=n+1;i++)
{
for(int j=1;j<=m;j++)//操作2的转移
{
int sum=0,p=i;
for(int k=i-1;k>=1;k--)//找到最长能删除的长度
{
sum+=a[k];
if(sum>b[j]) break;
p=k;
}
for(int k=i-1;k>=p;k--) f[i][j]=min(f[i][j],f[k][j]+m-j);
for(int k=i-1;k>=p;k--) if(f[k][j]+m-j==f[i][j]) g[i][j]=(g[i][j]+g[k][j])%mod;
}
int minn=1e18,sum=0;
if(i!=n+1)//不能在a为空转移
{
for(int j=1;j<=m;j++)//操作1的转移
{
if(minn==f[i][j]) g[i][j]=(g[i][j]+sum)%mod,sum=(sum+g[i][j])%mod;//和当前最小值相同
else if(minn<f[i][j]) g[i][j]=sum;//最小值更优
else sum=g[i][j];//当前值更优
minn=min(minn,f[i][j]);
f[i][j]=min(f[i][j],minn);
}
}
}
ans=1e18;
tot=0;
for(int i=1;i<=m;i++) ans=min(ans,f[n+1][i]);//计算最小花费和方案数
for(int i=1;i<=m;i++) if(f[n+1][i]==ans) tot=(tot+g[n+1][i])%mod;
if(ans==1e18) cout<<-1<<"\n";
else cout<<ans<<" "<<tot<<"\n";
return;
}
signed main()
{
ios::sync_with_stdio(0);
int t;
cin>>t;
while(t--) solve();
return 0;
}