Codeforces Round #632 (Div. 2)
A
00是白色,其余都是黑色就是一个可行解。
B
从前往后顺序遍历一次,在
a
a
a序列中出现过了1则之后允许变大,出现过了-1则之后允许变小。
C
第一反应是考虑坏数组(然而真正符合数学逻辑的称呼是: isn’t 好数组),在找了好久好数组之后坚定了找坏数组的信心。
先做一个前缀和,那些数值相同的位置s[i] == s[j]
表明了原序列中[i + 1, j]
是一段0序列,所以我们对所有前缀和排序,数值为第一关键字,下标id为第二关键字,均排为递增顺序。
接下来在数值相同的一个 台阶
上用双指针计数:当当前指针指向第二个位置的时候(显然至少有两个位置才可能构成一个0区间),以该位置为区间右端点的坏区间的左端点最远可以取到前一个元素的位置的后一位,取到后一位是由前缀和与0区间的关系所决定的。而如果某个子区间是坏的,那么它只要包括一个坏区间即可,所以我们在对前缀和的每一个台阶双指针计数时,应该对上述的最远位置不断取max。
此外,我们从1开始对该序列编号,用第0项表示什么都不取,用来构成某个前缀0区间的左端点。
最后求和所有可能的坏区间个数,用总数 n ( n + 1 ) 2 \frac{n(n+1)}{2} 2n(n+1)减去该个数即得好子区间个数。
代码
const int maxn = 2e5 + 10;
struct NODE{
ll key; int id;
}node[maxn];
bool cmp(NODE a, NODE b){
if(a.key != b.key) return a.key < b.key;
else return a.id < b.id;
}
ll ans[maxn];
int main(){
// Fast;
int n; scanf("%d", &n);
for(int i = 1; i <= n; i++){
scanf("%lld", &node[i].key);
node[i].key += node[i - 1].key;
node[i].id = i;
}
sort(node, node + 1 + n, cmp);
int p, q; p = q = 0;
while(p <= n){
while(q <= n && node[q].key == node[p].key){
if(q > p) ans[node[q].id] = max(ans[node[q].id], (ll)node[q - 1].id + 1);
q++;
}
p = q;
}
ll res = (ll)n * (ll)(n + 1) / 2;
for(int i = 1; i <= n; i++){
ans[i] = max(ans[i], ans[i - 1]);
res -= ans[i];
}
printf("%lld\n", res);
return 0;
}
D
当时自己写的时候没有分析清楚这个模型的本质,就弄得很复杂以致于不知道如何处理并行
情况的转头。
事实上,在某1秒内可以转头的面面相觑们不会受到其他对面面相觑转头的影响,这是因为他们的指向是-><-
,这一对箭头显然不会被其他的箭头改变匹配关系;那么据此就可以发现,一对面面相觑转头总要花费某1秒的时间,所以最优的情况就是在1秒内让尽可能多的面面相觑同时转头。
总共转头的次数可以用冒泡排序的思想来考虑,至多不超过 O ( n 2 ) O(n^2) O(n2);但是我的破mac开一个9e6的vector相当卡…最终就选择了开一个二维vector来存储每一秒的最优情况。
有了如上的讨论后,接下来要做的就是分层和分配的两个任务。分层最坏
O
(
n
3
)
O(n^3)
O(n3)地完成(???更精确的分析我等tutorial出来了看一下分析办法,这样分析n3肯定是过不去的,实际复杂度应该小的多),分配就根据k剩余的情况将某些层的并行转头扩展成多秒的异步转头即可。
代码
const int maxn = 3e3 + 10;
int ceng, n, k;
char s[maxn];
vector<vector<int>> vec;
vector<int> temp;
int check(){
int sum = 0;
for(auto i: vec) sum += i.size();
if(sum >= k) return 1;
return 0;
}
int main(){
// Fast;
scanf("%d %d", &n, &k); scanf("%s", s);
while(1){
temp.clear();
int fail = 1;
for(int i = 0; i < n - 1; i++){
if(s[i] == 'R' && s[i + 1] == 'L'){
temp.push_back(i);
fail = 0;
}
}
if(fail) break;
for(auto i: temp) swap(s[i], s[i + 1]);
vec.push_back(temp);
}
ceng = (int)vec.size();
if(k < ceng || !check()) puts("-1");
else{
int rem = k - ceng, act;
for(int i = 0; i < ceng; i++){
int size = (int)vec[i].size();
if(size - 1 <= rem){ act = size - 1; rem -= size - 1;}
else{act = rem; rem = 0; }
int p = 0;
for(int j = 0; j < act; j++){
printf("1 %d\n", vec[i][p++] + 1); printf("\n");
}
printf("%d ", size - act);
for(int j = 0; j < size - act; j++) printf("%d ", vec[i][p++] + 1); printf("\n");
}
}
return 0;
}
E
昨晚感觉是可做题,放弃了D来构造E。然而又看到F过的飞起,又放弃E去看F…最终一个都没做出来qwq
昨晚已经想到了可以构造一个这样的图: 车可以一路畅行,一次都不需要变化;而王后则因为其斜跳的能力被我们骗到一个陷阱里去而不得不瞬间移动。然而当时卡点在于尝试用前几个数字来设置陷阱,这样就不知道如何构造可以使得周围的格子都已经访问过,今天看别人未完成的题解得到了点拨: 用最后的几个数字来设置陷阱,这样前面的路径可以很容易设置成车和皇后都能轻松遍历到,并且可以容易构造使得二人最后都从指定入口开始陷阱之旅,那么也就满足了除了陷阱之外的格子都被遍历到。
下面考虑如何构造陷阱:
记最大值
M
=
n
2
M = n ^2
M=n2.
最开始打算用三个数字
M
,
M
−
1
,
M
−
2
M, M-1, M-2
M,M−1,M−2来构造陷阱,因为要满足把皇后骗走,所以
M
M
M和
M
−
2
M-2
M−2只能呈日字格,
M
−
1
M-1
M−1在
M
M
M的相邻位、
M
−
2
M-2
M−2的斜对角位;但是这样构造后皇后会被骗进去,车却不能一路畅行。那么考虑四个数字构造陷阱:
在如下置于左上角的3 * 2的方格中:
如果两个棋子能同时从M-3开始移动,并且除了这四个格子之外其余格子均被访问过,那么就可以满足皇后掉进陷阱、车畅行无阻。
在该日字格中还有两个空位要填充,接下来就优先考虑简便性,取一些比较好的值让整个矩阵的构造更方便。可以在(2, 2)
取1,(3, 1)
取M-4。那么从1开始,(根据棋子移动规则)只要将右边矩阵横平竖直的顺序赋值即可,而无需更复杂地构造一条欧拉路径。在下面矩阵中,我们倒序赋值,这是为了保证最终棋子停在第0列,以便于进入我们的陷阱中。
代码
const int maxn = 5e2 + 10;
int ans[maxn][maxn];
int main(){
// Fast;
int n; scanf("%d", &n); int M = n * n;
if(n <= 2){puts("-1"); return 0;}
ans[0][0] = M; ans[0][1] = M - 2;
ans[1][0] = M - 3; ans[1][1] = 1;
ans[2][0] = M - 4; ans[2][1] = M - 1;
int cnt = 2;
for(int i = 0; i < 3; i++) for(int j = 2; j < n; j++) ans[i][j] = cnt++;
for(int i = 3; i < n; i++) for(int j = n - 1; j >= 0; j--) ans[i][j] = cnt++;
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++) printf(j != n - 1? "%d ": "%d", ans[i][j]);
printf("\n");
}
return 0;
}
F
晚上想的时候考虑到了枚举gcd进行计数…但是经过了E的磨练后中途换题做F感觉状态很差,已经脑力耗尽了…(2h选手降到1h选手呜呜)
这道题考查思维多于数论,然而我并不能绕出来正确做法。我想到以不超过n的素数们作为一组基底,他们之间两两gcd均为1,同时乘上某个数就可以构成一个gcd为其他的数字;然而这样并不好算上限的截止区域,也不会计算有多个因子存在的个数。多个因子存在的情况其实仔细想一下就好办了,如果当前集合的最大gcd = d
,那么我们可以新添一些数使其gcd = d + 1
而不会产生其他影响: 直接加入不超过n的基底"素数"(1, 2, 3, 5, ..) * (d + 1)
即可。所以现在的问题就是,对于每一个gcd,如何计数该类gcd有几个。正面直接考虑很不方便,可以从2到n的全体的角度进行另一个角度的考虑: 利用欧拉筛记录每个数字除以其最小素因子后的数,将该数组递增排序,按这样的顺序取该集合就可以使得最大gcd
最小。合理性显然。
代码
const int maxn = 5e5 + 10;
int n;
int prime[maxn], top = 0;
int vis[maxn];
void ini(){
for(int i = 2; i <= n; i++){
if(!vis[i]){
prime[top++] = i;
vis[i] = 1;
}
for(int j = 0; j < top && i * prime[j] <= n; j++){
vis[i * prime[j]] = i;
if(i % prime[j] == 0) break;
}
}
}
int main(){
// Fast;
scanf("%d", &n); ini();
sort(vis + 2, vis + n + 1);
for(int i = 2; i <= n; i++) printf("%d ", vis[i]); printf("\n");
return 0;
}