T1
我们发现第一问很好解决,就直接按照从大到小的顺序排序,然后依次选就行了。
然后第一问的答案就出来了。
随后我们看第二问,我们发现和零一背包很相似。
解决这样一个问题, 我们可以使用 01 背包。用
f
i
,
j
,
k
f_{i, j, k}
fi,j,k 表示在前 i 个瓶子中选择了 j 个瓶子, 总容积为 k 的最大不移动的液体体积和。我们可以得到:
f i , j , k = max ( f i − 1 , j , k , f i − 1 , j − 1 , k − b i + a i ) f_{i, j, k}=\max \left(f_{i-1, j, k}, f_{i-1, j-1, k-b_{i}}+a_{i}\right) fi,j,k=max(fi−1,j,k,fi−1,j−1,k−bi+ai)
然后我们用滚动数组优化,把i这维省去就可以了
#include<bits/stdc++.h>
using namespace std;
#define int long long
int read() {
int x=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x*f;
}
const int N=110;
struct node {
int a,b;
} e[N];
int sum[N];
int all;
bool cmp(node x,node y) {
// if(x.b==y.b) return x.a>y.a;
return x.b>y.b;
}
int f[N][10010];
signed main() {
// freopen("3.in","r",stdin);
int n=read();
for(int i=1; i<=n; i++) {
e[i].a=read();
all+=e[i].a;
}
for(int i=1; i<=n; i++) {
e[i].b=read();
// sum[i]=sum[i-1]+e[i].b;
}
sort(e+1,e+1+n,cmp);
int id=0;
for(int i=1; i<=n; i++) {
sum[i]=sum[i-1]+e[i].b;
if(sum[i]>=all) {
id=i;
break;
}
}
int res=0,ans=0;
for(int i=1;i<=id;i++){
res+=e[i].b;
}
// cout<<res<<endl;
memset(f,-0x3f,sizeof(f));
f[0][0]=0;
for(int i=1; i<=n; i++){
for(int j=id; j; j--){
for(int k=res; k>=e[i].b; k--){
f[j][k]=max(f[j][k],f[j-1][k-e[i].b]+e[i].a);
}
}
}
// cout<<all<<" "<<res<<endl;
for(int i=all; i<=res; i++){
// cout<<f[id][i]<<endl;
ans=max(ans,f[id][i]);
}
// cout<<"OP"<<sum;
printf("%lld %lld",id,all-ans);
return 0;
}
T2
这道题目看到数学式子不能硬求,一般遇到都要进行化简。
我们进行化简:
∑
i
=
1
n
(
x
i
−
x
ˉ
)
2
n
=
∑
i
=
1
n
x
i
2
n
−
(
x
ˉ
)
2
\frac{\sum_{i=1}^{n}\left(x_{i}-\bar{x}\right)^{2}}{n}=\frac{\sum_{i=1}^{n} x_{i}^{2}}{n}-(\bar{x})^{2}
n∑i=1n(xi−xˉ)2=n∑i=1nxi2−(xˉ)2 。显然对于每个询问, 平均数是固定的, 所以题目相当于最小化平方和。假设一开始所有
a
i
都取到
l
i
a_{i} 都取到 l_{i}
ai都取到li , 只需要以此为基础将一些
a
i
a_{i}
ai 的值增加。若将
x
−
1
x-1
x−1 增加到
x
x
x , 平方和增加
x
2
−
(
x
−
1
)
2
=
x
×
2
−
1
x^{2}-(x-1)^{2}=x \times 2-1
x2−(x−1)2=x×2−1 。所以显然每次将最小值增加
1
1
1 最优。
然后我们就是实现了:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn=1e6+26,mx=1e6,mod=998244353;
struct Query {
ll x,id,ans;
friend bool operator<(Query xy,Query zb) {
return xy.x<zb.x;
}
} qq[maxn];
ll n,q,invn,l[maxn],r[maxn],cnt[maxn],ans,ansx;
ll read() {
ll x=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x*f;
}
inline ll Pow(ll x,ll y) {
if(!y)
return 1;
if(y&1)
return Pow(x,y^1)*x%mod;
return Pow(x*x%mod,y>>1);
}
int main() {
n=read(),q=read();
invn=Pow(n,mod-2);
for(ll i=1; i<=n; i++) {
l[i]=read(),r[i]=read();
ans+=l[i]*l[i];
ansx+=l[i];
cnt[l[i]+1]++;
cnt[r[i]+1]--;
}
for(ll i=0; i<=mx; i++)
cnt[i]+=cnt[i-1];
for(ll i=1; i<=q; i++) {
qq[i].x=read();
qq[i].id=i;
}
sort(qq+1,qq+q+1);
for(ll i=1,j=0; i<=q; i++) {
while(ansx<qq[i].x) {
if(qq[i].x-ansx<cnt[j]) {
cnt[j]-=(qq[i].x-ansx);
(ans+=(qq[i].x-ansx)*(j*2-1)%mod)%=mod;
ansx=qq[i].x;
break;
}
ansx+=cnt[j];
(ans+=cnt[j]*(j*2-1)%mod)%=mod;
j++;
}
qq[i].ans=ans;
qq[i].x%=mod;
}
sort(qq+1,qq+q+1,[](Query xy,Query zb) {return xy.id<zb.id;});
for(ll i=1; i<=q; i++)
printf("%lld\n",(qq[i].ans*invn%mod-qq[i].x*qq[i].x%mod*invn%mod*invn%mod+mod)%mod);
return 0;
}
T3
这道题感觉和滑动窗口很像,然后我们往单调队列想,就不难得出正解。
我们用单调队列来维护给定区间的最大值。
设
f
i
,
j
f_{i,j}
fi,j表示,前i个数已经选了j个数的最大值,单调队列里维护的是最大值点的位置,对转移进行优化,省去了不必要的转移
#include<bits/stdc++.h>
using namespace std;
#define int long long
int read() {
int x=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x*f;
}
const int N=5050;
int f[N][N];
int q[N];
int a[N];
int ans;
signed main(){
// freopen("3.in","r",stdin);
memset(f,0xcf,sizeof f);
int n=read(),k=read(),x=read();
for(int i=1;i<=n;i++){
a[i]=read();
}
if(n/k>x){
puts("-1");
return 0;
}
f[0][0]=0;
for(int j=1;j<=x;j++){
int l=1,r=1;
q[r]=0;
for(int i=1;i<=n;i++){
while(l<=r&&q[l]+k<i) l++;
f[i][j]=f[q[l]][j-1]+a[i];
while(l<=r&&f[i][j-1]>=f[q[r]][j-1]) r--;
q[++r]=i;
}
}
for(int i=n-k+1;i<=n;i++){
ans=max(ans,f[i][x]);
}
printf("%lld",ans);
return 0;
}
T4
说来也奇怪,我的贪心跑出来的最小值比答案的最小值还小,得分
17
p
t
s
17pts
17pts
先说一下我的假做法。
我们可以按高度进行排序,然后我们就依次进行判断就行了,这样肯定是假的,但是在写不出正解的情况下就得写一个这个。
#include<bits/stdc++.h>
using namespace std;
#define int long long
int read(){
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return x*f;
}
const int N=1e5+10;
struct node{
int h,w;
}gf[N];//女朋友
bool cmp(node a,node b){
return a.h>b.h;
}
signed main(){
// freopen("1.in","r",stdin);
int n=read(),l=read();
for(int i=1;i<=n;i++){
gf[i].h=read(),gf[i].w=read();
}
sort(gf+1,gf+1+n,cmp);
int ans=0,res=0;
int maxn=0;
for(int i=1;i<=n;i++){
if(res+gf[i].w>l){
res=0;
// cout<<maxn<<endl;
ans+=maxn;
maxn=0;
}
res+=gf[i].w;
maxn=max(maxn,gf[i].h);
}
ans+=maxn;
printf("%lld",ans);
return 0;
}
然后考虑正解
用线段树优化DP
f
i
f_{i}
fi 表示第
i
i
i 本书结尾时高度和的最小值。
f i = min ( f j + max ( h j + 1 , … , h i ) ) ( w j + 1 + … + w i ≤ L ) f_{i}=\min \left(f_{j}+\max \left(h_{j+1}, \ldots, h_{i}\right)\right)\left(w_{j+1}+\ldots+w_{i} \leq L\right) fi=min(fj+max(hj+1,…,hi))(wj+1+…+wi≤L)
w
w
w 这个限制我们可以通过二分得到
现在我们把问题转化成:
f i = min ( f j + max ( h j + 1 , … , h i ) ) ( pos i ≤ j ) f_{i}=\min \left(f_{j}+\max \left(h_{j+1}, \ldots, h_{i}\right)\right)\left(\operatorname{pos}_{i} \leq j\right) fi=min(fj+max(hj+1,…,hi))(posi≤j)
其中 p o s i p o s_{i} posi 为第 i 个位置的最左端点。
然后是如何使用线段树优化
p
o
s
i
pos_i
posi的值我们可以在一开始用双指针把区间存起来
而且我们还能用线段树二分来找到
p
o
s
i
pos_i
posi 这个值,但设个我感觉就复杂了
首先, 我们先开一个单调栈, 对于每个位置找出第一个大于
h
i
h_{i}
hi 的位置 l , 并将
[
l
+
1
,
i
]
[l+1, i]
[l+1,i] 这段区间内的
h
h
h 赋为
h
i
h_{i}
hi 。单调栈可以直接找到需要更新区间的端点
我们发现每一个位置的
f
f
f 是确定的, 我们每次只需区间修改
h
h
h 的值, 然后维护
f
+
h
f+h
f+h 和
f
f
f 的最小值。
最后更新用区间查询就行了
#include <bits/stdc++.h>
#define ll long long
#define lson (rt<<1)
#define rson (rt<<1|1)
using namespace std;
const int maxn=100000+10;
const ll inf=1e18;
int n,L,w[maxn],h[maxn],pre[maxn],sta[maxn],top;
ll sum[maxn],f[maxn],Min[maxn<<2],val[maxn<<2],tag[maxn<<2];
// Min 为 f + h 的最小值
// val 为 f 的最小值
inline int read() {
register int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)) {
if(ch=='-')f=-1;
ch=getchar();
}
while(isdigit(ch)) {
x=(x<<3)+(x<<1)+ch-'0';
ch=getchar();
}
return (f==1)?x:-x;
}
inline void pushup(int rt) {
Min[rt]=min(Min[lson],Min[rson]);
val[rt]=min(val[lson],val[rson]);
}
inline void pushdown(int rt) {
if(tag[rt]!=inf) {
Min[lson]=val[lson]+tag[rt];
Min[rson]=val[rson]+tag[rt];
tag[lson]=tag[rson]=tag[rt];
tag[rt]=inf;
}
}
void build(int l,int r,int rt) {
Min[rt]=val[rt]=tag[rt]=inf;
if(l == r) return ;
int mid=(l+r)>>1;
build(l,mid,lson);
build(mid+1,r,rson);
}
void update(int L,int R,int C,int l,int r,int rt) {
if(L <= l && r <= R) {
Min[rt]=val[rt]+C;
tag[rt]=C;
return ;
}
pushdown(rt);
int mid=(l+r)>>1;
if(L <= mid) update(L,R,C,l,mid,lson);
if(R > mid) update(L,R,C,mid+1,r,rson);
pushup(rt);
}
void modify(int x,int l,int r,int rt) {
if(l == r) {
Min[rt]=inf;
val[rt]=f[l-1];
return ;
}
pushdown(rt);
int mid=(l+r)>>1;
if(x <= mid) modify(x,l,mid,lson);
else modify(x,mid+1,r,rson);
pushup(rt);
}
ll query(int L,int R,int l,int r,int rt) {
if(L <= l && r <= R) return Min[rt];
pushdown(rt);
int mid=(l+r)>>1;
ll ans=inf;
if(L <= mid) ans=min(ans,query(L,R,l,mid,lson));
if(R > mid) ans=min(ans,query(L,R,mid+1,r,rson));
return ans;
}
int main() {
n=read(),L=read();
for(int i=1; i<=n; i++) {
h[i]=read(),w[i]=read();
sum[i]=sum[i-1]+w[i];
}
sta[++top]=1;
for(int i=2; i<=n; i++) {
while(top&&h[i]>h[sta[top]]) top--;
if(top) pre[i]=sta[top];
sta[++top]=i;
}
build(1,n,1);
for(int i=1; i<=n; i++) {
modify(i,1,n,1);
if(pre[i]+1<=i) update(pre[i]+1,i,h[i],1,n,1);
int l=lower_bound(sum,sum+i+1,sum[i]-L)-sum;
if(l<i) f[i]=query(l+1,i,1,n,1);
}
printf("%lld\n",f[n]);
return 0;
}
这次比赛全是DP,差评完全不像模拟赛