南京站

做出来三道签到题可以稳$Cu$尾,分别为$A,H,K$;

其中,$A,H$考的是简单的数学推理,$K$题是计算几何板子。

第一次调计算几何还是有点难顶,什么点在线段上点在直线上等等绕的也是有一点点晕的,不过如果有板子而且能熟练对着敲其实做的还是很快的(吧)。

题目复盘

A Hard Problem

$AC$人数最多的一题,可以打表来做。但是如果打表打的不够多,可能会认为答案是素数个数+1(看到不少队都踩这个坑了我开心了$bushi$)。打表到$n=9$的时候,就能推翻前面那个结论了。

如果不打表去想的话,距离新加入的数$n$

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <bits/stdc++.h>
using namespace std;
int main(void)
{
int t;
scanf("%d", &t);
while(t--)
{
int n;
scanf("%d", &n);
printf("%d\n", (n + 1) / 2 + 1);
}
return 0;
}

Prince and Princess

也是一道结论题。

假设要确定最终的答案,则应该满足$a > b + c$(理论上问完所有人后,得到的确切消息,要比假的消息和不确定的消息多)。

然后考虑最小询问次数,应满足b + c == 0 ? 1 : min(a + b + c, 2 * (b + c) + 1)。取min是考虑到最坏情况,问了b+c个说假话或者不确定的人,此时只需要再问b+c+1个人,由于这些人都是说真话的,因此就可以确定最终答案。

记得,有一个特殊情况1 0 0,只有公主一个人,因此一次都不需要询问,直接把公主拉走就可以了。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <bits/stdc++.h>
using namespace std;
int main(void)
{
int a, b, c;
scanf("%d %d %d", &a, &b, &c);
if(a == 1 && b == 0 && c == 0)
printf("YES\n0");
else if(a > b + c)
printf("YES\n%d", b + c == 0 ? 1 : min(a + b + c, 2 * (b + c) + 1));
else
printf("NO");
return 0;
}

Triangle

我的第一道计算几何题。

确定下来另一端结束点在哪条边上然后二分就可以了。

这题主要是熟悉模板就好,没有太多思考难度。

代码如下:

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
#include <bits/stdc++.h>
#define eps 1e-8
using namespace std;
int sgn(double x){
if (fabs(x) < eps)
return 0;
return x < 0 ? -1 : 1;
}
struct Point{
double x, y;
Point() {}
Point(double x, double y) : x(x), y(y) {}
Point operator + (Point B) { return Point(x + B.x, y + B.y); }
Point operator - (Point B) { return Point(x - B.x, y - B.y); }
Point operator * (double k) { return Point(x * k, y * k); }
Point operator / (double k) { return Point(x / k, y / k); }
//Point operator = (Point B) { return Point(B.x, B.y); }
bool operator == (Point B) { return sgn(x - B.x) == 0 && sgn(y - B.y) == 0; }
};
Point A, B, C, P;
typedef Point Vector;
struct Line{
Point p1, p2;
Line() {}
Line(Point p1, Point p2) : p1(p1), p2(p2) {}
};
double Dot(Vector A, Vector B){
return A.x * B.x + A.y * B.y;
}
double Cross(Vector A, Vector B){
return A.x * B.y - A.y * B.x;
}
double Distance(Point A, Point B){
return hypot(A.x - B.x, A.y - B.y);
}
int Point_Line_relation(Point p, Line v){
int c = sgn(Cross(p - v.p1, v.p2 - v.p1));
if (c == 0)
return 0;
return c > 0 ? 2 : 1;
}
int Point_Seg_relation(Point p, Line v){
return sgn(Cross(p - v.p1, v.p2 - v.p1)) == 0 && sgn(Dot(p - v.p1, p - v.p2)) <= 0;
}
double Area_Triangle(Point A, Point B, Point C){
return fabs(Cross(B - A, C - A) / 2);
}
void Print_Point(Point A){
printf("x = %lf, y = %lf\n", A.x, A.y);
}
void Print_Line(Line A){
printf("x1 = %lf, y1 = %lf, x2 = %lf, y2 = %lf\n", A.p1.x, A.p1.y, A.p2.x, A.p2.y);
}
void Solve(Line N, Point Other){
Point lf, rt;
if(fabs(N.p1.x-Other.x) <= eps && fabs(N.p1.y-Other.y) <= eps)
lf = N.p1, rt = N.p2;
else
lf = N.p2, rt = N.p1;
//Print_Point(lf); Print_Point(rt);
//Print_Line(N);
//printf("dd: %lf\n", Area_Triangle(A, B, C));
while(!(lf == rt))
{
//Print_Point(lf); Print_Point(rt);
Point mid = (lf + rt) / 2;
double Area_it = Area_Triangle(P, Other, mid);
//printf("debug: %lf\n", Area_it);
Area_it < Area_Triangle(A, B, C) / 2 ? lf = mid : rt = mid;
}
printf("%lf %lf\n", lf.x, lf.y);
}
int main(void)
{
int t;
scanf("%d", &t);
while(t--)
{
scanf("%lf %lf", &A.x, &A.y);
scanf("%lf %lf", &B.x, &B.y);
scanf("%lf %lf", &C.x, &C.y);
scanf("%lf %lf", &P.x, &P.y);
Line AB = Line(A, B); Line BC = Line(B, C); Line AC = Line(A, C);

//printf("debug: %d %d %d\n", Point_Seg_relation(P, AB), Point_Seg_relation(P, AC), Point_Seg_relation(P, BC));

if(!Point_Seg_relation(P, AB) && !Point_Seg_relation(P, AC) && !Point_Seg_relation(P, BC))
{
printf("-1\n");
}
else
{
int flag = 0;
if(Point_Seg_relation(P, AB) && !flag)
flag = 1, Distance(P, A) < Distance(P, B) ? Solve(BC, B) : Solve(AC, A);
if(Point_Seg_relation(P, BC) && !flag)
flag = 1, Distance(P, B) < Distance(P, C) ? Solve(AC, C) : Solve(AB, B);
if(Point_Seg_relation(P, AC) && !flag)
flag = 1, Distance(P, A) < Distance(P, C) ? Solve(BC, C) : Solve(AB, A);
}
}
}

