集训日记:DAY2

本文记录了一次编程竞赛的经历,作者反思了在解决T1队列、T2种花、T3格式化和T4逆序对问题时的思路与错误。对于T1,正解是从整体序列考虑;T2的正确策略是状态压缩DP;T3的关键在于考虑格式化前后容量的变化;T4则应用了按位贪心。作者意识到基础算法的重要性,承诺加强代码能力和基础训练。
摘要由CSDN通过智能技术生成

集训日记:DAY2

有了昨天模拟赛的惨状,今天模拟赛的宗旨就是:

得分!得分!得分就行!!!

于是开始看T1队列
把玩家合并成队列……?
玩家序列成环……?那得把序列复制一下
看完题的初步想法就是如果没有人跳反,那就先合并最近的,然后以此类推,有人跳反同理。
然后看T2种花
好家伙,又是hzwer???
这道题我刚开始把他想成了和Supermarket那道题类似,按花的存活时间从大到小排序,然后如果当天有要死的就施肥,没有要死的就接着种,看到第几天种完,即答案。如果某天有大于一个要死的就无解。(当时觉得一定是正解……)
再看T3格式化
这道题我想的是按格式化前后空间变化从大到小排序,因为变化越大就能腾出更多空间给别的盘。然后再模拟一下格式化过程,算出答案。(当时没想到可能有多余空间没用……)
最后看T4逆序对
读完题当时就觉得不可做,想着暴力枚举x,求逆序对,掏个部分分,算了一下1e5的数据如果逆序对是nlogn的话大概可以枚举x从0到65(无论如何不能TLE,TLE就全完了)(此时过去30min)
整体看完一遍题之后谨记gg的教诲先把T4暴力写了
大概花了30min写完并调通。
然后按顺序开我有思路的题,从T1开始。
T1在之前的基础上又想了10min,猛然发现我对于跳反的处理全不对(随手造了个样例就hack掉了……)然后去看特殊限制,似乎有30分没跳反的情况可以按原思路做,但是还没太想好怎么实现……于是弃掉去看T2
T2我当时仍然坚信我是对的,于是按照思路开始敲码。
大概花了30min敲完并编译成功,第一遍测试样例没过然后改了几处细节过了样例,总共大概40min。正当我信心满满准备开下一题的时候,我觉得还是一个造一个样例测试一下。
结果出锅了……我随手造的样例就把我hack掉了……
然后又照着几个造的样例改了大概30min终于通过了所有样例。
这时松了口气开下一题(此时过去2h10min)
T3按照我原有的思路写的出奇的顺利,30min写完调完,自己造的样例也过了,这时时间相当充沛——我有1h20min去调T1。
T1本来我都弃掉了,这下多出来1h20min来调,感觉挺有信心。
于是开始写那30分的特殊限制。
思路是有了,实现却遇到了困难。
光预处理每种数字的长度区间就调了30min我代码能力是真的弱
处理出数字长度之后统计这块有不知道怎么实现了……
剩下40分钟就是我用两个结构体一通瞎搞……
最后也没过样例……
下午吃完饭回来一看分,就T4的暴力和T3得点分,其他都没分……
而且T4的暴力分还因为初始inf设小了挂了一些分……
这点还是挺惊讶,考完我还觉得考得不错呢……
然后贴一下正解和标程:

T1队列

将个人的环复制一份,成为长度的序列,设势力一共有名玩家,则在新序列的一个连续段中,只要包含名势力的玩家,这个连续段中所有非势力的玩家死亡即可得到原环上的一个队列,同时这个连续段的长度减去就可以作为势力的一个解(不一定最优)
比如环1 2 2 1 2,破环成链后为1 2 2 1 2 1 2 2 1 2,选取第4个到第6个位置的连续段,长度3再减去势力1人数2即可得到势力1的一个候选解1(这个例子中同时也是最优解)
当某个势力人数过多时,这个连续段只要包含下取整名玩家即可,剩余的该势力玩家作为野心家。因此,在新序列中对每个势力,寻找包含足够数量的势力玩家的,长度最小的区间即可,这个长度最小的区间的两端一定是势力的角色,因此只需要对每个势力枚举右端点移动左端点扫描即可。

好家伙正解居然这么简单……我怎么就没想到从整体一段序列来考虑……

