7.15欢乐赛题解

#### T1 榴莲罐头

题目

小郭同学的生日快到了, 她打算用N 天来庆祝。编程班的同学们的最近注意力不太集
中, 因此小郭想提供大家最喜欢的榴莲罐头来使大家高兴。她已经计算出了第i 天需要的罐
头数为Xi。
小郭的学校提供了许多服务给编程班的同学们, 包括一个出售食品的小卖部。小卖部出
售的榴莲罐头价格是起伏不定的,第i 天,榴莲罐头的价格是Wi 块钱一罐。小郭同学每天
可以购买任意数量的罐头,这些罐头可以立即发给同学们享用,也可以寄存在小卖部里,小
郭同学随时可以把它们取出来。寄存一个罐头的花费是k 块钱一天。
小郭同学想知道,她最少需要花费多少钱就能满足每天罐头的需求。

【输入】

第一行输入两个整数N 和K, N 表示庆祝活动持续N 天,K 为一个罐头寄存一天的花费。
接下来有N 行,每行输入两个整数Wi 和Xi。Wi 表示第i 天购买一个罐头的费用,Xi
表示第i 天需要的罐头数量。

【输出】

输出一个整数,表示小郭同学所需花费的最少费用。结果可能很大,mod 100000007
后再输出。

【样例输入1】

3 3
1 2
3 2
8 2

【样例输出1】

20

【样例输入2】

4 5
87 18
88 20
96 30
89 32

【样例输出2】

8964

【样例1解释】

共有3 天,寄存的费用为每天3 元
第1 天一共购买2 个罐头共花费1*2=2 元;
第2 天购买4 个罐头共花费3*4=12 元;
第3 天不购买罐头,改用第2 天多购买的2 个罐头,共花费2*3=6 元的寄存费用。
总花费2+12+6=20 元。

【数据范围】

对于30%的数据,1<=N<=100, 0<=K<=100。
对于70%的数据,1<=N<=5000, 0<=K<=1000。
对于100%的数据,1<=N<=1000000, 0<=K<=10000, 0<=Wi,Xi<=50000。
 

思路

首先观察数据范围, 1<=N<=1000000 ,时间复杂度肯定是 O(n) 或者 O(nlogn)
O(n) 的算法很明显可以考虑贪心
贪心思路:
建立两个数组, F 数组存贮每天罐头的最优价格, X 数组存贮每天需要的罐头数量
每天需要的罐头有只有两种来源,要么买当天的,要么用寄存的。
F[i] 表示第 i 天罐头的最优价格。
F[i] 有两种抉择:要么新买,价格为 W[i] 。要么用寄存价格为 F[i-1]+K
F[i]=min{ F[i-1]+K W[i] }
初值 Ans=0 Ans 加上每天产生的费用 F[i]*X[i] 的值,输出 Ans 为最终答案
也可以不开数组,一边输入一边维护,并让 ans 加上最优值

代码实现

#include<cstdio>
#include<iostream>
#define p 100000007
using namespace std;
inline void _in(long long &d)
{
	char t=getchar();
	while(!isdigit(t))t=getchar()//isdigit用来判断是否为数字 
	for(d=0;isdigit(t);t=getchar())d=d*10+t-48;
}
int n;
long long Min=0x3FFFFFFE,k,a,b,ans;
int main()
{
	//freopen("bignews12.in","r",stdin);
	//freopen("can.out","w",stdout); 
	cin>>n>>k;
	while(n--)
	{
		_in(a);_in(b);
		Min=(Min+k>a)?a:Min+k;
		ans=(ans+Min*b)%p;
	}
	cout<<ans;
}

收获

贪心应用以及isdigit

#### T2 哥斯拉大战金刚

题目

