https://codeforces.com/gym/104334

我对这场比赛的评价是,逆天。

C

做法并不是很困难,但是想不到就很操蛋了。

简单来说,就是 sample

简单来说,把三种类型的操作记为 A,B,C 。

做法就是:

显然,每一个操作要么不操作,要么操作一次,因为操作的先后顺序并不影响结果。

又不难发现,只要枚举 $A_1,A_2,B_1$ 就足以得到所有的 A,B,C,枚举一下就行了。

又不难发现,实际上,我们可以任意指定两种操作全部翻转,不会使三角形发生变化,所以我们实际上可以认为 $A_1,B_1=0$ ,枚举 $A_2$ 就行了。

时空复杂度:$O(n^2)$

可以做到时间复杂度:$O(n)$ 。

做题时的历程:

队友1:“这道题目本质就是给你 $3n$ 个元,$\frac{n(n+1)}{2}$ 个方程组,让你判断是否有解,有可能是存在某种方法缩减方程的规模,或者是可以证明某些方程就是没有用的,或者是某些方程起决定性的因素。”

此时我想:“从这个角度想感觉有点困难,很少从方程组的角度去思考问题。”

然后我就自顾自的去乱搞了。

于是就想了个贼奇怪的写法,首先此时我发现了 $A_1,B_1=0$ 的情况,又发现根据一些情况可以解出奇数位置的值。

队友1:“你这不就是解方程消元的过程吗?”

我:“好像很有道理的样子。😂”

接着我又发现,根据已知的确定的奇数位置的权值,我可以确定 $A,B$ 中偶数位置的相同与否,

只要指定了 $A_2$ 貌似就可以搞出绝大多数的位置,其余位置貌似也可以陆续确定下来了。

队友1:“那你为什么不一开始就枚举 $A_2$ 呢?这样还省了偶数时并查集判断相同与不同的部分。还有,有个队很快就 AC 了这道题目,以我对他们的了解,应该存在更加简单的做法。我觉得应该可以一行一行扫下来,具体我就不清楚了。”

于是又思考了一会,思考我的做法中其实需要枚举的元只有 $A_2$ ,这样全局的权值都能确定了。

这时我闪过了一个念头,就是队友1讲的,或许奇数部分都不需要去写,直接枚举 $A_2$ 就可以得到所有操作的权值!

然后就知道怎么做了。

所以实际上题解的不难发现并不适用于我 QAQ ,队友1说的对啊。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<cstdio>
#include<cstring>
#define N 2100
#define fep(i,x,y) for(int i=x;i<=y;i++)
#define feq(i,x,y) for(int i=x;i>=y;i--)
using namespace std;
char st[N][N];
int n,a[N],b[N],c[N];
int gettype(int x,int y){return st[x][y]-'0';}
bool solve(){
c[1]=gettype(1,1);
c[2]=gettype(2,1)^b[1]^a[2];
b[2]=gettype(2,2)^c[2]^a[1];
fep(i,3,n)c[i]=gettype(i,2)^a[i-1]^b[2],a[i]=gettype(i,1)^b[1]^c[i],b[i]=gettype(i,i)^c[i]^a[1];
fep(i,1,n){
fep(j,1,i){
int x=i-j+1,y=j;
if(gettype(i,j)!=a[x]^b[y]^c[i])return 0;
}
}
return 1;
}
int main(){
scanf("%d",&n);
fep(i,1,n)scanf("%s",st[i]+1);
a[2]=0;if(solve()){printf("Yes\n");return 0;}
a[2]=1;if(solve()){printf("Yes\n");return 0;}
printf("No\n");
return 0;
}

D

实际上在很早的时候就发现这道题目实际搜索的情况数应该很少,实际的贡献应该是由类似这种情况贡献的。

1
2
3
4
 111  111
2221 2121
2111 2121
222 222

但是因为此时 G 更有情况就去做 G 了。

