Gravatar
syzhaoss
积分:1035
提交:316 / 316

子任务1:测试点$1\sim 3$

$1\leq k\leq n\leq15$,显然可以使用搜索法。

枚举所有括号序列,判断其是否合法。

  1、连续的*不能超过k个。

  2、括号要匹配。

  3、SAS类型是非法括号序列。

时间复杂度O($N\times 3^N$),预计得分15分。

ps:可直接跳到子任务4。

子任务2:测试点$1\sim 13$

显然是一道区间DP问题。

首先明确,合法的括号序列应该是(),(S),(A),(SA),(AS),AA,ASA。

定义$d(l,r)$表示区间$[l,r]$的子串是合法括号序列的方案数。通过观察可以发现,$d(l,r)$可以由S序列转移得到,因此需要定义额外的状态,定义$s(l,r)$表示区间$[l,r]$能否构成合法的S型。

那么对于(),(S),(A),(SA),(AS)型,显然有:

 $d(l,r)=1$, ()型

 $d(l,r)+=1$, (S)型,$s(l+1,r-1)=1$

 $d(l,r)+=d(l+1,r-1)$, (A)型

 $d(l,r)+=d(l+tk+1,r-1)$, (SA)型,$1\leq tk\leq k,s(l+1,l+tk)=1$

 $d(l,r)+=d(l+1,r-tk-1)$, (AS)型,$1\leq tk\leq k,s(r-tk,r-1)=1$

对于AA、ASA型,需要枚举中间的分隔点$j(l\leq j\leq r)$:

 $d(l,r)+=d(l,j)*d(j+1,r)$, (AA)型

 $d(l,r)+=d(l,j)*d(j+tk+1,r)$, (ASA)型,$1\leq tk\leq k,s(j+1,j+tk)=1$

但是,对于部分括号序列,会出现重复计数,例如()()()会被识别成A|AA或者AA|A,()*()*()会被识别成AS|ASA或ASA|S|A。

为了避免重复计数,要求在合并时只能合并一次,因此定义状态$a(l,r)$表示单独的A类型,也即(),(S),(A),(SA),(AS)型,这些类型的特点是不能拆分。因此,状态转移方程需要做出如下修改:

 $a(l,r)=d(l,r)$,(),(S),(A),(SA),(AS)型

对于AA,ASA型,这是出现重复计数的类型:

 $d(l,r)+=a(l,j)*d(j+1,r)$, (AA)型

 $d(l,r)+=a(l,j)*d(j+tk+1,r)$, (ASA)型,$1\leq tk\leq k,s(j+1,j+tk)=1$

综上,算法的流程如下:

 1、预处理得出所有的$s(l,r)$。

 2、从小到大依次枚举每个区间$[l,r]$,求出相应的$d(l,r)$。

 3、最终的答案即为$d(1,n)$。

注意:在运算过程中需要不断对$10^9+7$取余。

注意:通过观察可以得出,首字符必是(,尾字符必是),可以进行特殊处理。

时间复杂度O($N^4$),预计得分65分。

子任务3:测试点$1\sim 15$

对于测试点$14\sim 15$,字符串中仅含?。那么状态$d(l_1,r_1+k)$和$d(l_2,r_2+k)$虽然表示的区间不同,但是结果必然是相同的。

因此,定义状态$f(i)$表示$i$个?构成的合法的序列的方案数。那么,状态转移方程修改为:

$f(1)=0$,一个?

$f(2)=1$,()型

$f(i)+=1$,(S)型

$f(i)+=f(i-2)$,(A)型

$f(i)=f(i-2-tk)$,(AS),(SA)型,$1\leq tk\leq k$

$f(i)=f(j)*f(i-j)$,AA型,$2\leq j\leq i - 2$

$f(i)=f(j)*f(i-j-tk)$,ASA型,$2\leq j\leq i-2,1\leq tk\leq k$

同样,也需要考虑重复计数的问题,定义$g(i)$表示单独的A类型的方案数。

那么状态转移方程需要做出部分修改:

$g(i)=f(i)$,()(S)(A)(SA)(AS)型

$f(i)=g(j)*f(i-j)$,AA型,$2\leq j\leq i - 2$

$f(i)=g(j)*f(i-j-tk)$,ASA型,$2\leq j\leq i-2,1\leq tk\leq k$

