jz集训 8.18

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=0iaj,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=xyain

对于一个取法,我们设有 p p p件不是s的物品被我们选中。
如果 p &gt; = a s p&gt;=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 &gt; = 3200 n&gt;=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=1nai11

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&#x27; s,你可以任意删去字符使得 s ′ s&#x27; s t t t相等。
输出最短长度。
例如 s s s=“contest”, t t t=“son”。
s ′ s&#x27; s="contestcon"时,删去[0, 4],[6, 7], s ′ s&#x27; 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+=lenslast+q
如果有, a n s + = q − l a s t ans+=q-last ans+=qlast

复杂度 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值