Digital Path

不难想到记忆化搜索。

设置状态为dp[当前x坐标][当前y坐标][当前长度](一定以(x, y)结尾),就可以进行转移了。

然后考虑细节,不仅是路径的尾部不能够再拓展,头部也不能再拓展,所以在边界情况的时候要写两个check函数帮助我们判断现在这个边界是否代表一种合法解。

需要注意的是,大于4的长度不需要再多考虑,统一按照len=4即可,节省空间,这个处理思想比较重要(开头想着这样空间不够去想别的方程了,后面才想起来长度大于4特判掉就好了)。

代码如下:

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
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define maxn 1005
#define mod 1000000007
#define int long long
using namespace std;
int n, m, board[maxn][maxn], dp[maxn][maxn][5], vis[maxn][maxn];
int dx[] = {0, 1, -1, 0, 0};
int dy[] = {0, 0, 0, 1, -1};
bool check(int x, int y){
return x >= 1 && x <= n && y >= 1 && y <= m;
}
bool extended_end(int x, int y){
for(int i = 1; i <= 4; i++)
{
if(!check(x + dx[i], y + dy[i])) continue;
if(board[x][y] + 1 == board[x + dx[i]][y + dy[i]])
return false;
}
return true;
}
bool extended_init(int x, int y){
for(int i = 1; i <= 4; i++)
{
if(!check(x + dx[i], y + dy[i])) continue;
if(board[x][y] - 1 == board[x + dx[i]][y + dy[i]])
return false;
}
return true;
}
int dfs(int x, int y, int len){
if(len == 1)
return extended_init(x, y) ? 1 : 0;
if(~dp[x][y][len])
return dp[x][y][len];
int res = 0;
for(int i = 1; i <= 4; i++)
{
if(!check(x + dx[i], y + dy[i])) continue;
if(board[x][y] == board[x + dx[i]][y + dy[i]] + 1)
{
res = (res + dfs(x + dx[i], y + dy[i], len - 1)) % mod;
if(len == 4)
res = (res + dfs(x + dx[i], y + dy[i], len)) % mod;
}
}
return dp[x][y][len] = res % mod;
}
signed main(void)
{
memset(dp, -1, sizeof(dp));
scanf("%lld %lld", &n, &m);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
scanf("%lld", &board[i][j]);
int ans = 0;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
if(extended_end(i, j))
ans = (ans + dfs(i, j, 4)) % mod;
printf("%lld\n", ans);
return 0;
}

徐州站

