test20190803 夏令营NOIP训练19
60+100+0=160
贪婪大陆
面对蚂蚁们的疯狂进攻,小FF的Tower defence宣告失败……人类被蚂蚁们逼到了Greed Island上的一个海湾。现在,小FF的后方是一望无际的大海, 前方是变异了的超级蚂蚁。 小FF还有大好前程,他可不想命丧于此, 于是他派遣手下最后一批改造SCV布置地雷以阻挡蚂蚁们的进攻。
小FF最后一道防线是一条长度为N的战壕, 小FF拥有无数多种地雷,而SCV每次可以在[ L , R ]区间埋放同一种不同于之前已经埋放的地雷。 由于情况已经十万火急,小FF在某些时候可能会询问你在[ L' , R'] 区间内有多少种不同的地雷, 他希望你能尽快的给予答复。
输入格式:
第一行为两个整数n和m; n表示防线长度, m表示SCV布雷次数及小FF询问的次数总和。
接下来有m行, 每行三个整数Q,L , R; 若Q=1 则表示SCV在[ L , R ]这段区间布上一种地雷, 若Q=2则表示小FF询问当前[ L , R ]区间总共有多少种地雷。
输出格式:
对于小FF的每次询问,输出一个答案(单独一行),表示当前区间地雷总数。
样例输入:
5 4
1 1 3
2 2 5
1 2 4
2 3 5
样例输出:
1
2
数据范围:
对于30%的数据: 0<=n, m<=1000;
对于100%的数据: 0<=n, m<=10^5.
时间限制:
1S
空间限制:
50M
题解
看题发现答案=区间内端点的个数-区间内包含的线段个数+包含了区间的线段个数。
先想分块,左端点分块右端点排序,搞出来一个\(O(n \sqrt{n} \log n)\)的做法,放弃。
然后发现线段树套平衡树直接就可以做,码出来发现set的迭代器不支持减法。
于是我去学__gnu_pbds::tree,搞了好久终于对了。时间复杂度\(O(n \log^2 n)\),60分。上厕所听别人说那个分块算法有80……
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
#define pbds __gnu_pbds
#define co const
#define il inline
template<class T> T read(){
T x=0,w=1;char c=getchar();
for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
return x*w;
}
template<class T>il T read(T&x){
return x=read<T>();
}
typedef pbds::tree<std::pair<int,int>,pbds::null_type,std::less<std::pair<int,int> >,pbds::rb_tree_tag,pbds::tree_order_statistics_node_update> tree;
co int N=100000+1,INF=1e9;
#define lc (x<<1)
#define rc (x<<1|1)
int n,m;
tree ls,rs,s[N<<2];
void insert(int x,int l,int r,int p,int v,int id){
s[x].insert(std::make_pair(v,id));
if(l==r) return;
int mid=(l+r)>>1;
if(p<=mid) insert(lc,l,mid,p,v,id);
else insert(rc,mid+1,r,p,v,id);
}
int qin(int x,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr) return s[x].order_of_key(std::make_pair(qr,INF))-s[x].order_of_key(std::make_pair(ql,-INF));
int mid=(l+r)>>1;
if(qr<=mid) return qin(lc,l,mid,ql,qr);
if(ql>mid) return qin(rc,mid+1,r,ql,qr);
return qin(lc,l,mid,ql,qr)+qin(rc,mid+1,r,ql,qr);
}
int qout(int x,int l,int r,int ql,int qr){
if(r<ql) return s[x].order_of_key(std::make_pair(INF,INF))-s[x].order_of_key(std::make_pair(qr,INF));
int mid=(l+r)>>1;
if(ql<=mid+1) return qout(lc,l,mid,ql,qr);
return qout(lc,l,mid,ql,qr)+qout(rc,mid+1,r,ql,qr);
}
int main(){
// freopen("testdata.in","r",stdin),freopen("testdata.ans","w",stdout);
read(n),read(m);
for(int i=1;i<=m;++i){
int q=read<int>(),l=read<int>(),r=read<int>();
if(q==1){
ls.insert(std::make_pair(l,i)),rs.insert(std::make_pair(r,i));
insert(1,1,n,l,r,i);
}
else{
int ans=0;
ans+=ls.order_of_key(std::make_pair(r,INF))-ls.order_of_key(std::make_pair(l,-INF));
ans+=rs.order_of_key(std::make_pair(r,INF))-rs.order_of_key(std::make_pair(l,-INF));
ans-=qin(1,1,n,l,r);
ans+=qout(1,1,n,l,r);
printf("%d\n",ans);
}
}
return 0;
}
正解是树状数组,我们只需要每次埋地雷的时候,我们只需要标记一下当前区间的开头和结尾,我们查的时候,我们只需要查1到当前区间结尾中包含了多少个开头(一个开头代表了一种地雷)然后我们查1到当前区间前一个位置包含了多少个结尾(一个结尾代表我们一种地雷埋完了,就是我们看前面有几种地雷被埋完了!)然后把这个做差,我们就可以知道有多少种地雷是出现在当前区间中。时间复杂度\(O(n \log n)\)
多人背包
DD 和好朋友们要去爬山啦!他们一共有 K 个人,每个人都会背一个包。这些包的容量是相同的,都是 V。可以装进背包里的一共有 N 种物品,每种物品都有给定的体积和价值。
在 DD 看来,合理的背包安排方案是这样的:
- 每个人背包里装的物品的总体积恰等于包的容量。
- 每个包里的每种物品最多只有一件,但两个不同的包中可以存在相同的物品。
- 任意两个人,他们包里的物品清单不能完全相同。
在满足以上要求的前提下,所有包里的所有物品的总价值最大是多少呢?
输入格式:
第一行有三个整数:K、V、N。
第二行开始的 N 行,每行有两个整数,分别代表这件物品的体积和价值。
输出格式:
只需输出一个整数,即在满足以上要求的前提下所有物品的总价值的最大值。
样例输入:
2 10 5
3 12
7 20
2 4
5 6
1 1
样例输出:
57
数据范围:
总人数 K<=50。
每个背包的容量 V<=5000。
物品种类数 N<=200。
其它正整数都不超过 5000。
输入数据保证存在满足要求的方案。
时间限制:
1S
空间限制:
128M
题解
我发现我不会背包的拓展问题……这就是一个背包的k优解。
f(i,j)表示i体积,j优解。然后用双指针扫描转移即可。
#include<bits/stdc++.h>
#define co const
#define il inline
template<class T> T read() {
T x=0,w=1;
char c=getchar();
for(; !isdigit(c); c=getchar())if(c=='-') w=-w;
for(; isdigit(c); c=getchar()) x=x*10+c-'0';
return x*w;
}
template<class T>il T read(T&x) {
return x=read<T>();
}
using namespace std;
int k,v,n,ans,cnt,now[55];
int V[288],W[288],f[5008][55];
int main() {
read(k),read(v),read(n);
for(int i=0; i<=5000; i++)
for(int j=0; j<=50; j++)f[i][j]=-20021003;
f[0][1]=0;
for(int i=1; i<=n; i++)
read(V[i]),read(W[i]);
for(int i=1; i<=n; i++)
for(int j=v; j>=V[i]; j--) {
int c1=1,c2=1,cnt=0;
while(cnt<=k) {
if(f[j][c1]>f[j-V[i]][c2]+W[i])
now[++cnt]=f[j][c1++];
else now[++cnt]=f[j-V[i]][c2++]+W[i];
}
for(int c=1; c<=k; c++) f[j][c]=now[c];
}
for(int i=1; i<=k; i++) ans+=f[v][i];
printf("%d",ans);
}
新魔法药水
魔法师 DD 想给 MM 送一份生日礼物,可是他没有足够的金币。魔法娴熟的 DD 自然想到了利用自己高明的魔药配制技巧来多赚一些金币。
DD 一共知道 N 种魔药(以 1..N 编号),还掌握 M 种配制魔药的方法(以 1..M 编号)。他掌握的每种配制魔药的方法都可以简单表述如下:将若干种魔药各一瓶倒入坩埚内,用魔杖搅拌的同时施出一个特定的魔法,再经过适当浓缩,就可以得到一瓶新的魔药。
森林里有一家魔法商店,这里不仅出售各种魔药,同时也以比售价略低的价格收购各种魔药。DD 的如意算盘就是:首先用自己攒下的 V 个金币去魔法商店购买一些魔药作为原料,再用一天的时间在家努力地配制,最后把配制好的成品再卖给魔法商店。
然而,由于魔法修为的原因,DD 在一天之内最多只能施出 K 次魔法。
DD 想让你帮他算一算,他最多能够在这一天时间内赚到多少金币呢?
输入格式:
第一行有四个整数:N、M、V、K。
第二行开始的 N 行,每行有两个整数,第 i+1 行的两个整数分别表示第 i 种魔药的销售价和收购价。
第 N+2 行开始的 M 行,每行有若干个整数,表示 DD 知道的一种魔药配制方法。每行的格式都是这样的:第一个整数表示这种魔药配制方法可以得到的一瓶魔药成品的编号。下一个整数 n(n<N),表示这种配制方法需要 n 瓶原料。下面 n 个整数,表示这 n 瓶原料的编号。
输出格式:
只需输出一个整数,表示最多可以赚到金币的数量。
样例输入:
4 2 6 3
1 0
1 0
5 3
20 15
3 2 1 2
4 3 1 2 3
样例输出:
12
数据范围:
药水种类 N<=60。
配制方法数 M<=240。
初始的金币数 V<=1000。
每天可施的魔法数 K<=30。
时间限制:
1S
空间限制:
128M
题解
我想到了状态,但是没时间写了。f(i,j)表示第i个个物品用至多j次魔法的最小代价。然后我不知道怎么转移,想出来一个最短路。
先枚使用的魔法数,再枚魔法,这样进行转移才行。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
int n,m,V,cnt;
int tmp[61][31];//表示当前处理到i次用了j魔法的最低费用
int f[61][31];//表示第i件物品用j次魔法组成的最低费用
//用tmp更新f
int dp[1001][31];//表示当前花费i元用了j次魔法的最大收益
//用dp[i][j]-i更新答案
int v[61];//物品的售价
int id[250],sum[250];//每种魔法的成品和需要的原材料个数
vector<int>s[250];//每种魔法所需的原料是什么
int main()
{
memset(f,63,sizeof(f));
scanf("%d%d%d%d",&n,&m,&V,&cnt);
for(int i=1;i<=n;i++) scanf("%d%d",&f[i][0],&v[i]);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&id[i],&sum[i]);
for(int j=1;j<=sum[i];j++)
{
int x; scanf("%d",&x);
s[i].push_back(x);
}
}
for(int i=1;i<=cnt;i++)
for(int j=1;j<=m;j++)
{
memset(tmp,63,sizeof(tmp));
tmp[0][0]=0;
int T=i-1;
for(int k=0;k<=T;k++)
for(int l=1;l<=sum[j];l++)
{
int x=s[j][l-1];
for(int a=0;a<=k;a++)
tmp[l][k]=min(tmp[l][k],tmp[l-1][k-a]+f[x][a]);
}
f[id[j]][i]=min(f[id[j]][i],tmp[sum[j]][T]);
for(int k=i;k<=cnt;k++) f[id[j]][k]=min(f[id[j]][k],f[id[j]][i]);
}
for(int i=1;i<=V;i++)
for(int j=0;j<=cnt;j++)
{
for(int k=1;k<=n;k++)
for(int l=0;l<=j;l++)
if(f[k][l]<=i)
dp[i][j]=max(dp[i][j],dp[i-f[k][l]][j-l]+v[k]-f[k][l]);
}
printf("%d",dp[V][cnt]);
return 0;
}