结果做 D 的队友 WA 了,跟我说了一遍他的思路,我突然觉得我能写出比他更短的代码,而且更他清晰,最后二十分钟上机重写,结果暴 WA ,在过了 15 分钟后 AC 了。

悲。

实际上,这道题目比我想象中要难一点,我刚开始的想法没错是没错,但是用到的判断方式是更加巧妙的,我觉得虽然刚开始我的大方向是没有问题的,但是交给我来开这道题目我却不一定能想到这一个判断方法,也就是交给我来开这道题目我是不一定能做出来的,膜拜队友1,Orz。

我的做法是这样子的:

省流:贪心放,能放就放,能产生贡献的只有深度嵌合的两个 piece 所产生的 *2 的影响,而这种情况所占据的格子是一样的,所以贪心放是正确的,至于数量,贪心的过程中统计一下深度嵌合的情况就行了。

顺序遍历整个矩形,观察是否以这个位置作为左上角放下一个 piece 。

不难发现,无论一个 piece 是什么方向,四个角永远都是被占据的,所以我们在判断能够放下一个 piece 但是无法确定方向时,就直接占据四个角,能确定方向就直接放下去。

对于不能确定方向的 piece ,我们需要在四个角标记好这个角属于哪个 piece ,用于后面使用。

然后,就是大型的分类讨论了,这里可以直接看代码。

时空复杂度:$O(nm)$ 。

空间复杂度可以做到 $O(m)$ 。

至于证明,很简单,遵循以下的格式就行了:

归纳法,证明前 i 步满足以下的要求(注:这里说的在一个位置放下 piece 指的是这个 piece 的左上角在这个位置):

  1. 已经放下的 piece 一定合法(没确定方向则任意指定一个方向,同时没有确定方向的 piece 在四个边界的中间的位置一定没有被占用)。
  2. 在存在合法方案的情况下,合法方案在我们已经搜索过的区域会跟我们做出类似的选择:
    1. 如果一个位置有 piece ,那么合法方案也有。
    2. 如果一个位置没有 piece ,那么合法方案也没有。
    3. 如果一个 piece 确定了方向,那么合法方案对应的 piece 一般也是该方向,除了上面举的例子和类似的例子,这种情况下合法方案一定是例子中两种方法的一种。
    4. 任意一个 piece 所在的 $3*3$ 矩阵如果被包含在搜索过的区域,那他一定已经确定了方向。

归纳法证明就行了。

最后就是判断是否有未被占用的格子。

显然,如果存在合法方案,那么所有的 piece 都必须被确定了,反证法:如果合法方案和这种放置方法类似,那么合法方案无法填满格子,矛盾,证毕。

对于不存在合法情况,如果最后有未确定方向的 piece ,那么一定有未被占用的格子,如果没有,又填满了格子,又放置是合法的,那么显然矛盾,存在合法情况。