时间复杂度:O($N^3$),预计得分75分。

子任务4:测试点$1\sim 20$

显然是一道区间DP问题。

首先明确,合法的括号序列应该是(),(S),(A),(SA),(AS),AA,ASA。

定义$d(l,r)$表示区间$[l,r]$的子串是合法括号序列的方案数。通过观察可以发现,$d(l,r)$可以由非法括号序列转移得到,因此需要定义额外的状态。

  定义$s(l,r)$表示区间$[l,r]$能否构成合法的S型。

  定义$sa(l,r)$表示区间$[l,r]$的子串是SA型的方案数。

  定义$as(l,r)$表示区间$[l,r]$的子串是AS型的方案数。

那么对于(),(S),(A),(SA),(AS)型,显然有:

  $d(l,r)=1$, ()型

  $d(l,r)+=s(l+1,r-1)$, (S)型

  $d(l,r)+=d(l+1,r-1)$, (A)型

  $d(l,r)+=sa(l+1,r-1)$, (SA)型

  $d(l,r)+=as(l+1,r-1)$, (AS)型

对于AA、ASA型,需要枚举中间的分隔点$j(l\leq j\leq r)$:

  $d(l,r)+=d(l,j)*d(j+1,r)$, (AA)型

  $d(l,r)+=d(l,j)*sa(j+1,r)$, (ASA)型

同时需要维护AS,SA型,同样需要枚举中间的分隔点$j(l\leq j\leq r)$:

  $as(l,r)+=d(l,j)*s(j+1,r)$, (AS)型

  $sa(l,r)+=s(l,j)*d(j+1,r)$, (SA)型

但是,对于部分括号序列,会出现重复计数,例如()()()会被识别成A|AA或者AA|A,()*()*()会被识别成AS|ASA或ASA|S|A。

为了避免重复计数,要求在合并时只能合并一次,因此定义状态$a(l,r)$表示单独的A类型,也即(),(S),(A),(SA),(AS)型,这些类型的特点是不能拆分。

因此,状态转移方程需要做出如下修改:

  $a(l,r)=d(l,r)$,(),(S),(A),(SA),(AS)型

对于AA,ASA型,这是出现重复计数的类型:

  $d(l,r)+=a(l,j)*d(j+1,r)$, (AA)型

  $d(l,r)+=a(l,j)*sa(j+1,r)$, (ASA)型

综上,算法的流程如下:

  1、预处理得出所有的$s(l,r)$。

  2、从小到大依次枚举每个区间$[l,r]$,求出相应的$d(l,r),a(l,r),sa(l,r),sa(l,r)$。

  3、最终的答案即为$d(1,n)$。

注意:在运算过程中需要不断对$10^9+7$取余。

注意:通过观察可以得出,首字符必是(,尾字符必是),可以进行特殊处理。

时间复杂度O($N^3$),预计得分100分。


题目3620  [CSP 2021S]括号序列      12      评论
2021-11-26 14:48:54    
Gravatar
yrtiop
积分:2053
提交:304 / 803

前置芝士:CDQ分治(当然不会也无所谓,我会尽量讲明白的ヾ(◍°∇°◍)ノ゙)



看别人代码都是花里胡哨的线段树,树状数组,还有丧心病狂的平衡树。

好像没几个用 CDQ分治 写的

这道题本质上就是 CDQ分治(或者说二维偏序) 的模板题

将题目最开始的输入 $a_i$ 看做是对第 $i$ 个位置上增加了 $a_i$

查询操作根据前缀和,将询问拆成 $s-1,t$ 两个查询操作

第一个记录为-,第二个为+

题目中有两个维度:时间T位置x

对于两个操作:修改操作 $i$,查询操作 $j$,当且仅当 $T_i \le T_j$ 并且 $x_i \le x_j$ 时,$i$ 对 $j$有影响

(之前这个地方有疑惑,现在大概明白了,这样是正确的,感谢赵老师解答)

时间T 已经默认排好序了,就剩 位置x 需要处理一下了,并且要求处理的过程中不能破坏 时间T 的顺序性

CDQ分治 闪亮登场

它的操作主要由本篇代码中的 solve 函数完成

solve(l,r) 分为三步:

solve(l,mid)  solve(mid+1,r) 以及处理 $[l,mid]$ 对 $[mid+1,r]$ 的影响

