8.18 模拟赛记录

8.18 模拟赛记录

复盘

今天考的是构造,一开始觉得构造无非就是找一组解,和一般的问题也没啥不同,可以当综合打了。
惯例开局遍历,T1打表难打,基本不可做;T2k=1好写,别的比较复杂;T3并查集但又不完全并查集,说不上是什么东西;T4好像就是某种策略模拟。总之就是暴力完全无从下手的感觉。
由此,今天的策略还是莽一点,从T4开始做。
T4的策略基本就是先往中间凑,再去凑2k.我经过一些简单的感性理解认为没有无解的情况,所以第一步先把大的转移给小的,第二步奇数之间互相转移,第三步非2k互相转移凑,最后合并多个2k.这些工作花了我120多行两个小时的时间(while(1)嵌套是真的恐怖),自己出的几个样例都解决了,可以放到一边。这时候大概是10:30.
这时候回去思考T2,想了很久也没想出靠谱的策略,直接拿10分走人。T3我坚持认为和二分图有关系,然后突发奇想可以用n次最小生成树来解决这个最多忽略一个敌对关系的要求,这时候就剩下20分钟了,一顿操作猛如虎,成功写完,但是在n次最小生成树上出了问题,所以只能写一次的,希望能得到分。
期望分数:0+10+0+60.

复盘分析

得分:0+0+5+0.
首先T2大概是我看01串看的太久,完全忘记了要开long long,非常尴尬丢了10分。T4绝大部分在测的时候RE了,虽然觉得自己出的样例不差,但我认了,实现写的确实太烂,CSP之前赶紧练吧。
构造总归第一次做,有些地方不太敢去做、去尝试,但是现在熟悉了题型,虽然考得不好,但是总结到一些经验,这算是找到一些自信。
总归十天的集训结束了,收获很大,强化了知识点的同时挖了一堆新坑,也让我从爆低的分数当中意识到自己的代码实现能力迫切的需要练(暴力天天爆零绝对是不能接受的),有些东西迫切的需要强化。总而言之,路漫漫其修远兮,吾将上下而求索。

题解

首先经过这次模拟,我充分意识到,构造问题是求解一个存在性命题。这也就是说,在如此之大的操作空间内,我们总是可以通过一些方式取得一些特殊情况,从而大幅度的降低复杂度,同时使得问题能被解决。今天的题解就重点讨论这件事情。

