CodeForces CF #500 Div.2 (D,E已更新)

这次的题目真玄学啊。。。半个小时A掉了ABC题,然后就坐着喝了两个小时的茶

A. Piles With Stones

题意,给你n个数字,ai表示在i-th的位置上有ai块石头。已知现在有两种操作,分别是把某个石头移到另一个位置,或者拿走一块。

然后。。。再给你n个数字,问你是否在足量次操作后可以变成后面的状态。

我写代码的时间还没读题的时间长。。。。就是上面求和,下面求和,然后比大小。。。。毕竟石头不可能变多,所以上面一定大于等于下面。满足这个条件就可以了。

B. And

这题也是水题。给你n个数字和一个m。问是否用m去和这些数字按位&之后能出现两个相同的数字(也可能不需要任何 操作),问最少需要几次操作。不存在则输出-1。

显然,有以下四种情况:
1.开始就有相同的,那么答案是0;
2.在不满足的1情况下,如果存在某个数x,和另一个数y,满足 x & m = = y x\&m==y x&m==y,那么答案是1
3.在不满足的1,2情况下,如果存在某个数x,和另一个数y,满足 x & m = = y & m x\&m==y\&m x&m==y&m,那么答案是2
4.否则答案是-1

那么我们保存两个hash表,对一个数x分别保存x(hash1)和x&m(hash2)是否已经存在过。

然后当我们读取一个新的y的时候,查看hash1[y]和hash2[y]是否存在。再对y&m进行一样的操作。然后按照上面的分类保存最小的结果。最后更新hash表。

最后应该是O(n)的复杂度。

#define INI(x) memset(x,0,sizeof(x))
#define MIN(x,y) (((x)<(y))?(x):(y))
int ans=3;
int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='0')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}

bool a[100005],b[100005]; 
int n,m;

int main()
{
	INI(a);
	INI(b);
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		int temp=read();
		int ttemp=temp&m;
		if(a[temp])ans=0;
		if(b[temp])ans=MIN(ans,1);
		else
		{
			if(a[ttemp])ans=MIN(ans,1);
			if(b[ttemp])ans=MIN(ans,2);
		}
		a[temp]=1;
		b[ttemp]=1;
	}
	if(ans==3)cout<< "-1"<< endl;
	else cout<< ans<< endl;
}

感激涕零,终于会打代码块了

C. Photo of The Sky

这道题我wa了一次,然后改了一下算法就过了,算道思维题吧。

题意是说,给你2*n个数,组成n个坐标,问可以把这n个点全部筐进去的最小的横平竖直的矩形的面积。

简单来说,就是把2*n个数等分成两组,使两组的最大值与最小值的差的乘积最小。

然后我马上就能想到,那显然最大的数和最小的数不能在一组,那么排序后从中间切开不就好了吗!!

然后我自信满满的手写了一个样例:

1 2 3 4 5 6

从中间分开

1 2 3 | 4 5 6

假设1和6不动,这个时候我们把左边的任何一个数和右边的某个数换位置,那么显然答案会变大,这个不难证明,这里就略过了。

然后我就提交了,然后我就WA了。

然后我发现了另一组测试数据是这样的:

1 2 2 3

如果把2 2分到一组的话,那么面积是0。

但是用我的算法就是1。

然后我才意识到我的前提有问题:最大值和最小值也可能在同一组。

那么我们考虑,如果最大值和最小值分列两组,那么就用上面的算法,如果最大值和最小值在同一组,那么这一组的差值已经确定了,我们希望另一组的差值最小。这样的话,另一组的n个数必定是连在一起的,否则范围必然偏大。那么我们穷举另一组里每组连续的n个数的左右端点的差值,记录最小值。

最后把两种情况的结果比较,得到最终结果。

#define INI(x) memset(x,0,sizeof(x))
#define MIN(x,y) (((x)<(y))?(x):(y))
int n;
long long a[200005];
long long read()
{
	long long x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='0')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}


int main()
{
	n=read();
	for(int i=0;i<2*n;i++)a[i]=read();
	sort(a,a+n*2);
	long long ans=(a[n-1]-a[0])*(a[2*n-1]-a[n]);
	for(int i=1;i<n;i++)ans=MIN(ans,(a[2*n-1]-a[0])*(a[i+n-1]-a[i]));
	cout<<ans<<endl;
}

最后结果是排序的NlogN以及N的穷举,所以是O(NlogN)的复杂度。

DEF:不会

其实还有有一点发现的。。。就是D题应该是找一个构造法来解,然后过程中发现关键点已经给出,那么就更新结果,最后得到最优答案。。。但是我找不到构造法。

E题是DP题,我敢拍着胸脯(轮椅)讲,绝对是DP!

补充2018.8.2:

D.Chemical table

我无论如何也想不到,这居然是一道dfs题。。。

给你一个n*m的矩形,一开始有q个格子上被标记。对于任意两行两列,如果交汇的四个格子中有三个被标记,那么第4个会被自动标记。问你至少需要手动标记几个格子,使得整个矩形内的格子都被标记。