然后就做完了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <bits/stdc++.h>
#define N 1100
#define NN 1100000
#define fep(i,x,y) for(int i=x;i<=y;i++)
#define feq(i,x,y) for(int i=x;i>=y;i--)
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
const LL mod=998244353;
PII b[NN];int cnt;
int a[N][N],bel[N][N];
int n,m;LL ans=1;
void pd(int x,int y){
int sum=0;
if(a[x+2][y] || a[x][y+2] || a[x+2][y+2])return ;
fep(i,0,2){
fep(j,0,2)sum+=a[x+i][y+j];
}
if(sum>=3)return ;
if((sum==2 && !a[x+1][y+1]) || (sum==2 && a[x+1][y+1] && bel[x+1][y+1]))return ;
if(!sum || (sum==1 && a[x+1][y+1] && !bel[x+1][y+1])){
cnt++;b[cnt]=make_pair(x,y);
fep(i,0,1){
fep(j,0,1)a[x+i*2][y+j*2]=1;//bel[x+i*2][y+j*2]=cnt;
}
fep(i,0,2){
fep(j,0,2)bel[x+i][y+j]=cnt;
}
}
else if(sum==1 && a[x+1][y+1] && bel[x+1][y+1]){
ans=ans*2%mod;
int xx=b[bel[x+1][y+1]].first,yy=b[bel[x+1][y+1]].second;
fep(i,0,2){
fep(j,0,2)a[xx+i][yy+j]=1;
}
fep(i,0,2){
fep(j,0,2)a[x+i][y+j]=1;
}
}
else if(sum==1)return ;
else if(sum==2){
if(bel[x][y+1]){
int xx=b[bel[x][y+1]].first,yy=b[bel[x][y+1]].second;
fep(i,0,2){
fep(j,0,2){
if(!(i==1 && j==1))a[xx+i][yy+j]=1;
}
}
}
else if(bel[x+1][y]){
int xx=b[bel[x+1][y]].first,yy=b[bel[x+1][y]].second;
fep(i,0,2){
fep(j,0,2){
if(!(i==1 && j==1))a[xx+i][yy+j]=1;
}
}
}
fep(i,0,2){
fep(j,0,2)a[x+i][y+j]=1;
}
}
}
char st[N];
int main(){
scanf("%d%d",&n,&m);
fep(i,1,n){
scanf("%s",st+1);
fep(j,1,m)a[i][j]=st[j]-'0';
}
fep(i,1,n-2){
fep(j,1,m-2){
if(!a[i][j])pd(i,j);
}
}
fep(i,1,n){
fep(j,1,m){
if(!a[i][j]){printf("0\n");return 0;}
}
}
printf("%lld\n",ans);
return 0;
}

E

神仙队友 1 直接爆艹,因为他发现如果一个点在圆凸包内,那么必然不存在一条直线使得圆凸包在直线一侧,而点在包外存在,所以直接判断一个这个点是否能以一个小于180度的视角透视看到整个圆凸包。

只要求一下角度就行了。

sample

G

和队友2合力做出来了。

这道题目一开始是队友2负责的,他发现只要对任意两列拉满限制就可以得到最严格的限制,于是开写,但是他没发现题目还有 -1 的情况。

后面 WA 了两发才发现,这时我 A 了 C 开始来帮他,发现其实有可能满足限制的字符串可能不只输入给出的那些。

然后就开始了漫长的讨论沉默寡言的队友加话痨的我

我:“2-set计数?”

队友2:“咋整。”

我:“不会。”

我:“可能是先这样再那样。”

队友2:“emmmm。”

我:“woc,不对啊,怎么可能这么难,不可做啊,不应该是直接算出所有的可能性啊,这应该不可以计数啊,DAG 上怎么记啊,我觉得我们只要判断可能性是不是大于 $n+1$ 就行了啊。”

这时我突然醒悟了,对哦,只要判断是不是大于 $n+1$ 不就行了,这不是随便搞,只要能够在 $O(m)$ 的时间内处理一种可能性不就随便搞了。

然后就是我占用了队伍一半的机时写这道题目,最后竟然强连通都写错的精彩故事,怒交10发罚时,竟然还有两个认识的队伍同样的题目罚时比我们高,乐。

至于怎么 $O(m)$ 时间处理一种情况,很简单,首先,本题中如果一个位置全 $0$ 或者全 $1$ 显然可以直接在图中删去,因为其无法对决定其他位置起到任何的帮助,例如限制是要么你 $1$ 要么他 $0$ ,这个限制显然一点用都没有,而因为其一直是 $1$ ,我们也不可能给出你是 $0$ ,他是 ? 的限制,所以对于这种位置删去即可。

那么就不存在自己连向自己另一个状态的边了,而这个 2-set 又一定有解,存在两个相反的 DAG ,只考虑一个 DAG ,显然,只要点集 $S$ 满足以下要求,那么其就对应一个合法解:

$x$ 在 DAG 上能到 $y$,且 $x$ 属于 $S$ ,那么 $y$ 也属于 $S$ 。