(u1s1,T1其实是最正常的一道题了。)
T1首先有一个思路:2x2中的任意一种状态都有解,且最多需要4步。题目要求nm步以内得结论,假设我们暴力的枚举nm / 4个2x2的矩形进行修改,对于边长为奇数的情况额外处理,其步数也不超过nm,所以可以打一个包括了16种情况的表,然后在主函数调用O(n2)求解,完全能过。然而这个表其实不好打(不要问我为什么知道),所以放弃偷鸡,考虑逐层的递推。
由于我们能对一个2x2的矩形操作,所以我们能做到在不影响前面i行的前提下把第i+1行的灯都关了(当然对后续是有影响的)。这样我们一路操作完(n-2,m-2)的矩形,剩下的就用2x2的情况讨论即可。
代码如下(实在搬砖,不解释了):

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 111;
int n,m,a[N][N];
char ch[N];
vector<int> v[6];
void record(int x0,int y0,int x1,int y1,int x2,int y2){
	v[0].push_back(x0),v[1].push_back(y0);
	a[x0][y0]=!a[x0][y0];
	v[2].push_back(x1),v[3].push_back(y1);
	a[x1][y1]=!a[x1][y1];
	v[4].push_back(x2),v[5].push_back(y2);
	a[x2][y2] ^= 1;
}
int main(){
	//freopen("bulb.in","r",stdin);
	//freopen("bulb.out","w",stdout); 
	int i,j,t,res,x0,y0,x1,y1,x2,y2;
	scanf("%d",&t);
	while(t--){
		for(i = 0;i <= 5;i++) v[i].clear();
		scanf("%d %d",&n,&m);
		for(i = 1;i <= n;i++){
			scanf("\n%s",ch + 1);
			for(j = 1;j <= m;j++) a[i][j] = ch[j] - '0';
		}
		for(i = 1;i <= n - 2;i++){
			for(j = 1;j <= m - 1;j++){
				if(a[i][j]){
					x1 = i + 1,y1 = j,x2 = i + 1,y2 = j + 1;
					if(a[i][j + 1])	x2 = i;
					record(i,j,x1,y1,x2,y2);
				}
			}
			if(a[i][m]) record(i,m,i + 1,m - 1,i + 1,m);
		}
		for(j = 1;j <= m - 1;j++){
			if(a[n - 1][j] || a[n][j]){
				x0 = n - 1,y0 = j,x1 = n,y1 = j,x2 = n - 1,y2 = j + 1;
				if(!a[x0][y0]) x0 = n,y0 = j + 1;
				if(!a[x1][y1]) x1 = n,y1 = j + 1;
				record(x0,y0,x1,y1,x2,y2);
			}
		}
		if(a[n - 1][m] && a[n][m]){
			record(n - 1,m,n - 1,m - 1,n,m - 1);
			record(n - 1,m - 1,n,m - 1,n,m);
		}
		else if(a[n - 1][m]){
			record(n - 1,m - 1,n,m - 1,n - 1,m);
			record(n - 1,m - 1,n - 1,m,n,m);
			record(n - 1,m,n,m - 1,n,m);
		}
		else if(a[n][m]){
			record(n - 1,m - 1,n - 1,m,n,m);
			record(n - 1,m - 1,n,m - 1,n,m);
			record(n - 1,m,n,m - 1,n,m);
		}
		int ans = v[0].size();
		printf("%d\n",ans);
		for(i = 0;i < ans;i++)
			printf("%d %d %d %d %d %d\n",v[0][i],v[1][i],v[2][i],v[3][i],v[4][i],v[5][i]);
 	}
	return 0;
}

T2看似是一个很复杂的问题,然而我们可以分类讨论找特殊解。这就体现出构造问题的奇妙之处了。
k=1的时候那就是没得选,就是L最小;
k=2有一些讲究,如果L是个偶数,取L和L+1显然异或和只有1,是最好的;不然的话应该取L+1和L+2。
k=3则更复杂一些,根据前面的经验,要试着找异或和为0的情况,否则和k=2一样。
为此,这三个数应该每一位都有两个是1一个是0.最高位不必说,到第二位,为了使这三个数不重复,要使一个是11…,一个是10…,另一个就得是01…。这时候这三个数的大小关系已经确定了,为了尽量有解,最大的11…后面全都加0,而01…后面全都加1,这样一来10…后面也得全都加1.综上所述,这三个数应该满足11000…,10111…,01111…的形式,我们枚举位数即可。
k>=4其实都一样,和k=2差不多,如果L是偶数,直接从L往下取四个数,不然L+1取四个数。
在上面的讨论中,最多的时候要整到L+4,也就是说区间长度不小于5。对于R-L+1<5的情况,位数不多,可以直接暴搜。
代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
long long L,R,k,sum,res,cnt,a[11],num[11];
void find(long long x,int m){
	long long i,temp;
	if(m > 1){
		temp = 0;
		for(i = 1;i < m;i++){//注意是当前考虑到第i个数,还没加这一个
			temp ^= a[i];
			if(temp < res){
				for(i = 1;i <= cnt;i++) num[i] = 0;
				res = temp,cnt = m - 1;
				for(i = 1;i < m;i++) num[i] = a[i];
			}
		}
		if(m > k) return;
	}
	for(i = x;i <= R;i++){
		a[m] = i;
		find(i + 1,m + 1);
	}
}
int main(){
	int t,i;
	long long x,y; 
	//scanf("%d",&t);
	//while(t--){
		scanf("%lld %lld %lld",&L,&R,&k);
		cnt = 0,res = 1e18;
		if(R - L + 1 <= 4) find(L,1);
		else{
			if(k == 1){
				res = num[1] = L,cnt = 1;	
			}
			if(k == 2){
				if(L & 1) num[1] = L + 1,num[2] = L + 2;
				else num[1] = L,num[2] = L + 1;  
				res = 1,cnt = 2;
			}
			if(k == 3){
					for(i = 1;i <= 50;i++){
					x = (3ll << (i - 1)),y = (1ll << i) - 1;
					//必须加ll,不然会默认int而爆掉
					if(L <= y && x <= R){
						num[1] = y,num[2] = y + (1ll << (i - 1)),num[3] = x;
						res = 0,cnt = 3;
					}
				}
				if(!cnt){
					if(L & 1) num[1] = L + 1,num[2] = L + 2;
					else num[1] = L,num[2] = L + 1;  
					res = 1,cnt = 2;
				}
			}
			if(k >= 4){
				if(L & 1) num[1] = L + 4;
				else num[1] = L;
				res = 0,num[2] = L + 1,num[3] = L + 2,num[4] = L + 3,cnt = 4;
			}
		}
		printf("%lld\n%lld\n",res,cnt);
		for(i = 1;i <= cnt;i++) printf("%lld ",num[i]);
		printf("\n");
	//}
	return 0;
}
/*
8 15 3
8 30 7
*/ 

