寒假集训测试题解
T2
我感觉和小胖守皇宫类似,但我当时及没看数据范围也没读清题,直接树形背包DP,大样例炸了
改了半天,硬是忘记考虑不买优惠券的情况(苦笑)
点击查看代码
#include <bits/stdc++.h>
//#define int long long
using namespace std;
int n,b;
vector <int> g[5005];
int d[5005],c[5005],f[5005][5005][2];
int dfs(int u)
{
int p=1;
f[u][0][0]=0;
f[u][1][1]=c[u]-d[u];
f[u][1][0]=c[u];
for(auto v:g[u])
{
int siz=dfs(v);
for(int i=min(p,n);i>=0;i--)
{
for(int j=1;j<=siz&&j+i<=n;j++)
{
f[u][i+j][0]=min(f[u][i+j][0],f[u][i][0]+f[v][j][0]);
f[u][i+j][1]=min(f[u][i+j][1],f[u][i][1]+min(f[v][j][0],f[v][j][1]));
}
}
p+=siz;
}
return p;
}
signed main()
{
// freopen("B.in","r",stdin);
scanf("%d%d",&n,&b);
scanf("%d%d",&c[1],&d[1]);
// int mii=max(mii,c[1]-d[1]);
int x;
for(int i=2;i<=n;i++)
{
scanf("%d%d%d",&c[i],&d[i],&x);
g[x].push_back(i);
// int mii=max(mii,c[i]-d[i]);
}
memset(f,0x3f,sizeof(f));
dfs(1);
int ans=0;
for(int i=n;i>=0;--i){
if(f[1][i][0]<=b || f[1][i][1]<=b){
ans=i;
break;
}
}
printf("%d\n",ans);
return 0;
}
/*
6 16
10 9
10 5 1
12 2 1
20 18 3
10 2 3
2 1 5
5 10
3 1
3 1 1
3 1 2
3 1 3
3 1 4
*/
T3
序列块
- 心得:一开始想用区间DP,但发现数据范围2为数组开不下而且时间复杂度过高,放弃了
于是,我想到了线性DP,可不幸的是,我动态转移方程推错了(菜),做后用区间DP做超时了
区间DP代码
#include <bits/stdc++.h>
//#define int long long
using namespace std;
int n,t,a[200005],f[20005][20005];
int main()
{
scanf("%d",&t);
while(t--)
{
for(int i=1;i<=n;i++)
{
a[i]=0;
for(int j=1;j<=n;j++)f[i][j]=0;
}
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
if(n==1)
{
printf("%d\n",1);
continue;
}
int sum=0;bool flag=0;
for(int i=1;i<=n;i++)
{
i+=a[i];
if(i==n)
{
flag=1;
break;
}else if(i>n)
{
break;
}
}
if(flag)
{
printf("%d\n",0);
continue;
}else
{
int ans=0;
for(int i=1;i<=n;i++)f[i][i]=1;
for(int len=2;len<=n;len++)
{
for(int i=1;i+len-1<=n;i++)
{
int j=i+len-1;
if(i+a[i]==j)f[i][j]=0;
else
{
f[i][j]=j-i+1;
f[i][j]=min({f[i][j],f[i][j-1]+1,f[i+1][j]+1});
for(int k=i;k<j;k++)
{
f[i][j]=min({f[i][j],f[i][k]+f[k+1][j]});
}
// cout<<i<<" "<<j<<" "<<f[i][j]<<endl;
}
}
}
//cout<<f[1][n]<<endl;
printf("%d\n",f[1][n]);
}
}
return 0;
}
线性DP思路:就是记录从i->n的最少操作次数,非常简单
\[f[i]=f[i+1]+1删当前元素
\]
\[f[i]=f[i+a[i]+1](i+a[i]+1<=n)继承上一状态
\]
正解
#include <bits/stdc++.h>
//#define int long long
using namespace std;
int n,t,a[200005],f[20005];
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
// f[i]=i;
}
f[n]=1;f[n+1]=0;
for(int i=n;i>=1;i--)
{
// f[i]=i;
// for(int j=i;j>=1;j--)
// {
if(i+a[i]<=n)
{
f[i]=min(f[i+1]+1,f[i+a[i]+1]);
}
else f[i]=f[i+1]+1;
// }
}
cout<<f[1]<<endl;
}
return 0;
}
T4
送礼物
这道题需用到01分数规划
对于当时没学的我相当炸裂的
其实就是二分加单调队列
谁能想到,数据是真的强!!!我当时用线段树,想的是T不了几个点,没想到,几乎全T了
还Wa了一个(这是我SB)
还有我max括号写在了错位置,MD半天没调出来
首先要考虑长度为L的情况,可能最大值与最小值的距离小于l所以我们要额外考虑一下这种情况
剩下的就是l到r之间的了
\[\frac{a_(i,j)max - a(i,j)min}{j-i+k}
\]
转换一下
\[mid*a_(i,j)max\pm mid*i mid*a(i,j)min\pm mid*j >=k*ans
\]
点击查看代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=50001;
double ans,ANS;
double b[N];int a[N],n,k,l,r;
deque <int> q1;
void update1(int i)
{
while(!q1.empty()&&i>=q1.front()+(r-l))q1.pop_front();
while(!q1.empty()&&b[i]<=b[q1.back()])q1.pop_back();
q1.push_back(i);
}
void update2(int i)
{
while(!q1.empty()&&q1.front()-i>=(r-l))q1.pop_front();
while(!q1.empty()&&b[i]<=b[q1.back()])q1.pop_back();
q1.push_back(i);
}
bool check(double mid)
{
for(int i=1;i<=n;i++)
{
b[i]=(double)a[i]-mid*i;
}
ANS=-100000000.0;
q1.clear();
//q1.push_back(0);
for(int i=1;i<=n-l;i++)
{
update1(i);
ANS=max(ANS,b[i+l]-b[q1.front()]);
}
//q1.push_back(0);
for(int i=1;i<=n;i++)
{
b[i]=(double)a[i]+mid*i;
}
q1.clear();
// ANS=-100000000.0;
for(int i=n;i>l;i--)
{
update2(i);
ANS=max(ANS,b[i-l]-b[q1.front()]);
}
return (ANS>=k*mid);
}
void fen()
{
double L=0,R=1000.0;
while(R-L>1e-9)
{
double mid=(R+L)/2;
if(check(mid))
{
ans=max(ans,mid);
L=mid;
}else R=mid-(1e-9);
}
printf("%.4lf\n",ans);
}
deque <int> A,B;
//A.push_back(0);
//B.push_back(0);
void push(int i)
{
while(!A.empty()&&a[A.back()]>=a[i])
{
A.pop_back();
}
A.push_back(i);
while(!B.empty()&&a[B.back()]<=a[i])
{
B.pop_back();
}
B.push_back(i);
}
void update(int i)
{
while(!A.empty()&&i>=A.front()+l)
{
A.pop_front();
}
while(!B.empty()&&i>=B.front()+l)
{
B.pop_front();
}
}
main()
{
// freopen("D.in","r",stdin);
int tt;
scanf("%lld",&tt);
tt++;
while(tt--)
{
if(tt==0) break;
scanf("%lld%lld%lld%lld",&n,&k,&l,&r);
// memset(a,0,sizeof(a));
// memset(b,0,sizeof(b));
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
}
ans=0;ANS=0;
A.clear();B.clear();
//A.push_back(0);B.push_back(0);
for(int i=1;i<l;i++) push(i);
ans=-1e9;
for(int i=l;i<=n;i++)
{
update(i);push(i);
ans=max(ans,(1.0*(a[B.front()]-a[A.front()]))/(1.0*(l+k-1)));
//cout<<(1.0*(a[B.front()]-a[A.front()])))/(1.0*(l+k-1)<<' ';
}
// cout<<endl;
//cout<<ans<<endl;
fen();
}
return 0;
}
总结:关于什么情况下使用单调队列,首先看求的是什么?求的是区间中最大值最小值,我们可已考虑用单调队列
因为他的复杂度为\(O(n)\)而线段树是单词查询logn所以总共\(O(n^2logn)\)而这题测试点几乎全是50000可想而知