jz集训 8.18

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=0iaj,sum有n+1项。一定存在两项模n相等,设这两项为axa_xaxaya_yay,则∑i=xyai∣n\sum\limits_{i=x}^ya_i|ni=xyain

对于一个取法,我们设有ppp件不是s的物品被我们选中。
如果p&gt;=asp&gt;=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&gt;=3200n&gt;=3200n>=3200,输出 red。
否则输出 字符串s。

B

给定一个nnn个元素的序列aaa
输出1∑i=1n1ai\frac{1}{\sum\limits_{i=1}^n \frac{1}{a_i}}i=1nai11

C

nnn个数,每次操作可以选择两个数aaa,bbb,将他们变成a+b2\frac{a+b}{2}2a+b
问怎么变使得最后的结果最大?

贪心选择最小的两个数先和在一起。
然后顺着和就可以了。

D

有一个nnn个节点的树,每个节点有一个值cnticnt_icnti,初始为000
给定qqq个操作,每次操作给定两个数pppxxx
将以ppp为根的子树中每个节点的cntcntcnt都加上xxx
问在qqq个操作后每个节点的cnticnt_icnti

开一个lazylazylazy数组,每次操作直接在lazyplazy_plazyp上加xxx
一次dfs求出所有节点的cntcntcnt值。

E

有两个字符串sssttt
将字符串sss重复1010010^{100}10100次变成s′s&#x27;s,你可以任意删去字符使得s′s&#x27;sttt相等。
输出最短长度。
例如sss=“contest”,ttt=“son”。
s′s&#x27;s="contestcon"时,删去[0, 4],[6, 7],s′s&#x27;s=ttt
所以输出len(s’)=10。
在这里插入图片描述

扫一遍字符串sss,记录每个字母出现的位置,因为是遍历的sss,位置具有单调性,这为我们后续操作提供了遍历。

扫一遍字符串ttt,记录上一次选取的字母在sss的位置lastlastlast,查询最小的大于lastlastlasttit_iti出现的位置ppp
如果没有,记tit_iti最早出现的位置是qqqans+=lens−last+qans+=lens-last+qans+=lenslast+q
如果有,ans+=q−lastans+=q-lastans+=qlast

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值