三道签到题,需要手快才能Cu,后面的题目几乎没啥思路。

感觉有点像打表场?

A题可以打表找规律,F题就是交个表上去,C题嘛,我本来想打表的后面发现没什么必要了…

题目复盘

Cat

一道很不错的思维题,主要考虑到对连续四个数有这个规律:__00 ^ __01 ^ __10 ^ __11 = 0

于是,就可以分成三部分来做,一部分是开头零散的猫,一部分是中间可以整块整块买的猫(异或后价格为0的),一部分是末尾零散的猫。

也就是说,只需要快速处理掉中间那部分,然后暴力枚举开头和结尾的猫就可以了。

  • 开头怎么样才有零散的猫?

    打表得,若左区间端点为偶数,则容易分成整块整块的猫(异或后价格为0)。也就是说,若左端点为奇数,只需要将这个端点作为零散的猫处理,再向右移一位作为新的左端点就好。

  • 结尾怎么样才有零散的猫?

    计算出整块整块的猫什么时候会停下来(剩下部分不再能组成异或值为0的块),此时就可以开始枚举。

  • 如何枚举?

    异或运算,若是两段相同则值为0。也就是说,我们可以把左右两边零散的猫都装到数组里面处理,数组含义为第$1$只零散的猫到第$i$只零散的猫这一段的异或值,这样就不需要枚举左右端点。然后,$O(n^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
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define maxn 1000005
#define int long long
using namespace std;
int t, l, r, s, cnt, ans, dp[maxn];
signed main(void)
{
scanf("%lld", &t);
while(t--)
{
cnt = 0; dp[0] = 0;
scanf("%lld %lld %lld", &l, &r, &s);
if(l % 2)
{
++cnt;
dp[cnt] = dp[cnt - 1] ^ l, l++;
}
if((r - l + 1) % 4)
for(int i = l + (r - l + 1) / 4 * 4; i <= r; i++)
{
++cnt;
dp[cnt] = dp[cnt - 1] ^ i;
}
ans = (r - l + 1) / 4 * 4;
int tmp = ans;
for(int i = 0; i <= cnt; i++)
for(int j = i + 1; j <= cnt; j++)
if((dp[i] ^ dp[j]) <= s)
ans = max(ans, tmp + (j - i));
printf("%lld\n", ans ? ans : -1);
}
return 0;
}

<3 numbers

读题意,满足条件的数字其实就是素数和1的并集。

然后,题目是要求判断某个区间内的素数密度是否小于$\frac{1}{3}$。

本来试着用素数个数的估算法来做,发现效果非常不理想。

然后试着分块打表乱搞了一波,打完表之后再区间筛法,理论上是可以过的。

不过我TLE掉了,应该是那个表分的块数太少了,五十万一块或许可能大概能过吧…

代码如下:

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
#include <bits/stdc++.h>
#define maxn 5000005
#define BLOCK_LENGTH 5000000
#define int long long
using namespace std;
int BLOCK[] = {11528251, 11746355, 11875071, 11960486, 12025947, 12077488, 12124874, 12162364, 12194573, 12225757, 12247870, 12275588, 12295179, 12315907, 12333549, 12353427, 12369940, 12387579, 12401593, 12415306, 12428160, 12441645, 12452999, 12463548, 12474851, 12484168, 12493268, 12502639, 12512265, 12520938, 12529814, 12540221, 12548015, 12556536, 12563765, 12570535, 12577188, 12585200, 12592066, 12597064, 12602315, 12609274, 12615786, 12622892, 12628306, 12633867, 12640464, 12645949, 12651743, 12657768, 12662211, 12667490, 12672095, 12676085, 12680612, 12685147, 12688691, 12693999, 12697808, 12701589, 12706459, 12711306, 12714989, 12718386, 12722803, 12727230, 12731628, 12735731, 12739201, 12741903, 12745108, 12747734, 12750905, 12754008, 12756648, 12762011, 12765346, 12767621, 12771134, 12774721, 12778139, 12781101, 12784608, 12787268, 12789937, 12792391, 12794987, 12798110, 12801216, 12804549, 12806821, 12809469, 12812689, 12815709, 12818209, 12820982, 12823225, 12826901, 12828896, 12831363, 12833870, 12836241, 12838483, 12841338, 12843489, 12845942, 12848747, 12851071, 12853491, 12854949, 12856842, 12859662, 12862190, 12864464, 12866777, 12869452, 12872146, 12873814, 12875466, 12877312, 12879117, 12880971, 12882567, 12884173, 12886394, 12887983, 12890171, 12891585, 12893889, 12895894, 12897447, 12899622, 12901353, 12903092, 12905033, 12906735, 12908653, 12910945, 12912483, 12914324, 12915702, 12917194, 12920017, 12921090, 12923162, 12925063, 12927033, 12927959, 12929634, 12930547, 12931978, 12933610, 12934910, 12936498, 12939033, 12940306, 12941889, 12943665, 12944963, 12946181, 12947414, 12948965, 12950563, 12952827, 12954578, 12956139, 12957532, 12958948, 12960280, 12961636, 12963244, 12964412, 12966081, 12967496, 12968628, 12969973, 12971057, 12971994, 12973384, 12974516, 12975556, 12977296, 12978692, 12980150, 12981159, 12982274, 12983796, 12984595, 12986010, 12987195, 12988833, 12990105, 12991086, 12992149, 12993962, 12995413, 12996695, 12997310, 12998856, 12999886};
int lf, rt, cnt, isp[maxn], sp[maxn]; bool iisp[maxn];
void euler(int lim){
memset(iisp, true, sizeof(iisp));
iisp[1] = false;
for(int i = 2; i <= lim; i++)
{
if(iisp[i])
{
cnt++;
sp[cnt] = i;
}
for(int j = 1; j <= cnt && i * sp[j] <= lim ; j++)
{
iisp[i * sp[j]] = false;
if(i % sp[j] == 0) break;
}
}
}
int count(int lf, int rt){
memset(isp, 0, sizeof(isp)); int num = 0;
for(int i = 1; sp[i] * sp[i] <= rt && i <= cnt; i++)
{
int st = lf / sp[i];
while(st <= 1 || sp[i] * st < lf) st++;
for( ;sp[i] * st <= rt; st++)
isp[sp[i] * st - lf] = 1;
}
for(int i = 0; i <= rt - lf; i++)
if(!isp[i]) num++;
return num;
}
int calc(int l, int r){
int ans = 0;
while(r - l >= BLOCK_LENGTH)
ans += BLOCK[cnt++], l = l + BLOCK_LENGTH;
ans += count(l, r);
return ans;
}
signed main(void)
{
euler(50000);
int t;
scanf("%lld", &t);
while(t--)
{
int l, r, ans = 0, cnt = 0;
scanf("%lld %lld", &l, &r);
ans = calc(1, r) - calc(1, l - 1);
//printf("%d\n", ans);
printf(1.0 * ans / (r - l + 1) < 1.0 / 3 ? "Yes" : "No");
putchar('\n');
}
return 0;
}

然后考虑正解。实际上素数分布是会越来越稀疏的,何况素数密度本来就不是很大。因此我们可以考虑分类讨论。

保险一些,当$n \leq 10^4$的时候区间素数筛,其他情况直接返回$Yes$。

后面看了看网上的,好像我的范围太保险了…

代码如下:

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
#include <bits/stdc++.h>
#define maxn 5000005
using namespace std;
int lf, rt, cnt, isp[maxn], sp[maxn]; bool iisp[maxn];
void euler(int lim){
memset(iisp, true, sizeof(iisp));
iisp[1] = false;
for(int i = 2; i <= lim; i++)
{
if(iisp[i])
{
cnt++;
sp[cnt] = i;
}
for(int j = 1; j <= cnt && i * sp[j] <= lim ; j++)
{
iisp[i * sp[j]] = false;
if(i % sp[j] == 0) break;
}
}
}
int count(int lf, int rt){
memset(isp, 0, sizeof(isp)); int num = 0;
for(int i = 1; sp[i] * sp[i] <= rt && i <= cnt; i++)
{
int st = lf / sp[i];
while(st <= 1 || sp[i] * st < lf) st++;
for( ;sp[i] * st <= rt; st++)
isp[sp[i] * st - lf] = 1;
}
for(int i = 0; i <= rt - lf; i++)
if(!isp[i]) num++;
return num;
}
int main(void)
{
euler(50000);
int t;
scanf("%d", &t);
while(t--)
{
int l, r;
scanf("%d %d", &l, &r);
if(r - l + 1 > 10000)
printf("Yes");
else
printf(1.0 * count(l, r) / (r - l + 1) < 1.0 / 3 ? "Yes" : "No");
putchar('\n');
}
return 0;
}

The Answer to the Ultimate Question of Life, The Universe, and Everything.

开始的时候想着移项然后降低复杂度找正解。

后面发现,好像这就是个打表题吧…

这里有个要注意的地方,这题里面,如果要判断某个数开三次方后能不能被整除,不要搞个$pow(x,1/3)$,会出奇怪的BUG。只需要用$map$预处理一下就好了。

其他的就没太多要注意的了。

代码鸽鸽鸽了(关于我挂机打了个WA的表的悲伤的故事)

上海站

上海站理论上的签到好像很多?

换句话来说选手实力都好强看起来他们怎么什么都会做…

然后就是牛客的打印题目真的不友好,计蒜客那边按一下就好了,牛客这边老是缺一半,最后发现手机截图再打印最清晰。

题目复盘

Prefix Code

题面巨长,只有最后一段有点用。一道字典树板子题。然后可以用map快速搞定,不用写真的字典树。

就是判断给出的若干字符串是否存在字符串是其他字符串的前缀。

代码如下:

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
#include <bits/stdc++.h>
#define maxn 10005
#define int long long
using namespace std;
char str[15], s[maxn][15];
unordered_map<string, int> mp;
signed main(void)
{
int t, cases = 0;
scanf("%lld", &t);
while(t--)
{
memset(s, 0, sizeof(s));
mp.clear();
int n, maxx = 0;
scanf("%lld", &n);
for(int i = 1; i <= n; i++)
{
scanf("%s", str);
int len = strlen(str);
for(int j = len; j > 0; j--)
{
s[i][j-1] = str[j-1];
str[j] = '\0';
mp[str]++;
//printf("de: %d ", mp[str]);
//cout << str << endl;
}
}
for(int i = 1; i <= n; i++) maxx = max(mp[s[i]], maxx);
//printf("%d\n", maxx);
printf("Case #%lld: ", ++cases);
printf(maxx > 1 ? "No\n" : "Yes\n");
}
return 0;
}

Cave Escape

刚拿到这题的时候,看了一下,每个点的权值都是大于0的,贪心可得每个点都要走过才能获得最大权值。起点和终点在哪里不重要。

立马口胡了个$DP$,然后看了一下好像只过了$30\%$的数据点。后面冷静想了一下,好像这个不满足最优子结构性质,也不能乱转移?

然后就借鉴了一下网络,发现这是一颗最大生成树。

由于需要建的边有点多,然后跑Kruskal可能会$TLE$掉,因此正解是利用权值并不大的特点,开辟了若干个桶进行桶排序。

这个桶的思想在很多题都能够见到,需要格外留意。上次的$gcd$统计(HDU5656)就是个很好的例子。

把问题转化为从权值去考虑也是很重要的,比如某场牛客枚举mex值的一个计数问题。

当然Kruskal是可以卡过去的。

代码如下:

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
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define maxn 1505
using namespace std;
int t, n, m, sr, sc, tr, tc, cases;
int seq[maxn * maxn], val[maxn][maxn], a, b, c, p;
int eds, uset[maxn * maxn];
struct Edge{
int u;
int v;
int w;
} E[maxn * maxn];
bool cmp(const Edge &A, const Edge &B){
return A.w > B.w;
}
int read(){
int x = 0,f = 1; char ch = getchar();
while (ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x*10+ch-'0'; ch = getchar();}
return x*f;
}
int find(int x){
return x == uset[x] ? x : uset[x] = find(uset[x]);
}
void unionn(int x, int y){
int fx = find(x);
int fy = find(y);
if (fx == fy)
return;
uset[fx] = fy;
}
long long Krus(){
sort(E + 1, E + eds + 1, cmp);
int cnt = 0, now = 1;
long long sum = 0;
while (cnt < n * m - 1)
{
//printf("%lld %lld %lld\n", E[now].u, E[now].v, E[now].w);
//system("pause");
int fu = find(E[now].u);
int fv = find(E[now].v);
if (fu == fv)
{
now++;
continue;
}
sum += E[now].w;
unionn(E[now].u, E[now].v); cnt++; now++;
}
return sum;
}
void Build(int u, int v, int w){
eds++;
E[eds].u = u;
E[eds].v = v;
E[eds].w = w;
}
signed main(void)
{
scanf("%d", &t);
while (t--)
{
eds = 0;
n = read(); m = read(); sr = read(); sc = read(); tr = read(); tc = read();
seq[1] = read(); seq[2] = read(); a = read(); b = read(); c = read(); p = read();
for (int i = 3; i <= n * m; i++)
seq[i] = (a * seq[i - 1] + b * seq[i - 2] + c) % p;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
{
int now = (i - 1) * m + j;
val[i][j] = seq[now];
uset[now] = now;
if (j != 1)
Build((i - 1) * m + j, (i - 1) * m + j - 1, val[i][j] * val[i][j - 1]);
if (i != 1)
Build((i - 1) * m + j, (i - 2) * m + j, val[i][j] * val[i - 1][j]);
}
printf("Case #%d: %lld\n", ++cases, Krus());
}
return 0;
}

Color Graph

如果能够发现这是个二分图的题目,那么这就是道很裸的签到题。

如果没能发现的话还是比较难做的。

前置知识如下:

前置知识1

前置知识2

然后发现$n$很小,直接进行二进制枚举即可。

代码如下:

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
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define maxn 100005
using namespace std;
int t, n, m, u[maxn], v[maxn];
int check(int sta){
int cnt = 0;
for(int i = 1; i <= m; i++)
{
int us = (1 << (u[i] - 1)) & sta ? 1 : 0;
int uv = (1 << (v[i] - 1)) & sta ? 1 : 0;
if(us ^ uv)
cnt++;
}
return cnt;
}
int main(void)
{
int cases = 0;
scanf("%d", &t);
while(t--)
{
int ans = 0;
scanf("%d %d", &n, &m);
for(int i = 1; i <= m; i++)
scanf("%d %d", &u[i], &v[i]);
for(int i = 0; i <= (1 << n) - 1; i++)
{
ans = max(ans, check(i));
}
printf("Case #%d: %d\n", ++cases, ans);
}
return 0;
}

银川站

第一次做到签到题是”HelloWorld”这种的…总感觉怪怪的…

然后队友帮忙敲了大数的Python代码,敲了个签到(取模比我分类讨论快多了),我敲了颗线段树,就欢快打出GG了。

大概是Cu尾巴。有点难顶。

题目复盘

Pot!!

队友和我说是道线段树,就套上了以前的线段树板子直接莽了一波(写得好丑)。

题目要求求出在经过若干次区间乘之后,区间内素数因子的幂次最大数。

只要观察到,初始时序列内数字都是$1$,以及每次区间相乘的数都在$[2,10]$内,这道题就很好做了。因为$[2,10]$的区间乘,只需要考虑$2,3,5,7$四个素数因子就可以了,而区间乘问题也转为了区间加问题,维护四颗线段树对应四个因子即可。

代码如下:

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
#include <iostream>
#include <cstring>
#include <cstdio>
#define maxn 400005
#define int long long
using namespace std;
int n, q, num[maxn];
struct Tree{
int left;
int right;
int minn;
int val;
int lazy_sum;
}t2[maxn], t3[maxn], t5[maxn], t7[maxn];
void Build(int x, int left, int right, Tree tree[]){
tree[x].left = left;
tree[x].right = right;
if(left == right)
{
tree[x].minn = num[left];
return;
}
int mid = (left+right)>>1;
Build((x<<1), left, mid, tree);
Build((x<<1)+1, mid+1, right, tree);
tree[x].minn = max(tree[(x<<1)].minn, tree[(x<<1)+1].minn);
}
void PushDown(int x, Tree tree[]){
tree[(x<<1)].minn += tree[x].lazy_sum;
tree[(x<<1)+1].minn += tree[x].lazy_sum;
tree[(x<<1)].lazy_sum += tree[x].lazy_sum;
tree[(x<<1)+1].lazy_sum += tree[x].lazy_sum;
tree[x].lazy_sum = 0;
}
void Add(int x, int left, int right, int k, Tree tree[]){
if(tree[x].left >= left && tree[x].right <= right)
{
tree[x].minn += k;
tree[x].lazy_sum += k;
return;
}
if(tree[x].left > right || tree[x].right < left)
return;
if(tree[x].lazy_sum) PushDown(x, tree);
Add((x<<1), left, right, k, tree);
Add((x<<1)+1, left, right, k, tree);
tree[x].minn = max(tree[(x<<1)].minn, tree[(x<<1)+1].minn);
}
int Query(int x, int left, int right, Tree tree[]){
if(tree[x].left >= left && tree[x].right <= right)
return tree[x].minn;
if(tree[x].left > right || tree[x].right < left)
return 0;
if(tree[x].lazy_sum) PushDown(x, tree);
return max(Query((x<<1), left, right, tree), Query((x<<1)+1, left, right, tree));
}
signed main(void)
{
scanf("%lld %lld", &n, &q);
Build(1, 1, n, t2); Build(1, 1, n, t3);
Build(1, 1, n, t5); Build(1, 1, n, t7);
for(int i = 1; i <= q; i++)
{
string op;
cin >> op;
if(op == "MULTIPLY")
{
int l, r, v;
scanf("%lld %lld %lld", &l, &r, &v);
if(v == 1) continue;
if(v == 2)
Add(1, l, r, 1, t2);
if(v == 3)
Add(1, l, r, 1, t3);
if(v == 4)
Add(1, l, r, 2, t2);
if(v == 5)
Add(1, l, r, 1, t5);
if(v == 6)
Add(1, l, r, 1, t2),
Add(1, l, r, 1, t3);
if(v == 7)
Add(1, l, r, 1, t7);
if(v == 8)
Add(1, l, r, 3, t2);
if(v == 9)
Add(1, l, r, 2, t3);
if(v == 10)
Add(1, l, r, 1, t2),
Add(1, l, r, 1, t5);
}
else
{
int l, r;
scanf("%lld %lld", &l, &r);
int ans = Query(1, l, r, t2);
ans = max(Query(1, l, r, t3), ans);
ans = max(Query(1, l, r, t5), ans);
ans = max(Query(1, l, r, t7), ans);
printf("ANSWER %lld\n", ans);
}
}
return 0;
}

南昌站

疯狂演队友的一盘。

签到题符号写反$WA$了两发。

最大生成树使用的$Kruskal$,排序后标记【已使用的边】的方法失效了,又$WA$了两发。

我开$G$的时候,$C$题队友推了个奇妙的计数方法,然后迅速切换到$C$题去码,中间和队友沟通的不是很顺利,写炸了一次。沟通完成之后和暴力一对比,$OK$,交。

然后因为我忘了取模又演了队友,$WA$了两发。

现在已经炸到铁首了,必须要再开一题才有牌牌。

然后我接着写$G$,两个队友也暂时找不到能开的题,就一起来看。发现模数不是素数之后,搞了个set来维护最大值对应的最小盘子。

但是并不是很顺利,一直炸到结束都没能跳出来。

”是真的,铁牌已经寄到家了。“

题目复盘

And and Pair

队友想出来的一个奇妙计数方法。

由于目前他自己也忘了怎么想的。

决定以后要是读懂了就再补充这份题解。

代码如下:

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
#include <bits/stdc++.h>
#define maxn 1000005
#define int long long
#define mod 1000000007
using namespace std;
int t, cnt, s[maxn], ne[maxn], dp[maxn];
string str;
int quickpow(int a, int b, int p){
if(b == 0)
return 1;
int tmp = quickpow(a, b / 2, p);
tmp = ((tmp % mod) * (tmp % mod)) % p;
if(b & 1)
tmp = ((tmp % mod) * (a % mod)) % p;
return tmp % mod;
}
signed main(void)
{
scanf("%lld", &t);
while(t--)
{
cnt = 0;
memset(s, 0, sizeof(s));
memset(ne, 0, sizeof(ne));
memset(dp, 0, sizeof(dp));

cin >> str;
int len = str.size(), las = -1;
for(int i = len - 1; i >= 0; i--)
{
if(str[i] == '1')
{
if(las == -1)
ne[i] = len, las = i;
else
ne[i] = las, las = i;
}
}
for(int i = len - 1; i >= 0; i--)
{
if(str[i] == '1')
s[++cnt] = ne[i] - i - 1;
}
dp[1] = quickpow(2, s[1], mod);

for(int i = 2; i <= cnt; i++)
dp[i] = ((dp[i-1] % mod) * ((quickpow(2, s[i], mod) + quickpow(2, s[i] + 1, mod))) % mod) % mod;

int ans = 1;
for(int i = 1; i <= cnt; i++)
ans = (ans + dp[i]) % mod;
printf("%lld\n", ans);
}
return 0;
}