Day 18
50
T1 完全背包
Description
有一个容量为m的背包和n种物品,每种物品有价值vi和体积wi,且有无限件。问最大价值是多少。
20% n,m<=
1
0
3
10^3
103
40% n,m<=
1
0
4
10^4
104
a
i
,
b
i
a_i,b_i
ai,bi<=
10
10
10
60% n,m<=
1
0
5
10^5
105
100% n<=
1
0
6
10^6
106,m<=
1
0
1
6
10^16
1016,ai,bi<=
100
100
100。
Solution
40pts
直接背包即可,复杂度 O ( N M ) O(NM) O(NM)
#include <cstdio>
#include <iostream>
using namespace std;
const int N = 1000010;
int n,m;
int v[N],w[N];
int f[N];
int main(){
freopen("backpack.in","r",stdin);
freopen("backpack.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++){
scanf("%d%d",&w[i],&v[i]);
}
for(int i=1; i<=n; i++){
for(int j=w[i]; j<=m; j++){
f[j]=max(f[j], f[j-w[i]]+v[i]);
}
}
printf("%d\n",f[m]);
return 0;
}
100pts
注意到m很大,
a
i
,
b
i
a_i,b_i
ai,bi很小。
有n件物品,肯定会有
a
i
a_i
ai一样的物品,我们对于每种体积的物品,只留下价值最大的。
这样就只剩下最多
100
100
100件物品。
对于这些物品按照性价比排序,取出性价比最高的,且体积最小的物品,设它为s。
我们如果完全贪心的选择s放入,不一定最优。
注意到当背包剩余空间足够大的时候,我们放s进去一定是最好的。
那足够大的界限在哪里呢?
首先我们知道,对于一个元素个数为n的数列a,一定存在一些数的和是n的倍数。
证明:
我们对a做一个前缀和,设
s
u
m
i
=
∑
j
=
0
i
a
j
sum_i=\sum\limits_{j=0}^{i}a_j
sumi=j=0∑iaj,sum有n+1项。一定存在两项模n相等,设这两项为
a
x
a_x
ax,
a
y
a_y
ay,则
∑
i
=
x
y
a
i
∣
n
\sum\limits_{i=x}^ya_i|n
i=x∑yai∣n。
对于一个取法,我们设有
p
p
p件不是s的物品被我们选中。
如果
p
>
=
a
s
p>=a_s
p>=as,则这一定不是最优解。
根据上面的定理,对于
p
p
p项数列
c
i
c_i
ci,一定有一些物品体积的和是
a
s
a_s
as的倍数,那么我们用s来替换这些物品一定会更优。
这样我们就确定了范围。
我们在之间已经将 n n n件物品取了最优的,剩下来的物品种类不超过 100 100 100种,所以在剩余空间小于等于 100 a s 100a_s 100as时做一遍完全背包,在大于 100 a s 100a_s 100as时贪心的取s。
#include <cstdio>
#include <iostream>
#include <algorithm>
#define int long long
using namespace std;
typedef double db;
const int N = 110;
const int INF = 0x3f3f3f3f;
struct node{
int v,minw;
}a[N],b[N];
int n,m,sv,sw,ans,cntb;
int f[N*N];
bool cmp(node x,node y){
return x.v*y.minw==y.v*x.minw ? x.minw<y.minw : x.v*y.minw>y.v*x.minw;
}
signed main(){
freopen("backpack.in","r",stdin);
freopen("backpack.out","w",stdout);
scanf("%lld%lld",&n,&m);
for(int i=1; i<=100; i++){
a[i].v=i;
a[i].minw=INF;
}
for(int i=1; i<=n; i++){
int v,w;
scanf("%lld%lld",&w,&v);
a[v].minw=min(a[v].minw, w);
}
for(int i=1; i<=100; i++){
if(a[i].minw!=INF){
b[++cntb]=a[i];
}
}
sort(b+1, b+cntb+1, cmp); // 按照性价比排序
sv=b[1].v; // 取出最优的s
sw=b[1].minw;
int left=m-sw*100; // 在剩余空间足够的时候取s
if(left>0){
int t=left/sw;
ans+=t*sv;
m-=t*sw;
}
// 做一遍完全背包
for(int i=1; i<=cntb; i++){
for(int j=b[i].minw; j<=m; j++){
f[j]=max(f[j], f[j-b[i].minw]+b[i].v);
}
}
printf("%lld\n",ans+f[m]);
return 0;
}
T2 中间值
Description
听过原题,不会做了,有什么好说哒?
Solution
序列有性质:非严格单调递增。
我们考虑求两个序列合并后的第
k
k
k大值。
对于a序列的[l1, r1]和b序列的[l2, r2],我们取它们的第前
k
2
\frac{k}{2}
2k项进行比较。(令为a[x], b[y])
如果a[x]<=b[y]
说明第
k
k
k项一定不在a序列的[l1, x]上。
反之说明第
k
k
k项一定不在b序列的[l2, y]上。
分治查找即可。
这份代码常数十分大,甚至会TLE,请加上快读之类的优化使用。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 5010;
const int INF = 0x3f3f3f3f;
int a[N],b[N],tmp[N];
int n,m;
int work(int l1,int r1,int l2,int r2){
int cnt=0,len=r1-l1+1+r2-l2+1;
for(int i=l1; i<=r1; i++){
tmp[++cnt]=a[i];
}
for(int i=l2; i<=r2; i++){
tmp[++cnt]=b[i];
}
sort(tmp+1, tmp+1+cnt);
return tmp[(len>>1)+1];
}
int main(){
freopen("median.in","r",stdin);
freopen("median.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++){
scanf("%d",&a[i]);
}
for(int i=1; i<=n; i++){
scanf("%d",&b[i]);
}
for(int i=1; i<=m; i++){
int opt;
scanf("%d",&opt);
if(opt==1){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
if(x==0){
a[y]=z;
}
else{
b[y]=z;
}
}
else if(opt==2){
int l1,r1,l2,r2;
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
printf("%d\n",work(l1, r1, l2, r2));
}
}
return 0;
}
T3 序列
Description
Solution
难顶啊。
咕咕咕
Atcoder ABC 2019.8.18
又是喜闻乐见的At。
A
给定一个数
n
n
n,一个字符串
s
s
s。
如果
n
>
=
3200
n>=3200
n>=3200,输出 red。
否则输出 字符串s。
B
给定一个
n
n
n个元素的序列
a
a
a。
输出
1
∑
i
=
1
n
1
a
i
\frac{1}{\sum\limits_{i=1}^n \frac{1}{a_i}}
i=1∑nai11。
C
有
n
n
n个数,每次操作可以选择两个数
a
a
a,
b
b
b,将他们变成
a
+
b
2
\frac{a+b}{2}
2a+b。
问怎么变使得最后的结果最大?
贪心选择最小的两个数先和在一起。
然后顺着和就可以了。
D
有一个
n
n
n个节点的树,每个节点有一个值
c
n
t
i
cnt_i
cnti,初始为
0
0
0。
给定
q
q
q个操作,每次操作给定两个数
p
p
p和
x
x
x。
将以
p
p
p为根的子树中每个节点的
c
n
t
cnt
cnt都加上
x
x
x。
问在
q
q
q个操作后每个节点的
c
n
t
i
cnt_i
cnti。
开一个
l
a
z
y
lazy
lazy数组,每次操作直接在
l
a
z
y
p
lazy_p
lazyp上加
x
x
x。
一次dfs求出所有节点的
c
n
t
cnt
cnt值。
E
有两个字符串
s
s
s和
t
t
t。
将字符串
s
s
s重复
1
0
100
10^{100}
10100次变成
s
′
s'
s′,你可以任意删去字符使得
s
′
s'
s′和
t
t
t相等。
输出最短长度。
例如
s
s
s=“contest”,
t
t
t=“son”。
当
s
′
s'
s′="contestcon"时,删去[0, 4],[6, 7],
s
′
s'
s′=
t
t
t。
所以输出len(s’)=10。
扫一遍字符串 s s s,记录每个字母出现的位置,因为是遍历的 s s s,位置具有单调性,这为我们后续操作提供了遍历。
扫一遍字符串
t
t
t,记录上一次选取的字母在
s
s
s的位置
l
a
s
t
last
last,查询最小的大于
l
a
s
t
last
last的
t
i
t_i
ti出现的位置
p
p
p。
如果没有,记
t
i
t_i
ti最早出现的位置是
q
q
q。
a
n
s
+
=
l
e
n
s
−
l
a
s
t
+
q
ans+=lens-last+q
ans+=lens−last+q
如果有,
a
n
s
+
=
q
−
l
a
s
t
ans+=q-last
ans+=q−last
复杂度 O ( l e n t × l o g 2 l e n s ) O(lent\times log_2lens) O(lent×log2lens)
代码参考:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <ctime>
#include <cstdlib>
#include <vector>
#include <algorithm>
#define int long long
using namespace std;
const int L = 100010;
const int N = 27;
char s[L],t[L];
int lens,lent,ans;
int p[N][L];
int cnt[N];
int id(char s){
return s-'a'+1;
}
signed main(){
scanf("%s",s+1);
scanf("%s",t+1);
lens=strlen(s+1);
lent=strlen(t+1);
for(int i=1; i<=lens; i++){
p[id(s[i])][++cnt[id(s[i])]]=i;
}
int last=0;
for(int i=1; i<=lent; i++){
if(cnt[id(t[i])]==0){
puts("-1");
return 0;
}
int pos=upper_bound(p[id(t[i])]+1, p[id(t[i])]+1+cnt[id(t[i])], last)-p[id(t[i])];
int d=p[id(t[i])][pos];
if(pos==cnt[id(t[i])]+1){
ans+=lens-last;
ans+=p[id(t[i])][1];
last=p[id(t[i])][1];
}
else{
ans+=d-last;
last=d;
}
}
printf("%lld\n",ans);
return 0;
}