这道题做的时候我也没有好的思路,那么我先讲解法,然后补充解题思路吧(俗称马后炮)。

我们构造一张图,总共有n+m个点,分别对应矩形的两条边。

先证一个引理,我们假设一个矩形四个顶点分别为:

(a,c)(a,d)
(b,c)(b,d)

(其中a,b是1n的边上选的两点,c,d是1m的边上选的两点,这里先不考虑他们的数值,就算ab或ac我们这里也视为不同的点,后面会解释)

假设我们已经有了(a,c)(a,d)(b,c)三个点,那么我们在构造的图上把它们相连,会得到:

d–a--c–b

这样相连的四个点,根据定义,此时d–b也可以被连上了。

再反过来验证,随意找一个a–b--c–d,都可以构造成矩形的形式,然后将a–d连上。

对于更长的模型a–b--c–d--e–f我们经过裁剪可以保证a分别与b,d,f相连。对余下的点也同理,对更长的点也同理。也就是说,对一长串的点而言,我们对他们编号为1–2--3–4--5–6--…–x,我们的偶数项可以通过构造与其他所有奇数项相连,vice cerse.

接下来我们再看题目,我们把这n+m个点分为1n和n+1n+m两组,希望每一点都与非本组的每个点相连。显然我们的初始条件中不会出现同组之间互相连接的情况,必定是一串两组中的点交替出现的图,所以是满足题意的。那我们运用上一题的结论,保证只要点在同一个联通块中,那么可以在不添加新点的情况下满足联通块中每一点都与联通块中非本组的每个点相连。

那么我们只需要统计有多少联通块,然后数量-1就是最终答案。所以一通dfs即可。因为数据范围比较大,但是边小于等于200000条,邻接矩阵必死无疑,所以这里我们用一个前向星来储存。(其实也可以用vector,但是慢啊,而且丑陋。)

解题思路:我把这种题目定义为“有限构造”类,就是在一个有限的域中,存在某种递推关系,可以尝试用构造成图的方法来解答。有限是因为不能去解决像比方说求斐波那契数列那样的问题(但是如果有一个取模,问至少需要几组初始数据才能填满它的完系,这样有限规模的题目是可以构造图来解决的)。存在的递推关系则是用于构造图中的边。

值得注意的是,这只是一个初步的想法,既不必要,也不一定总是可行。但是我认为这种思路对于表现递推关系很有帮助,就这道题而言,用很简洁的方式刻画了矩形补角的关系。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define INI(x) memset(x,0,sizeof(x))

void read(int &x)
{
	x=0;int f=-1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return;
 }//快读
 
 struct graph{int to,next;}edge[400005];
 int tail[400005];
 int edge_cnt=0;
 void add_edge(int f,int t)
 {
 	edge[++edge_cnt].to=t;
 	edge[edge_cnt].next=tail[f];
 	tail[f]=edge_cnt;	
	return;
 }//前向星
 
 int n,m,q,r,c;
 bool vis[400005]; 
 int ans=-1;//答案就是联通块数目-1 
 
 void INITIATE()
 {
 	cin>>n>>m>>q;
	INI(vis);
	INI(tail);
 	for(int i=0;i<q;i++)
 	{
 		read(r);read(c);
 		c=c+n;
 		add_edge(r,c);
 		add_edge(c,r);
	}
	return;
 }//初始化模块 
 
 void dfs(int x)
 {
 	vis[x]=1;
 	for(int i=tail[x];i;i=edge[i].next)
 	{
 		int to=edge[i].to;
 		if(!vis[to])dfs(to);
	}
	return;
 }
 
 void SOLVE()
 {
 	for(int i=1;i<=m+n;i++)
 	{
 		if(!vis[i])
 		{
 			ans++;
 			dfs(i);
		 }
	 }
	 return;
 }
 
 int main()
 {
 	INITIATE();
 	SOLVE();
 	cout<<ans<<endl;
 }

E.Hills

这题果然是DP!!!我转移方程写对了四分之三。。。一个特殊情况没有考虑到位,稍加思索还是A掉了,其实也算是水题吧(并不)。

题面如下:输入一串数字代表一串山坡的高度,选一些山头造房子,可以建房子的山坡的定义是这个山坡的高度严格比旁边两个山坡大。我可以用挖掘机挖山坡使其高度-1(可以减到负数)。现在要分别找到想建1-n/2个房子,问每种情况下要挖掉的总高度的最小值。

这样的题目不dp一下也说不过去啊,具有非常明显的最优子结构。接下来就是表达式的选择。这里主要有两个要素:用i,j表示到i位置为止总共造了j个房子。但是因为相邻格子之间有些乱七八糟的东西,我们希望用多一个维度来表示i的位置上有没有造。

最终得到的是表达式如下:dp[i][j][c] (c=1或0)

其中i表示到第i位置为止,j表示已经造了j个,c1时表示j位置上有房子,c0时没有。

接下来需要解决递推式和初始条件,我们先看递推式:

1.对于c==0的情况,因为反正这个位置不造房子,那么上一个格子造不造都无所谓,并且对下一格也没有影响,所以就是上一格造或不造的状态中较小的一个:

dp[i][j][0] = MIN (dp[i-1][j][0],dp[i-1][j][1])

2.对于c==1的情况。上一个山头如果已经造了房子,那么这个山头必定不可能造房子(因为上一个山头造房子的时候已经比这个山头高了,造完了又不能推低,所以这个山头总是比上一个山头矮),所以从i-2的位置开始推。上一个山头如果还没造房子,那这里确实可以造,但是我希望知道上上的山头造了没有(如果造了,那么可能会影响上一个山头的高度),所以也应该从i-2开始推,我这里就是没考虑这一点,所以就出了偏差。

我们定义cost1[i]为i山头山头i-1需要被削去的高度,cost2[i]对应地表示山头i+1需要被削去的高度(假设上个山头没被砍过),可以预处理。另外定义一个tempcost1表示山头i-1被砍过的状态下需要再砍多少。以下给出对应的公式:

cost1[i]=MAX(h[i-1]-h[i]+1,0)
cost2[i]=MAX(h[i+1]-h[i]+1,0)
tempcost1=MAX(0,1-h[i]+MIN(h[i-1],h[i-2]-1))

其中h[i]为山头高度,h[0]=h[n+1]=0(因为最低的山为1)。

以下是转移方程:

dp[i][j][1]=MIN(dp[i-2][j-1][1]+cost2[i]+tempcost1,dp[i-2][j-1][0]+cost1[i]+cost2[i]);

考虑完递推式,就该考虑边界条件了,这边目前而言我还没发现比较好的定边界条件的办法,一般都是凭感觉。希望有心得的读者可以分享一下。我这边是把dp[i][i][1]也就是到i位置放下第一个房子的花费作为边界条件,手算一下应该没什么问题就过了。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MIN(x,y) (((x)<(y))?(x):(y))
#define MAX(x,y) (((x)>(y))?(x):(y))
#define ABS(x) (((x)>0)?(x):(-x))

const int maxn=5005;
int dp[maxn][maxn][2];
int a[maxn];
int cost1[maxn];
int cost2[maxn];
int n,k;
int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='0')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}

int main()
{
	cin>>n;
	k=(n+1)/2;
	
	for(int i=0;i<=n;i++)
	for(int j=0;j<=k;j++)
	dp[i][j][0]=dp[i][j][1]=1e9+7;
	for(int i=0;i<=n;i++)dp[i][0][0]=0;
	
	a[0]=a[n+1]=0;
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=1;i<=n;i++)
	{
		cost1[i]=MAX(a[i-1]-a[i]+1,0);
		cost2[i]=MAX(a[i+1]-a[i]+1,0);
	}
	for(int i=1;i<=n;i++)dp[i][1][1]=cost1[i]+cost2[i];
	
	for(int j=1;j<=k;j++)
	for(int i=1;i<=n;i++)
	{
		dp[i][j][0]=MIN(dp[i-1][j][1],dp[i-1][j][0]);
		int tempcost1=MAX(0,1-a[i]+MIN(a[i-1],a[i-2]-1));
		if(i!=1)dp[i][j][1]=MIN(dp[i-2][j-1][1]+cost2[i]+tempcost1,dp[i-2][j-1][0]+cost1[i]+cost2[i]);
	}
	
	for(int i=1;i<=k;i++)cout<<MIN(dp[n][i][0],dp[n][i][1])<<" ";
}
基于SSM框架的智能家政保洁预约系统,是一个旨在提高家政保洁服务预约效率和管理水平的平台。该系统通过集成现代信息技术,为家政公司、家政服务人员和消费者提供了一个便捷的在线预约和管理系统。 系统的主要功能包括: 1. **用户管理**:允许消费者注册、登录,并管理他们的个人资料和预约历史。 2. **家政人员管理**:家政服务人员可以注册并更新自己的个人信息、服务类别和服务时间。 3. **服务预约**:消费者可以浏览不同的家政服务选项,选择合适的服务人员,并在线预约服务。 4. **订单管理**:系统支持订单的创建、跟踪和管理,包括订单的确认、完成和评价。 5. **评价系统**:消费者可以在家政服务完成后对服务进行评价,帮助提高服务质量和透明度。 6. **后台管理**:管理员可以管理用户、家政人员信息、服务类别、预约订单以及处理用户反馈。 系统采用Java语言开发,使用MySQL数据库进行数据存储,通过B/S架构实现用户与服务的在线交互。系统设计考虑了不同用户角色的需求,包括管理员、家政服务人员和普通用户,每个角色都有相应的权限和功能。此外,系统还采用了软件组件化、精化体系结构、分离逻辑和数据等方法,以便于未来的系统升级和维护。 智能家政保洁预约系统通过提供一个集中的平台,不仅方便了消费者的预约和管理,也为家政服务人员提供了一个展示和推广自己服务的机会。同时,系统的后台管理功能为家政公司提供了强大的数据支持和决策辅助,有助于提高服务质量和管理效率。该系统的设计与实现,标志着家政保洁服务向现代化和网络化的转型,为管理决策和控制提供保障,是行业发展中的重要里程碑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值