众所周知,哥斯拉和金刚是时代仇敌,大战一触即发。金刚为了打败哥斯拉,要先前往地心空洞获得战斧。金刚现在所在之处可以被视为一个n*m的网格图,S表示金刚目前的位置,T表示地心空洞的入口,X表示障碍物,.表示平地。在前往地心空洞之前,金刚必须先获得一系列打开地心空洞的钥匙(在地图上通过数字1,2,…,k表示),并且获得i类钥匙的前提是金刚已经获得了1,2,…,i-1类钥匙,金刚在拿到地图上所有种类的钥匙之后即可前往地心空洞的入口。另外,同一种类的钥匙可能有多把,金刚只需获得其中任意一把即可。金刚每一步可以朝上下左右四个方向中的一个移动一格,值得注意的是,哥斯拉为了阻挠金刚的计划,还在地图上设置了q个陷阱(在网格图中用G表示),金刚第一次进入某个陷阱需要花费额外的一步来破坏陷阱(这之后该陷阱即可被视为平地)。为了更好的掌握全局,请你帮金刚计算到达地心空洞入口所需要花费的最少步数。输入数据保证有解。

【输入】

第一行输入两个整数n,m,表示网格图的大小。

接下来n行,每行输入m个字符,表示地图。

【输出】

输出一行包含一个整数,表示金刚到达地心空洞入口所需要花费的最少步数。

【样例输入1】

```
5 5
XX13X
X.GXX
S...T
XXGXX
....2
```

【样例输出1】

```
24
```

【数据范围】

对于20%的数据,1<=n,m<=10
对于另30%的数据,没有陷阱
对于100%的数据,1<=n,m<=100,1<=k<=9,1<=q<=7


思路

注意观察题目范围,小范围数据往往带有对正解的提示。
对于 30% 的数据,没有陷阱:
那么在该数据范围下可以轻松用 bfs 解决
一个比较显然的状态是:f[i][j][k]表示走到(i,j)这个格子,并且已经获得了前 k种钥匙所需的最小步
数,那么转移也比较直接,只需考虑上下左右四个方向,并判断是否能获得新一种类的钥匙即可得到转移的目标状态。
对于 70% 的数据,有陷阱:
此时状态肯定需要有所改变,题中所述的陷阱复杂之处在于只在第一次进入时花费额外的一单位时间, 所以在状态种肯定还需要纳入陷阱是否被破坏。
注意到陷阱数量非常少不超过 7 个,那么可以用状态压缩来记录陷阱的破坏状态,f[i][j][k][h]表示走到(i,j)这个格子,并且已经获得了前k种钥匙,且当前陷阱的破坏状态(状态压缩)为h所需的最小步数, 此时空间复杂度为2^{q}nmk,显然没有问题。
在状态表示中可以轻松得到陷阱是否需要花费额外时间,那么转移也就很简单了,只需判断是否踩上一 个未被破坏的陷阱即可,如果是,那么时间+2 ,并且修改状态 h ,否则当成平地处理。

代码实现

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
template <typename T>
inline void _read(T& x){
	char ch=getchar();bool sign=true;
	while(!isdigit(ch)){if(ch=='-')sign=false;ch=getchar();}
	for(x=0;isdigit(ch);ch=getchar())x=(x<<1)+(x<<3)+ch-'0';
	if(!sign)x=-x;
}
int n,m;
int dx[4]={0,1,0,-1};
int dy[4]={1,0,-1,0};
int dis[105][105][11][135]; //dis[i][j][k][l]
char a[105][105];
int b[105][105];
int kx,ky,Tx,Ty;
struct node{
	int x,y,keys,godz;
	node(){}
	node(int X,int Y,int K,int S){x=X;y=Y;keys=K;godz=S;}
};
int main(){
	freopen("Godzilla.in","r",stdin);
	freopen("Godzilla.out","w",stdout);
	int i,j,k;
	int cnt=0,all,max_key=0;
	memset(dis,0x3c,sizeof(dis));
	cin>>n>>m;
	for(i=1;i<=n;i++){
		scanf("%s",a[i]+1);
		for(j=1;j<=m;j++){
			if(a[i][j]=='S'){
				kx=i;ky=j;
			}
			if(a[i][j]=='T'){
				Tx=i;Ty=j;
			}
			if(a[i][j]=='G'){
				b[i][j]=++cnt;
			}
			if(isdigit(a[i][j]))max_key=max(max_key,a[i][j]-'0');
		}
	}
	all=(1<<cnt)-1;
	queue<node> q;
	dis[kx][ky][0][0]=0;
	q.push(node(kx,ky,0,0)); 
	int ans=1e9;
	while(q.size()){
		node t=q.front();q.pop();
		int step=dis[t.x][t.y][t.keys][t.godz];
		if(t.keys==max_key&&a[t.x][t.y]=='T'){
			ans=min(ans,step);
		}
		for(i=0;i<4;i++){
			int tx=t.x+dx[i],ty=t.y+dy[i],tk,ts;
			if(tx<1||ty<1||tx>n||ty>n||a[tx][ty]=='X')continue;
			if(isdigit(a[tx][ty])){
				tk=t.keys;if(a[tx][ty]-'0'==t.keys+1)tk++;
				ts=t.godz;
				if(dis[tx][ty][tk][ts]>step+1){
					dis[tx][ty][tk][ts]=step+1;
					q.push(node(tx,ty,tk,ts));
				} 
			}
			else if(a[tx][ty]=='G'){
				tk=t.keys;
				ts=(t.godz|(1<<(b[tx][ty]-1)));
				int ss;
				if((t.godz&(1<<(b[tx][ty]-1)))==0)ss=step+2;
				else ss=step+1;
				if(dis[tx][ty][tk][ts]>ss){
					dis[tx][ty][tk][ts]=ss;
					q.push(node(tx,ty,tk,ts));
				} 
			}
			else {
				tk=t.keys;
				ts=t.godz;
				if(dis[tx][ty][tk][ts]>step+1){
					dis[tx][ty][tk][ts]=step+1;
					q.push(node(tx,ty,tk,ts));
				} 
			}
		}
	}
	cout<<ans;
}

 收获

