云斗杯 · 五月 Silver 组模拟赛T1T2题解
为啥只有T1和T2? 因为T3T4不会
T1:无所谓的第一道题
题意:给定一张 \(n \times n\) 的 \(01\) 方阵,请计算其中 X 的数目。
X 定义为用 \(1\) 填充且形状为 X 的联通块。具体的,X 由左向斜线 \ 和右向斜线 / 构成,且需要保证左向斜线和右向斜线长度相等,而且 X 是中心对称图形,斜线长度大于 \(1\)。
例如:
101
010
101
中有一个X;
1001
0110
0110
1001
中有两个斜线长度为4的X。
————————↑以上内容均为题目描述↑—————————
考虑如何做:对于中心点为 \(1\) 个 \(1\) 的X,我们可以很容易地想到去枚举中心点,然后向外扩。
那么对于中心为一个块的怎么处理呢?
可以回想一下我们学manachar的时候,是在字符串中插入一些相同的占位符号,以保证每次都能找到一个对称中心。这道题我们也可以用相同的思路处理。也就是,在每一行和每一列之间插入一个字符,然后枚举中心点即可。
复杂度:$O_{(8n^3)} $
当然,可以优化的点很多。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 205;
char a[N][N], s[N][N];
int n;
int dx[4] = {-1, 1, -1, 1};
int dy[4] = {-1, 1, 1, -1};
int X[4], Y[4];
int main() {
scanf("%d", &n);
for(int i = 1; i<=n; i++) {
scanf("%s", a[i]+1);
}
for(int i = 1; i<=n; i++) {
for(int j = 1; j<=n; j++) {
s[(i<<1)-1][(j<<1)-1] = a[i][j];
s[(i<<1)-1][j<<1] = '$';
}
for(int j = 1; j<=2*n-1; j++) {
s[i<<1][j] = '$';
}
}
n<<=1;
for(int i = 0; i<=n; i++) {
s[0][i] = s[n][i] = '#';
s[i][0] = s[i][n] = '#';//边界标志。
}
n--;
long long ans = 0;
for(int i = 1; i<=n; i++) {
for(int j = 1; j<=n; j++) {
for(int k = 1; k<=n; k++) {
if(s[i][j] == '0') {
continue;
}//注意:中间点为0的时候不能扩展qwq否则只有20分
X[0] = i+k*dx[0], Y[0] = j+k*dy[0];
X[1] = i+k*dx[1], Y[1] = j+k*dy[1];
X[2] = i+k*dx[2], Y[2] = j+k*dy[2];
X[3] = i+k*dx[3], Y[3] = j+k*dy[3];
if((s[X[0]][Y[0]] == '#') || (s[X[1]][Y[1]] == '#') || (s[X[2]][Y[2]] == '#') || (s[X[3]][Y[3]] == '#')) {
break;
}
if((s[X[0]][Y[0]] == '0') || (s[X[1]][Y[1]] == '0') || (s[X[2]][Y[2]] == '0') || (s[X[3]][Y[3]] == '0')) {
break;
}
if((s[X[0]][Y[0]] == '1') && (s[X[1]][Y[1]] == '1') && (s[X[2]][Y[2]] == '1') && (s[X[3]][Y[3]] == '1')) {
ans++;
}
}
}
}
printf("%lld\n", ans);
return 0;
}
T2王座下的背叛纲领
这个题我最后一秒写完的,没交上……
简化题意:给定一个 \(w \times h\) 的方阵,每次可以将一个点及其上下左右四个点异或 \(1\),求将矩阵内元素全变成 \(1\) 的最少的操作次数。
说一说我拿到题的思路:我一开始没啥特别好的思路,但是看到 \(w\) 很小,似乎可以状压解决,而且这个题貌似很像之前做过的P3977棋盘。并且因为是异或,所以一个点改变两次状态是没有意义的。于是我就考虑状压dp。我先搜出来了一行的所有操作情况及其对应的状态改变,但是在转移的时候发现总是会把下一个状态算重。
这时候,我发现一个很重要的性质:就是,当你的上一行的状态确定时,你的下一行的操作情况也是确定的。因为你必须保证上一行全变成 \(1\)!那么,你的下一行所对应的操作就是去变换上一行状态中 \(0\) 所对应的位置。
很遗憾,发现这个性质后我没有好好利用,还是去想dp。最后才想到,其实对于第一行的每个初始状态,都有最多一个确定的方案去变换矩阵中的所有元素。那么,我们只需要枚举第一行的状态,然后往下搜索即可。注意,只有最后一行的终状态全为 \(1\),我们才能统计答案。
Code:
#include<bits/stdc++.h>
using namespace std;
const int N = 1200, H = 1210;
int T;
int w, h;
int fst[H];
int tsf[N], aft[N], opr[N], tot;
//分别对应:变换方式(1为变换,0为不变),对应的最终结果(1为异或1,0为不异或),操作次数,方案总数。
int f_tsf[N];//某一状态对应的方案
void dfs(int pos, int x, int y, int op) {
if(pos>=w) {
tsf[++tot] = x;
aft[tot] = y;
opr[tot] = op;
f_tsf[x] = tot;
return;
}
dfs(pos+1, x, y, op);
if(w == 1) {
dfs(pos+1, (x|(1<<pos)), y^1, op+1);
} else if(pos<1) {
dfs(pos+1, (x|(1<<pos)), (y^3), op+1);
} else if(pos == w-1) {
dfs(pos+1, (x|(1<<pos)), (y^(3<<(pos-1))), op+1);
} else {
dfs(pos+1, (x|(1<<pos)), (y^(7<<(pos-1))), op+1);
}
}
int mx;
void print_fst(int aa[], int bb[]) {
for(int i = 1; i<=tot; i++, puts("")) {
for(int j = 0 ; j<w; j++) {
printf("%d", ((aa[i]>>j)&1));
}
printf(" ");
for(int j = 0; j<w; j++) {
printf("%d", ((bb[i]>>j)&1));
}
}
}
long long ans;
void init() {
ans = 0x3f3f3f3f3f3f3f3f;
memset(opr, 0x3f, sizeof(opr));
memset(fst, 0, sizeof(fst));
tot = 0;
memset(tsf, 0, sizeof(tsf));
memset(aft, 0, sizeof(aft));
}
void dfs2(int pos, int lst, long long sum, int opp) {
if(pos>h) {
if(lst == mx) ans = min(sum, ans);
return;
}
int ned = lst^mx;
int tmp = f_tsf[ned];
dfs2(pos+1, fst[pos]^tsf[opp]^aft[tmp], sum+opr[tmp], tmp);
}
int main() {
scanf("%d", &T);
while(T--) {
init();
scanf("%d%d", &w, &h);
for(int i = 0; i<w; i++) {
for(int j = 1; j<=h; j++) {
char tmp[2];
scanf("%s", tmp);
if(tmp[0] == 'A') {
fst[j]|=(1<<i);
}
}
}
mx = (1<<w)-1;
dfs(0, 0, 0, 0);
for(int i = 1; i<=tot; i++) {
dfs2(2, fst[1]^aft[i], opr[i], i);
}
printf("%lld\n", ans);
}
return 0;
}