T3乍一看非常复杂,二分图?并查集?其实都不是,这题可以很暴力的做。
现在首先整一个队列,把所有的马丢进去,视为在同一组,每次取一匹,并同时看一下这匹马有多少个敌对关系。如果多于两个,就把他出队,加入另一组,然后把与他敌对的扔进队列。可以证明这个过程是能够结束的,所以直接这么做就行了。

看到题解的那一刻我差点把早饭都吐出来…

代码忘了存,贴段std:

#include<bits/stdc++.h>//这一看就不是我写的
using namespace std;
const int N=300030;
int n,m,c[N]={},e[N][4]={};
int main()
{
	//freopen("horse.in","r",stdin);
	//freopen("horse.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1,a,b;i<=m;++i)
	{
		scanf("%d%d",&a,&b);
		e[a][++*e[a]]=b,e[b][++*e[b]]=a;
	}
	queue<int> q;
	for(int i=1;i<=n;++i)
		q.push(i);
	while(!q.empty())
	{
		int s=q.front(),t=0;
		q.pop();
		for(int i=1;i<=*e[s];++i)
			t+=c[s]==c[e[s][i]];
		if(t>=2)
		{
			c[s]=!c[s];
			for(int i=1;i<=*e[s];++i)
				q.push(e[s][i]);
		}
	}
	for(int i=1;i<=n;++i)
		putchar(c[i]+'0');
	putchar('\n');
	return 0;
}

T4首先确实没有无解的情况。
这题首先能确定,n=0(只有一个球)必然是有解。现在考虑怎么往这个情况上靠近。
很明显,当两堆都是偶数个球,移动的时候是动偶数个;都是奇数个球,产生的结果是偶数个。而奇数个球的堆也必定是偶数个(总数就是偶数),所以我们确实要合并所有的奇数个的堆,产生偶数个的堆。这时候把所有的堆的球数目砍掉一半,问题的规模就小了一级,我们不断操作,最终就会得到只有一个球的情况。过程中记一下答案就行了。
仍然没存代码,贴个std:

#include<bits/stdc++.h>
using namespace std;
const int N=30,M=10010;
int n,m,a[M]={};
int op=0,p[N*M]={},q[N*M]={};
int main()
{
	//freopen("ball.in","r",stdin);
	//freopen("ball.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;++i)
		scanf("%d",a+i);
	for(int d=1;d<=n;++d)
	{
		int l=0;
		for(int i=1;i<=m;++i)
		{
			if(a[i]%2)
			{
				if(l)
				{
					++op,p[op]=l,q[op]=i;
					if(a[l]>=a[i])
						a[l]-=a[i],a[i]+=a[i];
					else
						a[i]-=a[l],a[l]+=a[l];
					l=0;
				}
				else
					l=i;
				//看到这个合并我才感觉到自己考试的时候写的是多么的愚蠢
			}
		}
		for(int i=1;i<=m;++i)
		{
			a[i]/=2;
		}
	}
	printf("%d\n",op);
	for(int i=1;i<=op;++i)
		printf("%d %d\n",p[i],q[i]);
	return 0;
}

Thank you for reading!

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值