要注意的是,这里的处理必须比较容易进行(比如加法,减法,取min,取max等)

这个算法的正确性在蓝书上和网上都有证明,此处略。

对我而言,CDQ分治 最难的其实是理解

以这个题目为例,我们要计算的是满足 $T_i \le T_j$ 并且 $x_i \le x_j$ 的二元组 (i,j) 产生的影响。

正如上面所言,时间T 不能被打乱,还要计算 $x_i \le x_j$

CDQ分治 处理这个问题的方法,大多可以在证明中理解。

$\forall k \in [mid+1,r]$,$[mid+1,k-1]$ 这一段产生的影响已经在递归中计算过了。

主要看的就是 $[l,mid]$ 这一段。

可以发现,不管 $[l,mid]$ 这一段怎样交换,改变,这里面的元素在现在的 solve(l,r) 中始终处于 $[l,mid]$ 这一段。

而这意味着什么呢?

也许 $[l,mid]$ 这一段的内部 时间T 是混乱的,但是并不影响这一段中任何一个操作对 $[mid+1,r]$ 的影响!!!

那么,我们可以在 solve 函数结束,各种乱七八糟的影响计算完毕后,把 $[l,r]$ 中的操作序列调整成我们想要的样子。

比如在这个题目里,solve 函数中,我们按照归并排序的方式,将 $[l,mid]$ 和 $[mid+1,r]$ 以 $x$ 为关键字排序

