【暑假集训模拟DAY10】构造问题

前言

长达近20天的集训终于告一段落......

何为构造?一般来说是一些求具体方案的问题,通过巧妙的构造方法进行转化或者构造一种合理的策略

最简单的例子比如铺地毯

由于比较考验思维,构造题一般难度不低,不过代码难度不大

今日一见,果不其然,构造什么的统统走开,打了3个暴力+一个固输-1

期望得分:10+30+30+10=80pts

实际得分:10+10+30+0=50pts

好吧。。。才知道没有无解的情况但是告诉说无解输出-1是常规操作

题解

T1 bulb

比较正常的构造题吧,尽管考场上没想到也觉得很难

关键点在于发现对于一个2*2的正方形,无论什么状态都可以在 4 步之内全部熄灭

所以应该可以处理每个2*2的正方形,不过有个小技巧可以不用那么头铁,就是直接按照顺序熄灭就好啦(主要是考场没发现2*2任何状态都能熄灭,以为如果熄灭操作不好以后要撤回,就写了搜索)

一些边角的地方要注意特殊处理,还有最后两行的两个点的特判

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f,N= 105;
int a[N][N],n,m,T;
vector<int> v[7];
void modify(int x0,int y0,int x2,int y2,int x3,int y3)
{
	v[1].push_back(x0),v[2].push_back(y0);
	a[x0][y0]^=1;
	v[3].push_back(x2),v[4].push_back(y2);
	a[x2][y2]^=1;
	v[5].push_back(x3),v[6].push_back(y3);
	a[x3][y3]^=1; 
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		for(int i=1;i<=6;i++) v[i].clear();
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
				scanf("%1d",&a[i][j]);
		for(int i=1;i<=n-2;i++)
		{
			for(int j=1;j<=m-1;j++)
			{
				if(!a[i][j]) continue;
				int x2=i+1,y2=j,x3=i+1,y3=j+1;
				if(a[i][j+1]) x3=i;
				modify(i,j,x2,y2,x3,y3);
			}
			if(a[i][m]) modify(i,m,i+1,m,i+1,m-1);//把(i,m)位置修改好 
		}
		for(int j=1;j<=m-1;j++)
		{
			if(a[n-1][j]||a[n][j]) 
			{
				int x0=n-1,y0=j,x2=n,y2=j,x3=n-1,y3=j+1;
				if(!a[n-1][j]) x0=n,y0=j+1;
				if(!a[n][j]) x2=n,y2=j+1;
				modify(x0,y0,x2,y2,x3,y3);
			}
		}
		//暴力改最后一个/两个位置 
		if(a[n-1][m]&&a[n][m])
		{
			modify(n-1,m-1,n,m-1,n,m);
			modify(n-1,m-1,n,m-1,n-1,m);
		}
		else if(a[n-1][m])
		{
			modify(n-1,m,n,m,n,m-1);
			modify(n-1,m-1,n-1,m,n,m-1);
			modify(n-1,m-1,n-1,m,n,m);
		}
		else if(a[n][m])
		{
			modify(n-1,m,n,m,n-1,m-1);
			modify(n,m,n-1,m,n,m-1);
			modify(n-1,m-1,n,m-1,n,m); 
		}
		printf("%d\n",(int)v[1].size());
		for(int i=0;i<(int)v[1].size();i++) printf("%d %d %d %d %d %d\n",v[1][i],v[2][i],v[3][i],v[4][i],v[5][i],v[6][i]);
			
	}
	
	return 0;
}

T2 set

暴搜挂了,原因应该是 for 里面的 i 没开 long long

正解(emm引用Hanoist的吧):

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的情况,位数不多,可以直接暴搜。