然后就是枚举这样的点集了,直接 DFS 。

假设所有点一开始都在点集中。(一开始都不在也是一样的)

按照拓扑排序进行搜索(主要是为了缩减时间复杂度和判重,记得是要在缩点后的图跑,否则可能会 TLE ):对于一个点,如果能到达他的点都被删了,那么他可以被删,每删除一个点,就能产生一个新的点集。

然后就 DFS 一个位置一个位置判断,直到 DFS 完或者发现出现了一种输入中没有的点集就退出,至于判断两个点集是否相同,可以 Hash 或者 01Trie 。

时空复杂度:$O(m(n+m)+\frac{nm^2}{w})$ 。

PS:但是常数巨大,跑了 3712ms ,差点超时,不过也有可能是我写的太丑导致的,毕竟赛场上赶时间,写丑一点很正常,能过就行不能过另说

UPD:啊,$nm^2$ 跑的过去?比我短,还比我快?啊?做法为 DFS 每次尝试暴力加入一个点,然后将其能到达的点全部加入进去,跑出一种可能性最坏可能把所有边都跑一遍,所以是 $nm^2$ 的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#include <bits/stdc++.h>
#define N 2100
#define NN 4100
#define M 16100000
#define fep(i,x,y) for(int i=x;i<=y;i++)
#define feq(i,x,y) for(int i=x;i>=y;i--)
using namespace std;
typedef vector<int> VI;
typedef long long LL;
int n, m,b[2005][2005];
struct Hash{
LL A,mod,fc[N];
void init(LL x,LL y){
A=x;mod=y;
fc[0]=1;fep(i,1,m)fc[i]=fc[i-1]*A%mod;
}
}h0;
int cntnow=1;
namespace ZJJ{
set<LL> shit;

int fa[NN];bool v[NN];/*delete*/
void init(){fep(i,0,m+m-1)fa[i]=i;}
int findfa(int x){
if(fa[x]==x)return x;
else return fa[x]=findfa(fa[x]);
}
void mer(int x,int y){x=findfa(x);y=findfa(y);fa[x]=y;}

struct node{
int y,next;
}a[M];int len,last[NN],in[N];
void ins(int x,int y){len++;a[len].y=y;a[len].next=last[x];last[x]=len;mer(y,x);}
int list[N],tail;
int dfn[NN],low[NN],sta[NN],top,cnt,ti,belong[NN];
VI fmem[NN];

void dfs1(int x){
low[x]=dfn[x]=++ti;sta[++top]=x;list[++tail]=x;
for(int k=last[x];k;k=a[k].next){
int y=a[k].y;
if(!dfn[y]){
dfs1(y);low[x]=min(low[x],low[y]);
}
else if(!belong[y])low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x]){
cnt++;belong[x]=cnt;fmem[cnt].clear();in[cnt]=0;fmem[cnt].push_back(x);
while(sta[top]!=x)belong[sta[top--]]=cnt,fmem[cnt].push_back(sta[top+1]);
top--;
}
}
bool del[NN],edg[N][N];
bool check(){
LL sum=0;
fep(i,1,tail)((!del[list[i]] && list[i]>=m) || (del[list[i]] && list[i]<m))?sum=(sum+h0.fc[list[i]%m])%h0.mod:0;
// printf("%lld\n",sum);
return shit.find(sum)!=shit.end();
}
bool dfs2(int dep){
if(!dep)return 1;
if(!in[dep]){
// if(dep==2)printf("%d\n",in[1]);
fep(i,1,dep-1)in[i]-=edg[dep][i];
for(auto x:fmem[dep])del[x]=1;
if(!check())return 0;
if(!dfs2(dep-1))return 0;
for(auto x:fmem[dep])del[x]=0;
fep(i,1,dep-1)in[i]+=edg[dep][i];
// if(dep==2)printf("%d\n",in[1]);
}
return dfs2(dep-1);
}
bool solve(int stt){
// printf("OK\n");
stt=findfa(stt);tail=0;shit.clear();
cnt=top=0;
fep(i,0,m+m-1){
if(findfa(i)==stt && !dfn[i])dfs1(i);
}
fep(i,1,cnt)memset(edg[i]+1,0,cnt);
fep(i,1,cnt){
for(auto x:fmem[i]){
for(int k=last[x];k;k=a[k].next){
if(belong[x]!=belong[a[k].y])edg[belong[x]][belong[a[k].y]]=1;
}
}
}
// fep(i,1,cnt){
// fep(j,1,cnt)printf("%d ",edg[i][j]);
// printf("\n");
// }
fep(i,1,cnt){
fep(j,1,cnt)in[i]+=edg[j][i];
}
fep(i,1,tail){
int x=list[i]%m;
v[x]=v[x+m]=1;
}
fep(i,0,n-1){
LL pre=0;
fep(j,1,tail){
int x=list[j]%m;
pre=(pre+b[i][x]*h0.fc[x])%h0.mod;
}
shit.insert(pre);
// printf("%lld\n",pre);
}
// printf("%d\n",check());
// fep(i,1,tail)printf("%d ",list[i]);
// printf("\n");
// printf("OK\n");
if(!check() || !dfs2(cnt))return 0;
cntnow*=shit.size();
// printf("OK %d\n",tail);
return n%cntnow==0;
}
}
std::bitset <2000> h1[2005], h2[2005], cs;
int vis[2005];
char s[2005];
struct node{
int x, y, type;
};
std::vector <node> ans;
inline void f(int x, int y, int type){
ans.push_back((node){x, y, type});
if(x==y)ZJJ::v[x]=ZJJ::v[x+m]=1;
return ;
}
void ins(int x,int y,int type){
if(ZJJ::v[x] || ZJJ::v[y])return ;
else{
if(type==1)ZJJ::ins(x+m,y),ZJJ::ins(y+m,x);
else if(type==3)ZJJ::ins(x+m,y+m),ZJJ::ins(y,x);
else if(type==2)ZJJ::ins(x,y),ZJJ::ins(y+m,x+m);
else ZJJ::ins(x,y+m),ZJJ::ins(y,x+m);
}
}
bool solve(){
ZJJ::init();h0.init(2,10000000000000061ll);
for(auto x:ans)ins(x.x,x.y,x.type);
fep(i,0,m-1){
if(!ZJJ::v[i] && !ZJJ::solve(i))return 0;
}
return cntnow==n;
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 0; i < n; ++i){
scanf("%s", s);
for(int j = 0; j < m; ++j)
if(s[j] == '1') b[i][j] = 1;
else b[i][j] = 0;
}
for(int i = 0; i < n; ++i)
for(int j = 0; j < m; ++j)
h1[j][i] = b[i][j],
h2[j][i] = b[i][j] ^ 1;
for(int i = 0; i < m; ++i){
if(h1[i].count() == n) f(i, i, 4), vis[i] = 1;
if(h2[i].count() == n) f(i, i, 1), vis[i] = 1;
}
for(int i = 0; i < m; ++i)
for(int j = i + 1; j < m; ++j){
if(vis[i] || vis[j]) continue;
cs = h1[i] ^ h1[j];
if(cs.count() == n){
f(i, j, 1), f(i, j, 4);
continue;
}
cs = h1[i] | h1[j];
if(cs.count() == n) f(i, j, 4);
cs = h1[i] | h2[j];
if(cs.count() == n) f(i, j, 2);
cs = h2[i] | h1[j];
if(cs.count() == n) f(i, j, 3);
cs = h2[i] | h2[j];
if(cs.count() == n) f(i, j, 1);
}
if(!solve()){printf("-1\n");return 0;}
printf("%d\n", (int)ans.size());
for(auto v : ans) printf("%d %d %d\n", v.x, v.y, v.type);
return 0;
}