【暑假集训模拟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(只有一个球)必然是有解。现在考虑怎么往这个情况上靠近。
很明显,当两堆都是偶数个球,移动的时候是动偶数个;都是奇数个球,产生的结果是偶数个。而奇数个球的堆也必定是偶数个(总数就是偶数),所以我们确实要合并所有的奇数个的堆,产生偶数个的堆。这时候把所有的堆的球数目砍掉一半,问题的规模就小了一级,我们不断操作,最终就会得到只有一个球的情况。过程中记一下答案就行了
代码没存,不贴了