题集链接 ;
官方题解 ;
OP
标准的新生赛,所以补得比较快~
下次出题的时候结构可以参考一下这场,难度分布还比较适用;
A 小贪一手
贪心
思路
出于取模的性质,我们可以直接构造~
由于保证了解一定存在,也不需要担心无解的问题;
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <bits/stdc++.h> using namespace std;typedef long long ll;int main () { int t; cin>>t; int x,y,n; while (t--) { scanf ("%d%d%d" ,&x,&y,&n); int bas=n/x*x+y; if (bas>n)bas-=x; printf ("%d\n" ,bas); } return 0 ; }
B A+B Problem (very easy)
模拟,字符串处理
思路
注意:题目中输入的-
为英文的连字符,并非数学的减号;
先处理出所有合法数字的字符串对整型的映射,再在每次+
或字符串结束时分割字符串,映射到数字并计算;
代码
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 <bits/stdc++.h> using namespace std;typedef long long ll; map<string, int > mp; string bas[100 ] = {};int main () { mp["zero" ] = 0 ;bas[0 ]="zero" ; mp["one" ] = 1 ;bas[1 ]="one" ; mp["two" ] = 2 ;bas[2 ]="two" ; mp["three" ] = 3 ;bas[3 ]="three" ; mp["four" ] = 4 ;bas[4 ]="four" ; mp["five" ] = 5 ;bas[5 ]="five" ; mp["six" ] = 6 ;bas[6 ]="six" ; mp["seven" ] = 7 ;bas[7 ]="seven" ; mp["eight" ] = 8 ;bas[8 ]="eight" ; mp["nine" ] = 9 ;bas[9 ]="nine" ; mp["ten" ] = 10 ; mp["eleven" ] = 11 ; mp["twelve" ] = 12 ; mp["thirteen" ] = 13 ; mp["fourteen" ] = 14 ; mp["fifteen" ] = 15 ; mp["sixteen" ] = 16 ; mp["seventeen" ] = 17 ; mp["eighteen" ] = 18 ; mp["nineteen" ] = 19 ; mp["twenty" ] = 20 ;bas[20 ]="twenty" ; mp["thirty" ] = 30 ;bas[30 ]="thirty" ; mp["forty" ] = 40 ;bas[40 ]="forty" ; mp["fifty" ] = 50 ;bas[50 ]="fifty" ; mp["sixty" ] = 60 ;bas[60 ]="sixty" ; mp["seventy" ] = 70 ;bas[70 ]="seventy" ; mp["eighty" ] = 80 ;bas[80 ]="eighty" ; mp["ninety" ] = 90 ;bas[90 ]="ninety" ; for (int i = 21 ; i <= 99 ; i++) { if (bas[i].length () <= 1 ) { bas[i] = bas[i / 10 * 10 ] + '-' + bas[i % 10 ]; mp[bas[i]] = i; } } int t; string gt; cin >> t; while (t--) { cin >> gt; int p = 0 ; int ans = 0 ; for (int i=0 ;; i++) { if (gt[i] == '+' || !gt[i]) { ans += mp[gt.substr (p, i - p)]; p = i + 1 ; if (!gt[i]) break ; } } cout << ans << endl; } return 0 ; }
C Alice and Bob
博弈论
思路
内容已补全
由于每次最大拿取量的限制,对于偶数m粒棋子,每一轮(各操作一次)的后手(与全局的先后手无关)可以控制两人操作完 只剩 m − 1 − ⌈ m / 2 ⌉ m-1-\lceil m/2\rceil m − 1 − ⌈ m / 2 ⌉ 粒(对于奇数m粒棋子后手无法控制);
由于最后一轮完成后剩余0枚,我们可以解得上一个关键点为2,由2推出再上一个关键点为6……以此类推就会发现关键点的分布规律;
对于初始棋子数,如果恰好为关键点,则可以保证后手(全局的)必胜,否则先手者可以使棋子点数到达关键点,此时后手则必败了(可以认为全局先手第一次操作后到达关键点,此后全局后手变为回合先手,全局先手可以控制总棋子数到达关键点);
代码
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 #include <bits/stdc++.h> using namespace std;typedef long long ll; vector<int >ok;int main () { for (int i=2 ;i<=1000 ;i=i*2 +2 ) { ok.push_back (i); } int t,n; cin>>t; while (t--) { cin>>n; int f=0 ; for (int i=0 ;i<ok.size ()&&!f;i++) { if (n<ok[i])break ; else { if (n==ok[i])f=1 ; } } if (f)printf ("YES\n" ); else printf ("NO\n" ); } return 0 ; }
D 进化
模拟,搜索,贪心
思路
所有运算项均为正数;
对于所有乘法项,显然乘法项越后计算结果越大;
我们可以将运算项视为不可通行块,找到当前区域边界上的所有运算项,将加法项直接计算,如果没有加法项,则选择倍率最低的乘法项运算;
运算项运算后便认为是可通行块,循环以上步骤,直到当前边界上没有新的运算块;
由于数据很小,我们可以放心大胆地多次bfs;
代码
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 #include <bits/stdc++.h> using namespace std;typedef long long ll; pair<int , int > mp[10 ][10 ]; pair<int , int > tmp[70 ];bool rch[10 ][10 ];int di[4 ] = {0 , 1 , 0 , -1 }, dj[4 ] = {1 , 0 , -1 , 0 };int n, m;int ctmp = 0 ;void bfs (int x, int y) { queue<pair<int , int >> que; if (!mp[x][y].first) que.push ({x, y}), rch[x][y] = 1 ; else if (mp[x][y].first > 0 ) { tmp[ctmp++] = {x, y}; } while (!que.empty ()) { int nowi = que.front ().first, nowj = que.front ().second; que.pop (); for (int k = 0 ; k < 4 ; k++) { int nni = nowi + di[k], nnj = nowj + dj[k]; if (nni >= 1 && nni <= n && nnj >= 1 && nnj <= m) { if (!mp[nni][nnj].first && !rch[nni][nnj]) que.push ({nni, nnj}), rch[nni][nnj] = 1 ; else if (mp[nni][nnj].first > 0 && !rch[nni][nnj]) tmp[ctmp++] = {nni, nnj}, rch[nni][nnj] = 1 ; } } } }int main () { int k, ni, nj, tx, ty, t, v; cin >> n >> m; string g; for (int i = 1 ; i <= n; i++) { cin >> g; for (int j = 0 ; j < m; j++) { mp[i][j + 1 ].first = (g[j] == '#' ) ? -1 : 0 ; if (g[j] == 'S' ) ni = i, nj = j + 1 ; } } cin >> k; for (int i = 1 ; i <= k; i++) { scanf ("%d%d%d%d" , &tx, &ty, &t, &v); mp[tx][ty] = {t, v}; } ll ans = 1 ; while (1 ) { ctmp = 0 ; memset (tmp, 0 , sizeof tmp); memset (rch, 0 , sizeof rch); bfs (ni, nj); if (!ctmp) break ; int addd = 0 ; pair<int , int > mnm = {0 , 0 }; for (int i = 0 ; i < ctmp; i++) { if (mp[tmp[i].first][tmp[i].second].first == 1 ) ans += mp[tmp[i].first][tmp[i].second].second, mp[tmp[i].first][tmp[i].second].first = 0 , addd = 1 ; else if (mp[tmp[i].first][tmp[i].second].first == 2 && (mnm == make_pair (0 , 0 ) || mp[tmp[i].first][tmp[i].second].first < mp[mnm.first][mnm.second].first)) { mnm = {tmp[i].first, tmp[i].second}; } } if (!addd) { ans *= mp[mnm.first][mnm.second].second; mp[mnm.first][mnm.second].first = 0 ; } } cout << ans; return 0 ; }
E 防疫物资
树的直径
思路
题目描述便确定了题中所给是一棵树;
我们可以发现在每一个送货回路内,任意两节点都可以认为被走了一次(走过的道路可以组成路径),如果将这两个节点用道路连接,那么这两点原路径上的道路都可以被少走一次;
依此,我们找出这棵树上距离最远的两点即可,就是树的直径;
答案可以表示为原路径长-直径长+1;
树的直径可以由 双dfs法 或者 dp法 求得,时间复杂度相同,这里使用的是前者;
代码
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 #include <bits/stdc++.h> using namespace std;typedef long long ll; vector<int >rod[100005 ]; ll d[100005 ];int pos;int ds=-1 ;void dfs (int u, int f,int lth) { ds++; d[u]=d[f]+lth; if (d[u]>d[pos])pos=u; for (int i = 0 ; i < rod[u].size (); i++) { if (rod[u][i] == f) continue ; dfs (rod[u][i], u,1 ); ds++; } }ll tree_r (int n) { for (int i=1 ;i<=n;i++)d[i]=0 ; pos=0 ; dfs (1 ,1 ,0 ); int lth=ds; for (int i=1 ;i<=n;i++)d[i]=0 ; dfs (pos,pos,0 ); return lth-d[pos]+1 ; }int main () { int n,u,v; cin>>n; for (int i=1 ;i<=n-1 ;i++) { scanf ("%d%d" ,&u,&v); rod[u].push_back (v); rod[v].push_back (u); } cout<<tree_r (n); return 0 ; }
F 有挂
线段树 / 树状数组
思路
区间和线段树模板题;
也许也可以用差分+树状数组解决;
代码
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 #include <bits/stdc++.h> using namespace std;typedef long long ll;const int N = 1e5 ;struct { int l, r; ll sum, lt; } segt[N << 2 | 1 ];void pushdown (int p) { if (segt[p].lt) { segt[p << 1 ].sum += segt[p].lt * (segt[p << 1 ].r - segt[p << 1 ].l + 1 ); segt[p << 1 | 1 ].sum += segt[p].lt * (segt[p << 1 | 1 ].r - segt[p << 1 | 1 ].l + 1 ); segt[p << 1 ].lt += segt[p].lt; segt[p << 1 | 1 ].lt += segt[p].lt; segt[p].lt = 0 ; } }void icg (int p, int cl, int cr, ll d) { if (cl <= segt[p].l && segt[p].r <= cr) { segt[p].sum += d * (segt[p].r - segt[p].l + 1 ); segt[p].lt += d; return ; } pushdown (p); int mid = segt[p].r + segt[p].l >> 1 ; if (cl <= mid) icg (p << 1 , cl, cr, d); if (cr >= mid + 1 ) icg (p << 1 | 1 , cl, cr, d); segt[p].sum = segt[p << 1 ].sum + segt[p << 1 | 1 ].sum; }ll ick (int p, int cl, int cr) { if (cl <= segt[p].l && segt[p].r <= cr) return segt[p].sum; pushdown (p); int mid = segt[p].r + segt[p].l >> 1 ; ll val = 0 ; if (cl <= mid) val += ick (p << 1 , cl, cr); if (cr >= mid + 1 ) val += ick (p << 1 | 1 , cl, cr); return val; }void build (int p, int cl, int cr) { segt[p].l = cl, segt[p].r = cr, segt[p].lt = 0 , segt[p].sum = 0 ; if (cl == cr) { segt[p].sum=0 ; return ; } int mid = cl + cr >> 1 ; build (p << 1 , cl, mid); build (p << 1 | 1 , mid + 1 , cr); segt[p].sum = segt[p << 1 ].sum + segt[p << 1 | 1 ].sum; }int main () { int n,m,x,op,l,r,k; cin>>n>>m>>x; build (1 ,1 ,n); while (m--) { scanf ("%d%d%d" ,&op,&l,&r); if (op==1 ) { scanf ("%d" ,&k); icg (1 ,l,r,k/x+(!(k%x==0 ))); } else { printf ("%lld\n" ,ick (1 ,l,r)); } } return 0 ; }
G
数位dp,感谢汪佬(wxy);
思路
构造状态表示 f [ i ] [ j ] f[i][j] f [ i ] [ j ] 为以 i 结尾长度 m 的区间,如果按照 j 的二进制表示(从低到高第k位(第0位开始表示)为1代表第i-k日打球,0代表该日不打球),欢乐度合法的最大值(若 j 的表示非法,则该值即为 0 );
则有转移方程(状态合法时)
f [ i ] [ ( j < < 1 ) 的 末 m 位 ] = f [ i − 1 ] [ j ] f [ i ] [ ( j < < 1 ) + 1 的 末 m 位 ] = f [ i − 1 ] [ j ] + a [ i ] f[i][(j<<1)的末m位]=f[i-1][j]\\
f[i][(j<<1)+1的末m位]=f[i-1][j]+a[i]
f [ i ] [ ( j < < 1 ) 的 末 m 位 ] = f [ i − 1 ] [ j ] f [ i ] [ ( j < < 1 ) + 1 的 末 m 位 ] = f [ i − 1 ] [ j ] + a [ i ]
其中取末m位的过程通过位运算完成;
由于m最大为8,二进制枚举每个状态不会造成很大的复杂度压力;
__builtin_popcount(t)
是封装好的 bitcount 函数;
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <bits/stdc++.h> using namespace std;typedef long long ll;const int N = 1e5 + 10 ;int f[N][1 << 9 ], n, m, a[N];int main () { cin >> n >> m; for (int i = 1 ; i <= n; ++i) cin >> a[i]; for (int i = 1 ; i <= n; ++i) for (int s = 0 ; s < (1 << m); ++s) { int t = (s << 1 ) & ((1 << m) - 1 ); if (__builtin_popcount(t) <= m / 2 ) f[i][t] = max (f[i][t], f[i - 1 ][s]); if (__builtin_popcount(t) + 1 <= m / 2 ) f[i][t | 1 ] = max (f[i][t | 1 ], f[i - 1 ][s] + a[i]); } int ans = 0 ; for (int s = 0 ; s < (1 << m); ++s) ans = max (ans, f[n][s]); cout << ans; }
H 爱美之心人皆有之
st表,二分
参考并简化了这份代码 ;
思路
st表是一种 O ( n log n ) O(n\log n) O ( n log n ) 时空复杂度预处理,O ( 1 ) O(1) O ( 1 ) 时间复杂度求区间最大 / 小值的数据结构;
对于以 i 为左端点的所有区间而言,随着右端点的右移,区间极差会单调递增,我们可以依此二分;
找到区间极差等于 k 的最左右端点和最右右端点,便可以求出区间个数;
需要注意无解的情况;
代码
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 #include <bits/stdc++.h> using namespace std;typedef long long ll;const int N = 5e5 + 5 ;int mi[N][21 ], ma[N][21 ], lg[N], a[N];int ck_mi (int l, int r) { int k = lg[r - l + 1 ]; return min (mi[l][k], mi[r - (1 << k) + 1 ][k]); }int ck_ma (int l, int r) { int k = lg[r - l + 1 ]; return max (ma[l][k], ma[r - (1 << k) + 1 ][k]); }int main () { int n, k; cin >> n >> k; for (int i = 1 ; i <= n; i++) scanf ("%d" , &a[i]); for (int i = 1 ; i <= n; i++) { ma[i][0 ] = mi[i][0 ] = a[i], lg[i] = log2 (i); } for (int i = 1 ; i <= 20 ; i++) { for (int j = 1 ; j + (1 << i) - 1 <= n; j++) { mi[j][i] = min (mi[j][i - 1 ], mi[j + (1 << (i - 1 ))][i - 1 ]); ma[j][i] = max (ma[j][i - 1 ], ma[j + (1 << (i - 1 ))][i - 1 ]); } } ll ans = 0 ; for (int i = 1 ; i <= n; i++) { int l1 = i, r1 = n; while (l1 < r1) { int mid = (l1 + r1) >> 1 ; if (ck_ma (i, mid) - ck_mi (i, mid) >= k) r1 = mid; else l1 = mid + 1 ; } int l2 = i, r2 = n; while (l2 < r2) { int mid = (l2 + r2 + 1 ) >> 1 ; if (ck_ma (i, mid) - ck_mi (i, mid) <= k) l2 = mid; else r2 = mid - 1 ; } if (ck_ma (i, l1) - ck_mi (i, l1) == k) ans += l2 - l1 + 1 ; } cout << ans; return 0 ; }
I 签签签到
愚人节快乐
思路
脑子空空,注意转义,也可以用raw字符串( C++11功能);
代码
1 2 3 4 5 6 7 8 9 #include <bits/stdc++.h> using namespace std;typedef long long ll;int main () { cout<<"%d%\\n\"" ; return 0 ; }
1 2 3 4 5 6 7 8 9 #include <bits/stdc++.h> using namespace std;typedef long long ll;int main () { cout<<R"(%d%\n")" ; return 0 ; }