状态压缩,数组新用法,复习了结构体。

#### T3 长城

为了抵御外敌,在长城沿线从西向东有连绵不断的 n 个烽火台,第 i 个烽火台的高度记为 $a_i$。

守卫们需要按照一定的策略将这 n 个烽火台划分成 k 组,每一组包含若干连续的烽火台(至少一个)。每一组烽火台能带来的防御值定义为该组烽火台内的最高高度和最低高度之差,守卫们当然想使得划分后能带来最大的防御值之和。

你需要帮守卫们计算对于每一个 k=1,2,...,n,最大的防御值之和是多少。

【输入】

第一行一个整数 n,表示烽火台的个数。

第二行 n 个整数 $a_1,a_2,...,a_n$,分别表示这 n 个烽火台的高度。

【输出】

输出 n 行,第 i 行输出一个整数,表示在 k=i 时最大的防御值。

【样例输入1】

```
5
1 2 3 4 5
```

【样例输出1】

```
4
3
2
1
0
```

【样例输入2】

```
5
1 2 1 2 1
```

【样例输出2】

```
1
2
2
1
0
```

【数据范围】

对于20%的数据,1<=n<=200
对于50%的数据,1<=n<=400
对于100%的数据,1<=n<=10000,1<=a_i<=10^5

思路

T3
根据题目背景,很容易能看出这是一个动态规划题。
对于 50% 的数据, N<=400 ,能够支 n^3 的算法。
一个比较直接的状态是f[i][j]表示前i 个烽火台划分成 j 段的最大防御值。
转移也不难想,决策只需要考虑最后一段包含哪些烽火台即可:f[i][j]=max{f[k][j-1]+W(k+1,i)}
,其中W(i.j)表示a[i],.....,a[j]
中最大最小值之差。
那么显然,该转移的时间复杂度是 n^{3},可以拿到 50% 的分数。
接下来考虑 100% 的分数,考虑优化上述算法降低时间复杂度。 首先注意到一个性质:一定存在一种最优方案,使得每一段的第一个一定是这一段的最小值或最大值。
证明是显然的,因为如果第一个既不是最大也不是最小,那么把它分给前一段一定不亏。
那么我们可以利用这个性质来优化上述算法:
假设我们已经算完了f[...][k]
,接下来我们要算f[....][k+1]
构造g1[i]=max{f[i-1][k]+a[j]},g2[i]=max{f[j-1][k]-a[j]},j小于等于i
构造h[i]=max{gi[i]-a[i],g2[i]+a[i]},那么根据该定义,可以知道 h[i]表示最后一段的第一个为
最小或最大的情况下,以a[i]作为最后一段中的最大或最小的最大防御值。
那么由于我们并不知道每一段的最后一个是否是最大或者最小, f[i][k+1]只需再对 取前缀最大即可。 有同学可能会有疑问,我们在计算h[i]的时候,直接把a[i]当成了最大或最小来计算,然而我们其实并不 知道是否它真的是最后一段的最大或最小。这个问题其实是不需要考虑的,因为最优解一定会被我们的策略考虑到,并且最优解的防御值也会更大(因为不合法情况也即a[i] 并非真的最大或最小时,得到的最 后一段的防御值一定没有取真的最大或最小时大),所以那些不合法的情况对应的防御值也会更小,最 后得到的最大防御值一定对应的是合法解。