(这里有点像归并排序求逆序对,结合代码理解,或者先去看看这道题

然后就不难理解了叭(*^▽^*)

//CDQ分治第一弹[
//STO CDQ 
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cctype>
using namespace std;
const int maxn = 5e5 + 5;
struct query {
	int op,id,x,y;//id:若当前操作为询问,则为询问的编号
	//op:1为修改,2为负查询,3为正查询
	//x,y:操作的点和要增加的量 
	query() {
		op = id = x = y = 0;
	}
	query(int op,int id,int x,int y):op(op),id(id),x(x),y(y){}
}Q[maxn],b[maxn];//b数组:辅助进行归并排序 
int n,m,cnt,tot,ans[maxn];
int read() {
	int s = 0,f = 1;
	char c = getchar();
	for(;!isdigit(c);c = getchar()) {
		if(c == '-')f = -1;
	}
	for(;isdigit(c);c = getchar())s = (s << 1) + (s << 3) + (c ^ '0');
	return s * f;
}
void write(int x) {
	if(x > 9)write(x / 10);
	putchar(x % 10 + '0');
	return ;
}
void print(int x) {
	write(x);
	putchar('\n');
	return ;
}//快读快写 
//个人对于CDQ分治的理解:
//主要是解决偏序问题,当然这些偏序不都是裸的类似逆序对这样的
//对于答案的修改可能更加繁琐一点
//但是核心思想都是用CDQ分治解决偏序
//1:solve(l,mid),2:solve(mid+1,r)和3:(l,mid)对(mid+1,r)的影响这三块
//需要根据题目的要求自行决定顺序
//不恰当的栗子:DP
//DP时后半部分的计算直接由前半部分的影响决定
//这时就要1,3,2这样处理
//而对于本题,处理需要两个区间都有序,所以要先递归 ,即1,2,3 
void solve(int l,int r) {
	if(l >= r)return ;
	int mid = l + r >> 1;
	solve(l , mid);
	solve(mid + 1 , r);
	//此处先递归是因为CDQ分治解决偏序类似归并排序
	//需要两个区间都是有序的才能进行进一步的处理
	int i = l,j = mid + 1,sum = 0;//Mergesort part
	//sum用来统计[l,mid]的修改对[mid + 1,r]的影响 
	for(int k = l;k <= r;++ k) {
		if(j > r||(i <= mid&&Q[i].x <= Q[j].x)) {
			//此时当前的i对于[j,r]区间都有影响
			//记录在sum里,当查找到一个i,使得i无法对于j产生影响
			//那么再统计前面所有的i对当前j的影响
			//写的不太明白,可以结合下面else部分中的代码理解 
			if(Q[i].op == 1) {
				sum += Q[i].y;
			}
			b[k] = Q[i ++];
		}
		else {
			if(Q[j].op == 2) {
				ans[Q[j].id] -= sum;
			}
			else ans[Q[j].id] += sum;
			//若当前询问是-操作,那么ans[Q[j].id]减去sum值(也就是前半部分的影响)
			//若为+操作,则刚好相反 
			b[k] = Q[j ++];
		}
	} 
	for(int k = l;k <= r;++ k) {
		Q[k] = b[k];//归并排序的合并部分
		//为了保证当前递归的上一层可以快速进行,需要让Q[l~r]有序
		//结合函数开头理解 
	}
	return ;
}
int main() {
	freopen("shulie.in","r",stdin);
	freopen("shulie.out","w",stdout);
	n = read();
	for(int i = 1;i <= n;++ i) {
		int x = read();
		Q[++ cnt] = query(1 , 0 , i , x);
	}
	m = read();
	for(int i = 1;i <= m;++ i) {
		char c[10];
		scanf("%s",c + 1);
		int x = read(),y = read();
		if(c[1] == 'A') {
			Q[++ cnt] = query(1 , 0 , x , y);
		}
		else {
			Q[++ cnt] = query(2 , ++ tot , x - 1 , 0);
			Q[++ cnt] = query(3 , tot , y , 0);
		}
	}
	solve(1 , cnt);
	for(int i = 1;i <= tot;++ i)print(ans[i]);
	fclose(stdin);
	fclose(stdout);
	return 0;
}



题目264  数列操作A AAAAAAAAAAAAAAA      20      2 条 评论
2021-11-25 21:59:06    
Gravatar
冷月星云
积分:306
提交:104 / 368

在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子,青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳过的距离都是正整 数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点: 0 , 1 ,……, L (其中 L 是桥的长度)。坐标为 0 的点表示桥的起点,坐标为 L 的点表示桥的终点。青蛙从桥的起点开始,不停的向终点方向跳跃。一次跳跃的距离是 S 到 T 之间的任意正整数(包括 S,T )。当青蛙跳到或跳过坐标为 L 的点时,就算青蛙已经跳出了独木桥。题目给出独木桥的长度 L ,青蛙跳跃的距离范围 S,T ,桥上石子的位置。你的任务是确定青蛙要想过河,最少需要踩到的石子数。输入文件的第一行有一个正整数 L ( 1 <= L <= 10^9 ),表示独木桥的长度。第二行有三个正整数 S , T , M ,分别表示青蛙一次跳跃的最小距离,最大距离,及桥上石子的个数,其中 1 <= S <= T <= 10 , 1 <= M <= 100 。第三行有 M 个不同的正整数分别表示这 M 个石子在数轴上的位置(数据保证桥的起点和终点处没有石子)。所有相邻的整数之间用一个空格隔开。输出文件只包括一个整数,表示青蛙过河最少需要踩到的石子数。

【输入样例】                【输出样例】                                    对于30%的数据,L <= 10000;

10                                  2                                                      对于全部的数据,L <= 10^9。

2 3 5

2 3 5 6 7

30分解(基础DP):

      通过对题目的观察我们可以很快发现可以使用动态规划来解决这道题目,我们把每一个桥上的点踩过石子的次数视作前s至t个点踩过石子的次数,若这一个点上有石子则将这个值+1,选择这个点最少的石子次数,最终根据题意输出数轴上第l至l+t个点上最少的石子次数。

ans[i]=min(ans[i],ans[j])      i-t<=j<=i-s              第i个点没有石子           边界:ans[0]=0

ans[i]=min(ans[i],ans[j]+1)  i-t<=j<=i-s              第i个点有石子


50分解(特殊数据):

         在刚刚不优化DP的情况下我们可以发现一种特殊情况,即s=t,在这种情况下,我们只能选且必选数轴上为s倍数的点,因此只需要计算所有为s倍数点上石子数的和就能够直接得到答案。(注:在下一种做法中该特殊数据需要特判)


正解:状压DP

      根据前面的基本动态规划,我们可以直接看出O(L)的令人绝望的时间复杂度和a[L]的巨大数组,于是我们需要对其进行比较大的优化。

      回想一下刚开始的样例,我们可以发现,在第2个点之后每一个点都必定可以到达,所以我们可以尝试将中间部分大量的没有石子的空白区域跳过节省时间和空间消耗。

   首先我们将s和t的关系分为三种情况:

      ①:t-s=1

             在这种情况下,我们可以比较快速的得到在第t*s-t-s个是最后一个不可拼出的数,即第t*s-t-s+1及以后的点均必可跳到,我们可以选择将这一段到下一个石子处压缩为一步来减少时间消耗。

  ②:t-s>1

          这种情况是一定会优于情况①的,因为我们完全可以仅取第s和s+1个跳跃点,所以这种情况可以省略。

  ③:s=t

       当s=t时很明显是不可能取到s+1的,但由于这种情况易于解决所以只需要加个特判即可。

数学证明:

px+(p+1)y=gcd(p,p+1)是有整数解的,即可得:px+(p+1)y=s是一定有整数解的。

设px+(p+1)y=s的解为:x=x0+(p+1)t,y=y0−pt。令0<=x<=p(通过增减t个p+1来实现),s>p∗(p+1)−1,

则有:y=s−pxp+1>=s−p2p+1>p∗(p+1)−1−pxp+1>=0

即表示,当s>=p∗(p+1)时,px+(p+1)y=s有两个非负整数解,每次走p步或者p+1步,p∗(p+1)之后的地方均能够到达。

如果两个石子之间的距离大于p∗(p+1),那么就可以直接将他们之间的距离更改为p∗(p+1)。

综上,得到压缩路径的方法:若两个石子之间的距离>t∗(t−1),则将他们的距离更改为t∗(t−1)。

因为t<=10,因此我们可以直接将大于10*9的距离直接化为90.








题目111  [NOIP 2005]过河 AAAAAAAAAA      3      评论
2021-11-25 18:42:48    
Gravatar
冷月星云
积分:306
提交:104 / 368

篝火晚会

佳佳刚进高中,在军训的时候,由于佳佳吃苦耐劳,很快得到了教官的赏识,成为了“小教官”。在军训结束的那天晚上,佳佳被命令组织同学们进行篝火晚会。一共有 n 个同学,编号从 1 到 n 。一开始,同学们按照 1 , 2 ,……, n 的顺序坐成一圈,而实际上每个人都有两个最希望相邻的同学。如何下命令调整同学的次序,形成新的一个圈,使之符合同学们的意愿,成为摆在佳佳面前的一大难题。    佳佳可向同学们下达命令,每一个命令的形式如下:(b 1 , b 2 ,... b m -1 , b m )     b1,b2....b m可以不是连续的,比如说可以这样命令(1,8,10)    这 里 m 的值是由佳佳决定的,每次命令 m 的值都可以不同。这个命令的作用是移动编号是 b 1 , b 2 ,…… b m –1 , b m 的这 m 个同学的位置。要求 b 1 换到 b 2 的位置上, b 2 换到 b 3 的位置上,……,要求 b m 换到 b 1 的位置上。    执行每个命令都需要一些代价。我们假定如果一个命令要移动 m 个人的位置,那么这个命令的代价就是 m 。我们需要佳佳用最少的总代价实现同学们的意愿,你能帮助佳佳吗?


输入文件的第一行是一个整数 n ( 3 <= n <= 50000 ),表示一共有 n 个同学。其后 n 行每行包括两个不同的正整数,以一个空格隔开,分别表示编号是 1 的同学最希望相邻的两个同学的编号,编号是 2 的同学最希望相邻的两个同学的编号,……,编号是 n 的同学最希望相邻的两个同学的编号。


输出文件 包括一行,这一行只包含一个整数,为最小的总代价。如果无论怎么调整都不能符合每个同学的愿望,则输出 -1 。

一、可行解相关

1.可行解?:

      我们只需要把一号同学安在第一个位置,然后将他需要的同学安在右边,接着对新安放的同学执行相同操作,直到将所有同学执行完毕即可得到一条安放好所有同学的链。

2.其他解?:

      有两种情况可以构成其他解,一是将n号同学放在第一个位置,很明显这种解与第一种解等价;二是将整个链倒置,我们需要在稍后对这种情况进行讨论。

3.无解?:

      当某一个同学左拥右抱都不能满足其他同学时(三个或更多同学想要和一个同学坐在一起)

      当某一群同学构成一个环却孤立了其他同学时(出现两个或更多环)

二、最优解相关

根据前面对于可行解和原题目的讨论我们可以总结得到下面几条规律:

最初的座位顺序为“原序列”,即1、2、3、。。。、n,

目标的座位顺序为“目标序列”,如4、3、2、。。。、1,

求解的是从原序列变换到目标序列的最小代价。

那么如何得到最优解呢?让我们在小数据中寻找规律

1、2、3,变换到1、3、2                                                                    

1、2、3,变换到3、1、2                                                        

1、2、3、4,变换到2、1、4、3                    

1、2、3、4、5,变换到2、1、3、5、4

1、2、3、4、5,变换到1、3、4、5、2,


(2,3)


(2,3,1)


(1,2),(3,4)


(1,2),(4,5)

(2,5,4,3)


经过推理,我们可以得到以下几条规律(不太好解释,同学们自己理解一下):

两个序列中相同的部分无需调动,调动只会带来额外的代价;

差异部分则可分成若干个组,以组与组之间没有共同数字为划分依据;

最优方案中每条指令只会操作一个组;

一个差异组需要该组的长度的指令才能完成变换。


最后 我们仅需用链表存储后寻找与原题不同的“组”及其花费,然后将链表左右调换再次计算取最小值输出即可


注:COGS题解功能使用还不太熟练,同学们凑合理解一下吧~


题目112  [NOIP 2005]篝火晚会 AAAAAAAAAA      3      评论
2021-11-19 21:01:00    
Gravatar
冷月星云
积分:306
提交:104 / 368

《浅谈贪心算法在中学生信息学奥林匹克竞赛中的几种运用思路和方式》

关天泽


贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择,因此,贪心算法在一般问题的应用中仅能得到次优解,适用范围往往有一定限制。

在中学生信息学奥林匹克竞赛中(下文简称NOI),贪心算法往往难以完全适用,以至于出现部分考生对贪心算法的不重视。事实上,贪心算法的应用非常广泛,有时尽管无法使用贪心算法或贪心算法较难证明,但贪心算法对于NOI等测试和训练中仍有指导性意义。对此,我在此对贪心算法的可能性用法做出几点探讨。

一:对于难以直接证明的贪心进行一步步逼近例题:[NOIP2010冲刺十二] 奶牛晒衣服【题目描述】  

    在熊大妈英明的带领下,时针和它的同伴生下了许多牛宝宝。熊大妈决定给每个宝宝都穿上可爱的婴儿装。于是,为牛宝宝洗晒衣服就成了很不爽的事情。

圣人王担负起了这个重任。洗完衣服后,你就要弄干衣服。衣服在自然条件下用1的时间可以晒干A点湿度。抠门的熊大妈买了1台烘衣机。使用烘衣机可以让你用1的时间使1件衣服除开自然晒干的A点湿度外,还可烘干B点湿度,但在1的时间内只能对1件衣服使用。

   N件的衣服因为种种原因而不一样湿,现在告诉你每件衣服的湿度,要你求出弄干所有衣服的最少时间(湿度为0为干)。

【输入格式】  第一行N,A,B;接下来N行,每行一个数,表示衣服的湿度(1≤湿度,A,B≤500000,1≤N≤500000)。 【输出格式】  一行,最少时间。 【样例输入】  3 2 1

1

2

3 【样例输出】 1

这是一道经典的贪心优化题(当然这道题有二分的做法)。经过简单的计算可以得到裸贪的话时间复杂度远超限定的时间范围。

加上我们对于贪心的效率了解,可以得知贪心法的效率往往是最高的,所以我们可以选择从贪心的优化着手做题。我们发现每一次贪心都要对最湿的衣服进行烘干,并把烘干后的衣服重新加入排序中。我们可以联想到优先队列与该操作完全重合。由此,只需要将贪心与优先队列相结合就可以AC这道题。因篇幅限制,代码不在此展示(下文同)

二、对于DP起到思路指导作用例题:[NOIP 2005] 过河【问题描述】 在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子,青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳过的距离都是正整 数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点: 0 , 1 ,……, L (其中 L 是桥的长度)。坐标为 0 的点表示桥的起点,坐标为 L 的点表示桥的终点。青蛙从桥的起点开始,不停的向终点方向跳跃。一次跳跃的距离是 S 到 T 之间的任意正整数(包括 S,T )。当青蛙跳到或跳过坐标为 L 的点时,就算青蛙已经跳出了独木桥。


题目给出独木桥的长度 L ,青蛙跳跃的距离范围 S,T ,桥上石子的位置。你的任务是确定青蛙要想过河,最少需要踩到的石子数。 【输入文件】 输入文件的第一行有一个正整数 L ( 1 <= L <= 10^9 ),表示独木桥的长度。第二行有三个正整数 S , T , M ,分别表示青蛙一次跳跃的最小距离,最大距离,及桥上石子的个数,其中 1 <= S <= T <= 10 , 1 <= M <= 100 。第三行有 M 个不同的正整数分别表示这 M 个石子在数轴上的位置(数据保证桥的起点和终点处没有石子)。所有相邻的整数之间用一个空格隔开。 【输出文件】 输出文件只包括一个整数,表示青蛙过河最少需要踩到的石子数。 【输入样例】 10

2 3 5

2 3 5 6 7【输出样例】 2【数据规模】 对于30%的数据,L <= 10000;

对于全部的数据,L <= 10^9。


初见题目时,先考虑能否使用贪心算法,即直接选取最远距离跳。由于有最短跳跃距离限制,可以轻易证明,直接选取最远无石子距离跳跃会造成部分石子多解,对此我们可以由贪心思路推断出动态规划的状态转移方程。


通过贪心算法的发现,我们把每一个桥上的点踩过石子的次数视作前s至t个点踩过石子的次数,若这一个点上有石子则将这个值+1,选择这个点最少的石子次数,最终根据题意输出数轴上第l至l+t个点上最少的石子次数。得到动态规划的状态转移方程。


最后对其进行状态压缩即可AC,由于与本文思想关系不大,在此不再赘述。


题目1210  [NOIP 2010冲刺十二]奶牛晒衣服 AAAAAAAAAA      6      评论
2021-11-19 11:09:31    
Gravatar
冷月星云
积分:306
提交:104 / 368
 这道题主要有两种常见做法:二分法和贪心法

这里我们主要讲一下二分法

(Q:你不是说你最喜欢贪心吗

 A:我会说我懒得打优先队列吗?)


【题目描述】

    在熊大妈英明的带领下,时针和它的同伴生下了许多牛宝宝。熊大妈决定给每个宝宝都穿上可爱的婴儿装。于是,为牛宝宝洗晒衣服就成了很不爽的事情。

圣人王担负起了这个重任。洗完衣服后,你就要弄干衣服。衣服在自然条件下用1的时间可以晒干A点湿度。抠门的熊大妈买了1台烘衣机。使用烘衣机可以让你用1的时间使1件衣服除开自然晒干的A点湿度外,还可烘干B点湿度,但在1的时间内只能对1件衣服使用。     N件的衣服因为种种原因而不一样湿,现在告诉你每件衣服的湿度,要你求出弄干所有衣服的最少时间(湿度为0为干)。


聚焦题目,显而易见贪心的方法就是每天取最大值,但如果裸贪的话......500000*500000,请?

对于我个人来说,数据结构能不用是绝对不会用的,考虑了插入排序和一些优化的数学方式之后决定走向二分法。


我们发现,每天晒干湿度的值是单调递增的,换句话说,二分的前提条件已经到手了。那么,我们只要对用于晒干衣服的天数进行二分,讨论当前情况下能否晒干并对其进行二分查找操作即可快速得到需要晒干的最少天数。


我们可以设置一个函数,对每一件衣服在当前天数情况下能否晒干进行判断,再对没有晒干衣服使用烘衣机的总天数进行计算,最后将使用烘衣机的天数与预设的天数比较,前者大则当前天数下无法晒干,需要增加预设的天数;前者小则可以晒干,减少预设的天数进一步压榨效率。


int check(int ans){
	int days = 0;
	for(int i = 1;i<=n;i++){
		if(wet[i]>ans*A){
			days += ceil( (wet[i] - ans * A) *1.0 / B); //需要小数否则会舍弃小数位 
		}
	}
	if(days>ans){
		return 0;
	}
	else{
		return 1; 
	} 
}



代码详见右下角,祝愿大家都能天天·一A,学业进步!

注:代码中二分部分的注释时间复杂度多打了一个N,总体不影响,我懒得再传一次代码了(逃



题目1210  [NOIP 2010冲刺十二]奶牛晒衣服 AAAAAAAAAA      5      评论
2021-11-18 17:12:44