顺便吐槽一句,改的时候因为k==3的情况位运算错了debug了近 1h,我吐了

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f,N = 1e6+10;
int T,k,cnt;
ll l,r,ans[N],tmp[N],minn,now;
map<ll,bool> vis;
void dfs(int stp)
{
	//printf("%d ",stp);
	if(!minn) return;
	if(stp!=0&&now<minn) 
	{
		minn=now;
		for(int i=1;i<=stp;i++) ans[i]=tmp[i];
		cnt=stp;
		//printf("stp==0:%d\n",stp==0); 
		//printf("minn=%lld,stp=%d\n",minn,stp);
	}
	if(stp==k||stp==r-l+1) return;
	for(ll i=l;i<=r;i++)	
	{
		if(!minn) return;
		if(vis[i]) continue;
		vis[i]=1;
		now^=i;
		tmp[stp+1]=i;
		dfs(stp+1); 
		now^=i;
		vis[i]=0;
	}
}
int main()
{
	//init();
	minn=1e18;
	scanf("%lld%lld%d",&l,&r,&k);
	if(r-l+1<=4) //小范围直接搜索 
	{
		dfs(0);
		printf("%lld\n%d\n",minn,cnt);
		for(int i=1;i<=cnt;i++) printf("%lld ",ans[i]);	
		return 0;
	}
	if(k==1) 
	{
		printf("%lld\n%d\n%lld\n",l,1,l);
		return 0;
	}
	if(k==2)
	{
		if(l&1) ans[1]=l+1,ans[2]=l+2;
		else ans[1]=l,ans[2]=l+1;
		minn=1,cnt=2;
	}
	if(k==3)
	{
		for(int i=1;i<=50;i++)	
		{
			ll x=3ll<<(i-1),y=(1ll<<i)-1;
			if(y>=l&&x<=r) 
			{
				ans[1]=x,ans[2]=y;
				ans[3]=x-1;
				//ans[3]=y+(1ll<<(i-1));
				minn=0,cnt=3;
				break;
			}
		}
		if(!cnt)
		{
			if(l&1) ans[1]=l+1,ans[2]=l+2;
			else ans[1]=l,ans[2]=l+1;
			minn=1,cnt=2;
		}
	}
	if(k>=4)
	{
		if(l&1)	ans[1]=l+1,ans[2]=l+2,ans[3]=l+3,ans[4]=l+4;
		else ans[1]=l,ans[2]=l+1,ans[3]=l+2,ans[4]=l+3;
		minn=0,cnt=4;
	}	
	printf("%lld\n%d\n",minn,cnt);
	for(int i=1;i<=cnt;i++) printf("%lld ",ans[i]);	
	return 0;
}
/*
2
8 15 3
8 30 7
*/

T3 horse

一般这种看起来特别像并查集的题,一般都不是并查集---conprour

这题的暴力自然不用说,30pts,可以欣赏我优美的代码

正解就很......很离谱,只能感性理解了

思路就是把不合法的🐎放到一个待处理的队列里,然后每次看队列的元素是否合法,不合法就换组,而且能证明最终一定有解且最多操作m次就可以完成

关于一定有解的感性证明:

我们是把非法的马移动到合法的地方,假设从A移到B,那么A的讨厌关系至少减少两个,B的讨厌关系至多增加一个,所以总的讨厌关系数量至少减少一个

(说实话感觉也不是特别严谨,因为经过测试这个一定有解的性质是因为每个马最多讨厌3个马,但这里没解释出来,也就是说为什么一定能移动到一个合法的位置)

代码:

赛时代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f,N = 1005;
int n,m,bel[N],enm[N][N],cnt[N];
bool finda,vis[N];
set<int> s1,s2;
set<int>::iterator it; 
void dfs(int stp)
{
	if(finda) return;
	if(stp==n) 
	{
		for(int i=1;i<=n;i++)
			printf("%d",bel[i]);
		finda=1;
		return; 
	}
	for(int i=1;i<=n;i++)
	{
		if(finda) return; 
		if(vis[i]) continue;
		vis[i]=1;
		bool flag=0;
		for(it=s1.begin();it!=s1.end();it++)
		{
			if(enm[*it][i]) cnt[*it]++,cnt[i]++;
			if(cnt[*it]>1||cnt[i]>1) flag=1;
		}
		if(!flag) 
		{
			bel[i]=0;
			s1.insert(i);
			dfs(stp+1);
			s1.erase(i); 
			for(it=s1.begin();it!=s1.end();it++)
				if(enm[*it][i]) cnt[*it]--,cnt[i]--;
		}
		else 
			for(it=s1.begin();it!=s1.end();it++)
				if(enm[*it][i]) cnt[*it]--,cnt[i]--;
		//-------------------------
		flag=0;
		for(it=s2.begin();it!=s2.end();it++)
		{
			if(enm[*it][i]) cnt[*it]++,cnt[i]++;
			if(cnt[*it]>1||cnt[i]>1) flag=1;
		}
		if(!flag)
		{
			bel[i]=1;
			s2.insert(i);
			dfs(stp+1);
			s2.erase(i); 
			for(it=s2.begin();it!=s2.end();it++)
				if(enm[*it][i]) cnt[*it]--,cnt[i]--;
		}
		else 
			for(it=s2.begin();it!=s2.end();it++)
				if(enm[*it][i]) cnt[*it]--,cnt[i]--;
		vis[i]=0;
	}
}
int main()
{
	freopen("horse.in","r",stdin);
	freopen("horse.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		enm[x][y]=enm[y][x]=1;
	}
	dfs(0);
	if(!finda) printf("-1");
	return 0;
}
/*
5 9
1 2
3 2
3 1
4 1
4 2
4 3
5 3
5 2
5 1
*/ 
AC代码
#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][0]]=b,e[b][++e[b][0]]=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][0];++i)
				q.push(e[s][i]);
		}
	}
	for(int i=1;i<=n;++i)
		printf("%d",c[i]);
	putchar('\n');
	return 0;
}

T4 ball移球游戏

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

代码没存,不贴了

posted @ 2021-08-19 20:31  conprour  阅读(46)  评论(0编辑  收藏  举报