代码实现

#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#define maxn 10005
#define inf 1e9
using namespace std;
int n;
int a[maxn];
int f[maxn][maxn];  
int main(){
	freopen("GreatWall.in","r",stdin);
	freopen("GreatWall.out","w",stdout);
	int i,j,k;
	cin>>n;
	for(i=1;i<=n;i++)scanf("%d",&a[i]);
	for(i=1;i<=n;i++){
		for(j=1;j<=i;j++){
			int Max=a[i],Min=a[i];
			for(k=i-1;k>=j-1;k--){
				f[i][j]=max(f[i][j],f[k][j-1]+Max-Min);
				Max=max(Max,a[k]);
				Min=min(Min,a[k]);
			}
		}
	}
	for(i=1;i<=n;i++)printf("%d\n",f[n][i]);
}

收获

这个动态规划搞崩我,真的不会

#### T4 照相

毕业季来了,同学们都忙着拍纪念照。

班上总共有 n 个同学,他们依次具有 1-n 的编号,并且 i 号同学的高度是 $h_i$。他们想要先在校门口拍照,以及在其它 q 个具有纪念意义的地方拍照。对于每一个地方,这 n 个同学会以一个已知的顺序依次到达。并且为了能够得到尽可能多的个人参与度,每到达一个同学就会拍一张照片,所以每一个地方都会拍 n 张照片。在一个地方拍完这 n 张照片后,同学们出发前往下一个地点继续拍照。

为了确保有序性,在照每一张照片的时候,参与的同学们总是按照编号从小到大的顺序站成一排。同时,一张照片的有序值被定义为所有相邻两同学身高之差的平方之和。你的任务是,对于每一个地点,求出在该地点的 n 张照片的有序值之和。

【输入】

第一行两个整数,n,q,分别表示学生的个数和有纪念意义的地点个数。

第二行包含 n 个整数,$h_1,h_2,...,h_n$,分别表示这 n 个同学的身高。

第三行包含 n 个整数(1-n的一个排列),$p_1,p_2,...,p_n$,表示同学们在校门口的到达顺序,$p_i$表示第 i 个到达校门口的同学编号。

在接下来的 q 行中,每一行包含一个整数 k,表示同学们到达该地点的顺序为到达上一个地点的顺序向左轮转 (k+lastans) 次,lastans 表示在上一个地点拍的 n 张照片的有序值之和。

注意:顺序$p_1,p_2,...,p_n$向左轮转一次得到的顺序是$p_2,p_3,...,p_n,p_1$。

【输出】

输出 q+1 行,第 i 行输出在第 i 个地方拍的 n 张照片的有序值之和,第一个地方是校门口。

【样例输入1】

```
5 4
1 2 3 4 5
1 2 3 4 5
6
6
8
10
```

【样例输出1】

```
10
10
13
21
36
```

【样例1解释】

对于样例1,在每个地点的学生到达顺序依次为:
[1,2,3,4,5] -> [2,3,4,5,1] -> [3,4,5,1,2] -> [4,5,1,2,3] -> [5,1,2,3,4]

【数据范围】

对于30%的数据,1<=n<=100,1<=q<=100
对于另30%的数据,1<=n<=100000,1<=q<=10
对于100%的数据,1<=n<=100000,1<=q<=100,1<=h_i<=10^4,0<=k<=10^9

思路