#include<bits/stdc++.h>
using namespace std;
const int N=50050,M=50050;
int n,m,a[N+N]={};
int s[M]={},e[M]={},l[M]={},x[M]={},d[M]={};
int main()
{
	freopen("alignment.in","r",stdin);
	freopen("alignment.out","w",stdout);
	int T;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;++i)
			scanf("%d",a+i);
		for(int i=1;i<=n;++i)
			a[n+i]=a[i];
		//copy(a+1,a+n+1,a+n+1);
		for(int i=1;i<=m;++i)
			s[i]=e[i]=l[i]=x[i]=d[i]=0;
		// s[i] i第一次出现的位置
		// e[i] i最后一次出现的位置
		// x[i] i的次数
		for(int i=1;i<=n;++i)
		{
			if(s[a[i]]==0)
				s[a[i]]=i;
			++x[a[i]];
			e[a[i]]=i;
		}
		// d[i] i对应的相邻间距的最大值
		// l[i] 扫描过程中i上一次出现的位置 
		for(int i=1;i<=n;++i)
		{
			if(l[a[i]]!=0)
				d[a[i]]=max(d[a[i]],i-l[a[i]]-1);
			l[a[i]]=i;
		}
		int X=n/2;
		for(int i=1;i<=m;++i)
		{
			d[i]=max(d[i],n-e[i]+s[i]-1);
			if(x[i]<=X)
				printf("%d ",n-d[i]-x[i]);
			else
			{
				int ans=n;
				int p1=s[i],p2=p1,t=1;
				// p1 等价于 l
				// p2 等价于 r
				// t 为 l到r(可跨过n)中i的数量 
				while(t<X)
				{
					//t+=(a[++p2]==i);
					++p2;
					if(a[p2]==i)
						++t;
				}
				// 找l对应的最小的r 
				while(p2<=n+n)
				{
					ans=min(ans,p2-p1+1-X);
					//更新答案 
					++p1;
					while(a[p1]!=i)
						++p1;
					//移动l 
					++p2;
					while(p2<=n+n && a[p2]!=i)
						++p2;
					//移动r 
				}
				printf("%d ",ans);
			}
		}
		puts("");
	}
	return 0;
}

T2种花

首先,如果有两种花的m值均为1,那么显然是无法开花展的。
接着,对于其它情况,关键在于分析出,最优方案中,一定是种植一种花并且用肥料续到足够的天数后,再种植下一种花。
假设我们规定了从晚到早种花的顺序,并且知道使要求从晚到早的前k种花全部开放至少需要d天,那么接下来考虑第k+1种花——其要续到足够的天数,腾出时间使得hzwer有时间把前k种花种出来,那么很显然第k+1种花需要d/(m-1)上取整这么多天的时间来种植和施肥。
我们现在的任务就是要求一个最优的顺序,这个可以使用状态压缩DP来完成,记f[S]表示要种的花为集合S时,最少需要多少天的时间,每次枚举一种不在S中的花进行转移即可。

原来我想的和正解半毛钱关系没有……说好的贪心怎么还开始状压了?!rnm!!!


