前言
居然说T1是签到题,直接不会,T2构造简单多了。
太蒟蒻了。qwq
被同桌吊打中。qwq
T2
容易想到一种美丽的构造方法。
n=2时:
0 1
1 0
n=4时:
1 1 0 1
0 0 0 1
1 0 0 0
1 0 1 1
n=6时:
1 1 0 1 0 1
0 0 0 1 0 1
1 1 0 0 0 0
0 0 0 0 1 1
1 0 1 0 0 0
1 0 1 0 1 1
n=8时:
1 1 0 1 0 1 0 1
0 0 0 1 0 1 0 1
1 1 0 0 0 0 0 0
0 0 0 1 0 0 1 1
1 1 0 0 1 0 0 0
0 0 0 0 0 0 1 1
1 0 1 0 1 0 0 0
1 0 1 0 1 0 1 1
容易发现就是从外围用1 1绕一圈,然后再在里面的圈继续相同的方法绕。
对于每一圈,其1的个数为:
( n 2 − 1 ) × 4 × 2 = 4 n − 8 (\frac{n}{2}-1) \times 4\times 2 =4n-8 (2n−1)×4×2=4n−8
特别地,对于n=2的情况,1的个数为2。
对于放置1,就变得非常简单了,外围按照这种方法放即可,里面如果能放下,即n-6后大于等于2,由于是相同的,直接递归即可。
特别地,内围n=2时,放法为
1 0
0 1
我们统计1的个数就有2种方法了
可以每次递归时计算,但对于1e9的数据非常不友好,直接爆栈。
那么可以直接先算出了,用for循环,O(n/6)得到,或者推式子O(1)得到。
这里我使用的for循环。
由于输出较大,建议快输,putchar。
代码实现:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define re register
#define num ch-'0'
void get(int &res){
char ch;bool flag=0;
while(!isdigit(ch=getchar()))
(ch=='-')&&(flag=true);
for(res=num;isdigit(ch=getchar());res=res*10+num);
(flag)&&(res=-res);
}
void print(int X)
{
if(X<0) {
X=~(X-1); putchar('-');}
if(X>9) print(X/10);
putchar(X%10+'0');
}
int n,cas;
int ans;
bool a[10005][10005];
struct node{
int x,y;
};
void solve(node st,node ed){
if(n>2){
for(re int i=st.x+3;i<=ed.x;i+=2) a[i][st.y]=a[i][st.y+1]=1;
for(re int i=st.x;i<=ed.x-3;i+=2) a[i][ed.y]=a[i][ed.y-1]=1;
for(re int i=st.y+3;i<=ed.y;i+=2) a[ed.x][i]=a[ed.x-1][i]=1;
for(re int i=st.y;i<=ed.y-3;i+=2) a[st.x][i]=a[st.x+1][i]=1;
st.x+=3;st.y+=3;
ed.x-=3;ed.y-=3;
n-=6;
solve(st,ed);
}
else if(n==2)
a[st.x][st.y]=a[ed.x][ed.y]=1;
}
signed main(){
//freopen("s.in","r",stdin);
//freopen("s.out","w",stdout);
get(n);get(cas);
int N=n;
for(re int i=n;i>=2;i-=6){
ans+=4*1ll*i-8;
if(i==2) {
ans+=2;break;}
}
print(ans);putchar('\n');
if(cas){
if(n==0){
cout<<1<<"\n";
cout<<1;
return 0;
}
if(n==2){
cout<<2<<"\n";
cout<<"1 0"<<"\n";
cout<<"0 1"<<"\n";
return 0;
}
else solve((node){
1,1},(node){
n,n});
for(re int i=1;i<=N;++i){
for(re int j=1;j<=N;++j)
putchar(a[i][j]+'0'),putchar(' ');
putchar('\n');
}
}
return 0;
}
T1
对于异或,显然有如果 A ⊗ B = C A\otimes B=C A⊗B=C,其中 A , C A,C A,C为定值后, B B B的值也唯一确定了。
证明:
令 A ⊗ B = C , A ⊗ D = C ∴ A ⊗ B = A ⊗ D 令A\otimes B=C,A\otimes D=C \\\therefore A\otimes B=A\otimes D 令A⊗B=C,A⊗D=C∴A⊗B=A⊗D
根据结合律,有
A ⊗ ( B − D ) = 0 ∴ B = D A\otimes (B-D)=0 \\\therefore B=D A⊗(B−D)=0∴B=D
- 所以只要最后求的x和a[i]确定了,那么b[i]也就确定了。
由于数据较小,可以暴力枚举每一个 a [ i ] ⊗ b [ j ] a[i]\otimes b[j] a[i]⊗b[j]用hash统计一下相同的值的个数。
这里hash的实现用的unorder_map,和map用法差不多,不过map内部是红黑树,是有序的,unorder顾名思义是无序的。 - 枚举完后直接遍历unorder_map即可,如果某个对应的数量大于等于n,说明该情况可能存在,进一步判断是否存在。
这里遍历使用的是for(auto i:mp) if(i.second>=n) check(i.first);
意思很简单,表示用i去遍历mp这个unorder_map,其中auto表示得到这个i的变量类型。 - 为什么是可能呢?不妨假设 A ⊗ B = x A\otimes B=x A⊗B=x,a数组为A,C,C,C;b数组为B,B,B,B。
我们发现枚举的时候会让 A ⊗ B A\otimes B A⊗B的键值变成4,会通过判断,但实际是不行的,那么我们就要判断是否重复。 - 根据上面有2个值固定了,那么另外的那个值也是固定的。
所以我们可以求不同的b[i]值的个数,再枚举每一个a[i],找 a [ i ] ⊗ x = b [ j ] a[i]\otimes x=b[j] a[i]⊗x=b[j]对应的键值,如果b[j]对应的键值为0,说明在上面的暴力枚举中没有一一匹配,结束。否则,减去1,表示有1个匹配上了这个值。
还是使用unorder_map来统计个数。
代码实现:
void check(int x){
unordered_map<int,int>p;
for(int i=1;i<=n;i++) p[b[i]]++;
for(int i=1;i<=n;i++){
if(p[a[i]^x]==0) return;
p[a[i]^x]--;
}
ans[++cnt]=x;
}
最后如果结束循环后没有强制return,说明一一匹配了,统计答案。
完整代码:
#include<bits/stdc++.h>
using namespace std;
const int N=2e3+5;
int n,a[N],b[N];
unordered_map<int,int>mp;
int ans[N],cnt;
void check(int x){
unordered_map<int,int>p;
for(int i=1;i<=n;i++) p[b[i]]++;
for(int i=1;i<=n;i++){
if(p[a[i]^x]==0) return;
p[a[i]^x]--;
}
ans[++cnt]=x;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) scanf("%d",&b[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
mp[a[i]^b[j]]++;
for(auto i:mp) if(i.second>=n) check(i.first);
cout<<cnt<<endl;
sort(ans+1,ans+cnt+1);
for(int i=1;i<=cnt;i++) cout<<ans[i]<<endl;
return 0;
}