题目要求强制在线,就不考虑离线做法了。
要求:已知在某个地点同学们的到达顺序,计算每张照片的有序值之和。
要想拿到 60 分并不难,理清题意,对于某个固定的地点,假设我们已经算完了前 i 张照片的有序值,不妨记第 i 张照片的有序值为 val 。那么我们想知道第 i+1 张照片的有序值,其实我们只需要知道第 i+1 个 到来的人(假设编号为id )该站那个位置即可,题目已知学生按照编号从小到大排列,所以只需一个简单的数据结构即可实现这个功能,不妨用set ,如果 set 中的编号都比id大,那么新的有序值为val+(h[id]-h[set_m_i])^2中 的 最 小 编 号;同理,如果如果set 中的编号都比 id 小,那么新的有序值为中 的 最 大 编 号val+(h[id]-h[set_m_a])^{2};否则,id号人一定是插在原有的两个人之间,不妨记这两个人的 id是x,y,那么新的有序值为val-(h[x]-h[y])^{2}+(h[x]-h[id])^{2}+(h[y]-h[id])^{2} =val+2h[id]^{2}-2h[id]*h[x]-2h[id]*h[y]+2h[x]h[y] 该方法的时间复杂度为O(nlogn),因为对于每一个地点,计算完n 张照片的复杂度都是
nlogn100 分只需考虑如何将复杂度降低到O(qn),也即去掉数据结构操作花费的nlogn
考虑数据结构实现的功能:找出已有的第 i 个加入的人最相邻的两个编号,这个能否优化呢?
不妨设在该地点下第 i 个到达的人是b[i],构造数组a[b[i]]=i
,那么a[i]就表示编号为 i 的人到达的时间。在a 数组下进行考虑, 已有的第 i 个加入的人最相邻的两个编号 就变成了a[i]左边和右边第一个比a[i]小的元素的下标,该问题可以通过维护一个单调上升的单调栈轻松解决,正反两次即可求得每一个左 边和右边第一个比 小的元素的下标,也即“ 每个人加入时最相邻的两个已有编号 。其他的更新计算等 均保持不变,时间复杂度通过引入单调栈优化掉了logn,总时间复杂度O(qn)
这个思路当然不是我写的,我只是个蒻苣。

代码实现

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<set>
#define maxn 100005
#define ll long long
using namespace std;
int n,t;
int h[maxn],p[maxn],a[maxn]; //h[i]:i鍙蜂汉鐨勯珮搴? p[i]:鍒濆鎺掑垪涓i涓汉鐨勭紪鍙? a[i]:i鍙蜂汉杩涘叆闃熷垪鐨勬椂闂?
ll solve(int pos){
	set<int> S;
	int i,j,k,l,r,id;
	ll ans=0,temp=0;
	S.insert(p[pos]);
	set<int>::iterator it;
	for(i=1;i<n;i++){
		pos++;if(pos>n)pos-=n;
		id=p[pos];
		it=S.lower_bound(id);
		if(it==S.begin()){
			r=*it;
			temp+=1ll*(h[id]-h[r])*(h[id]-h[r]);
		}
		else if(it==S.end()){
			it--;
			l=*it;
			temp+=1ll*(h[id]-h[l])*(h[id]-h[l]);
		}
		else {
			r=*it;
			it--;
			l=*it;
			temp+=(2ll*h[id]*h[id]-2ll*(h[l]*h[id]+h[r]*h[id]-h[l]*h[r]));
		}
		ans+=temp;
		S.insert(id);
	}
	return ans;
}
int main(){
	freopen("Photo.in","r",stdin);
	freopen("Photo.out","w",stdout);
	int i,j,k,pos=1;
	ll ans;
	cin>>n>>t;
	for(i=1;i<=n;i++)scanf("%d",&h[i]);
	for(i=1;i<=n;i++)scanf("%d",&p[i]);
	ans=solve(pos);
	printf("%lld\n",ans);
	while(t--){
		scanf("%d",&k);
		pos+=(ans+k)%n;
		if(pos>n)pos-=n;
		ans=solve(pos);
		printf("%lld\n",ans);
	}
}
/*
5 4
1 2 3 4 5
1 2 3 4 5
6
6
8
10
*/

收获

新思路

小结

 没有小结,直接崩溃,手撸代码的时候还没开空调,快热死了

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值