Day 18
50
T1 完全背包
Description
有一个容量为m的背包和n种物品,每种物品有价值vi和体积wi,且有无限件。问最大价值是多少。
20% n,m<=10310^3103
40% n,m<=10410^4104 ai,bia_i,b_iai,bi<=101010
60% n,m<=10510^5105
100% n<=10610^6106,m<=101610^161016,ai,bi<=100100100。
Solution
40pts
直接背包即可,复杂度O(NM)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很大,ai,bia_i,b_iai,bi很小。
有n件物品,肯定会有aia_iai一样的物品,我们对于每种体积的物品,只留下价值最大的。
这样就只剩下最多100100100件物品。
对于这些物品按照性价比排序,取出性价比最高的,且体积最小的物品,设它为s。
我们如果完全贪心的选择s放入,不一定最优。
注意到当背包剩余空间足够大的时候,我们放s进去一定是最好的。
那足够大的界限在哪里呢?
首先我们知道,对于一个元素个数为n的数列a,一定存在一些数的和是n的倍数。
证明:
我们对a做一个前缀和,设sumi=∑j=0iajsum_i=\sum\limits_{j=0}^{i}a_jsumi=j=0∑iaj,sum有n+1项。一定存在两项模n相等,设这两项为axa_xax,aya_yay,则∑i=xyai∣n\sum\limits_{i=x}^ya_i|ni=x∑yai∣n。
对于一个取法,我们设有ppp件不是s的物品被我们选中。
如果p>=asp>=a_sp>=as,则这一定不是最优解。
根据上面的定理,对于ppp项数列cic_ici,一定有一些物品体积的和是asa_sas的倍数,那么我们用s来替换这些物品一定会更优。
这样我们就确定了范围。
我们在之间已经将nnn件物品取了最优的,剩下来的物品种类不超过100100100种,所以在剩余空间小于等于100as100a_s100as时做一遍完全背包,在大于100as100a_s100as时贪心的取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
序列有性质:非严格单调递增。
我们考虑求两个序列合并后的第kkk大值。
对于a序列的[l1, r1]和b序列的[l2, r2],我们取它们的第前k2\frac{k}{2}2k项进行比较。(令为a[x], b[y])
如果a[x]<=b[y]
说明第kkk项一定不在a序列的[l1, x]上。
反之说明第kkk项一定不在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
给定一个数nnn,一个字符串sss。
如果n>=3200n>=3200n>=3200,输出 red。
否则输出 字符串s。
B
给定一个nnn个元素的序列aaa。
输出1∑i=1n1ai\frac{1}{\sum\limits_{i=1}^n \frac{1}{a_i}}i=1∑nai11。
C
有nnn个数,每次操作可以选择两个数aaa,bbb,将他们变成a+b2\frac{a+b}{2}2a+b。
问怎么变使得最后的结果最大?
贪心选择最小的两个数先和在一起。
然后顺着和就可以了。
D
有一个nnn个节点的树,每个节点有一个值cnticnt_icnti,初始为000。
给定qqq个操作,每次操作给定两个数ppp和xxx。
将以ppp为根的子树中每个节点的cntcntcnt都加上xxx。
问在qqq个操作后每个节点的cnticnt_icnti。
开一个lazylazylazy数组,每次操作直接在lazyplazy_plazyp上加xxx。
一次dfs求出所有节点的cntcntcnt值。
E
有两个字符串sss和ttt。
将字符串sss重复1010010^{100}10100次变成s′s's′,你可以任意删去字符使得s′s's′和ttt相等。
输出最短长度。
例如sss=“contest”,ttt=“son”。
当s′s's′="contestcon"时,删去[0, 4],[6, 7],s′s's′=ttt。
所以输出len(s’)=10。
扫一遍字符串sss,记录每个字母出现的位置,因为是遍历的sss,位置具有单调性,这为我们后续操作提供了遍历。
扫一遍字符串ttt,记录上一次选取的字母在sss的位置lastlastlast,查询最小的大于lastlastlast的tit_iti出现的位置ppp。
如果没有,记tit_iti最早出现的位置是qqq。ans+=lens−last+qans+=lens-last+qans+=lens−last+q
如果有,ans+=q−lastans+=q-lastans+=q−last
复杂度 O(lent×log2lens)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;
}