```cpp
#include<bits/stdc++.h>
using namespace std;
const int Inf=1<<29,N=18,S=1<<N;
int n,m[N]={},f[S]={};
int main()
{
	freopen("flower.in","r",stdin);
	freopen("flower.out","w",stdout);
	int T;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		for(int i=0;i<n;++i)
			scanf("%d",m+i);
		sort(m,m+n);
		if(n>1 && m[1]==1)
		{
			puts("Poor hzwer!");
			continue;
		}
		for(int i=0;i<(1<<n);++i)
			f[i]=Inf;
		if(m[0]==1)
		{
			f[0]=1;
			for(int i=0;i<(1<<(n-1));++i)
				for(int b=1;b<n;++b)
					if(!(i&(1<<(b-1))))
						f[i|(1<<(b-1))] = min(f[i|(1<<(b-1))],f[i]+(f[i]+m[b]-2)/(m[b]-1));
			printf("%d\n",f[(1<<(n-1))-1]);
		}
		else
		{
			for(int b=0;b<n;++b)
				f[1<<b]=1;
			for(int i=1;i<(1<<n);++i)
				for(int b=0;b<n;++b)
					if(!(i&(1<<b)))
						f[i|(1<<b)] = min(f[i|(1<<b)],f[i]+(f[i]+m[b]-2)/(m[b]-1));
			printf("%d\n",f[(1<<n)-1]);
		}
	}
}

T3格式化

若硬盘格式化后容量总是增大,那么我们将硬盘按格式化前的容量从小到大排序并依次进行格式化一定是最优的,而所需额外容量也很好计算——每当当前空闲容量小于待格式化的硬盘容量,那么就购买额外的容量来补充。
若硬盘格式化后容量总是减小,则我们可以按时间反向进行操作——从最终的状态开始,每次选取一个硬盘进行逆格式化,这时就转化为了前一种情况,因此我们如果正向进行操作时,选择先格式化格式化后容量较大的硬盘一定是最优的,所需额外的容量也可以进行类似的操作确定。
将二者结合起来,我们就可以得到最优方案:先按格式化前容量从小到大的顺序格式化格式化后容量增大的硬盘,再按格式化后容量从大到小的顺序格式化格式化后容量减小的硬盘即可,中途如果空闲空间不够那么就购买额外的硬盘空间补充。

差亿点就是正解啊!!!多余的空间用不上的反数据没找出来……

#include<bits/stdc++.h>
using namespace std;
const int N=1001000;
struct disk
{
	int a,b;
}d[N]={},d1[N]={},d2[N]={};
bool disk_cmp1(const disk &d1,const disk &d2)
{
	return d1.a<d2.a;
}
bool disk_cmp2(const disk &d1,const disk &d2)
{
	return d1.b<d2.b;
}
int n,t1=0,t2=0;
int main()
{
	freopen("reformat.in","r",stdin);
	freopen("reformat.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
		scanf("%d%d",&d[i].a,&d[i].b);
	for(int i=1;i<=n;++i)
		if(d[i].a<=d[i].b)
			d1[++t1]=d[i];
		else
			d2[++t2]=d[i];
	sort(d1+1,d1+t1+1,disk_cmp1);
	sort(d2+1,d2+t2+1,disk_cmp2);
	long long ans=0,now=0;
	for(int i=1;i<=t1;++i)
	{
		if(d1[i].a>now)
		{
			ans+=d1[i].a-now;
			now=d1[i].a;
		}
		now+=d1[i].b-d1[i].a;
	}
	for(int i=1;i<=t2;++i)
		now-=d2[i].a-d2[i].b;
	for(int i=1;i<=t2;++i)
	{
		if(d2[i].b>now)
		{
			ans+=d2[i].b-now;
			now=d2[i].b;
		}
		now+=d2[i].a-d2[i].b;
	}
	cout<<ans<<endl;
}

T4逆序对

经典的按位贪心模型——对于每一位来说,只有全部翻转和全部不翻转两种选择,对于序列里的两个数来说,当且仅当 的从高到低数的第0到i-1位都相同,并且第位不同时,两个数在第位上是0/1决定这两个数是否为逆序对。
因此从高到低按位划分可以计算出从高到低第位翻转和不翻转时,由该位所决定的逆序对的个数,由上述分析可知,每一位是否翻转的选择都是独立的,对每一位直接贪心即可。

这题正解我是确实没想出来,但是暴力分没拿满不应该!!!

#include<bits/stdc++.h>
using namespace std;
const int N=300030,D=30;
int n,a[N]={},x=0,tmp[N]={};
long long p[D+1]={},q[D+1]={},ans=0;
void work(int l,int r,int d)
{
	if(l>r || d<0)
		return;
	int t0=0,t1=0;
	for(int i=l;i<=r;++i)
	{
		if((a[i]&(1<<d)))
		{
			++t1;
			p[d]+=t0;
		}
		else
		{
			++t0;
			q[d]+=t1;
		}
	}
	int pos=l,mid;
	for(int i=l;i<=r;++i)
		if((a[i]&(1<<d)))
			tmp[pos++]=a[i];
	mid=pos;
	for(int i=l;i<=r;++i)
		if(!(a[i]&(1<<d)))
			tmp[pos++]=a[i];
	copy(tmp+l,tmp+pos,a+l);
	work(l,mid-1,d-1);
	work(mid,r,d-1);
}
int main()
{
	freopen("inverse.in","r",stdin);
	freopen("inverse.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
		scanf("%d",a+i);
	work(1,n,D);
	for(int d=D;d>=0;--d)
		if(p[d]<q[d])
		{
			x|=1<<d;
			ans+=p[d];
		}
		else
			ans+=q[d];
	cout<<ans<<' '<<x<<endl;
	fclose(stdin);
	fclose(stdout);
	return 0;
}

总结

这次模拟赛比上次能强点至少没爆零
想想曾经模拟连续五场爆零最后还进省队的 wzh大佬
时间分配和策略上基本没有大问题,以后可以延用。
至于挂分就是单纯的思路想错和细节挂分吧……
体现出这段时间忽视基础算法,光顾图论,数据结构等问题。
至于代码能力……老生常谈了,还是不行,以后一定要多加练习
这次的成绩不是很满意,感觉对不起自己的付出。
而且也有一些人得了不该得的分。
但我绝对不会那样做,没有意义,就算题做不对至少提升一下代码能力。
今天我觉得我代码能力和debug能力比昨天强多了虽然还是很弱
也不能找什么运气不好的理由,还是找问题,继续努力吧!
毕竟,虽然无数次怀疑过,但是我还是选择相信:

天道酬勤!

妈呀!今天的题还没弄完呢!!!溜了溜了!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值