序号 | 题目 | Solved | Attempted | 类型 | 补题 |
---|---|---|---|---|---|
A | Distinct Sub-palindromes | Sstee1XD、lllllan | / | 思维 | |
B | Fibonacci Sum | / | / | ||
C | Leading Robots | / | / | 单调栈 | Sstee1XD、lllllan |
D | Finding a MEX | / | / | ||
E | Lead of Wisdom | Sstee1XD | / | 暴搜 | lllllan |
F | The Oculus | lllllan | / | 自然溢出哈希 | |
G | Total Eclipse | / | / | 思维+并查集 | lllllan |
H | Tokitsukaze and Multiple | / | / | 前缀和+贪心 | lllllan |
I | Little W and Contest | / | / | 组合数学+并查集 | lllllan |
J | Parentheses Matching | lllllan | / | 贪心 |
文章目录
A - Distinct Sub-palindromes【思维】
题意: (读了大概二十分钟才真正弄懂以后)觉得还是比较简单的,即找出长度为n
的字符串中子串回文数最少的字符串的个数(字符串由小写的字母组成)。说人话就是比如长度为2,那么字符串一共有aa
、ab
、一直到zz
,其中aa
的回文子串有a、aa;ab
中的回文子串有a、b。然后统计这里面回文子串数目最少的字符串的数量。
题解: 首先题目中虚晃了一枪,Since the answer can be huge, output it modulo 998244353.
我们有几个队伍都被这句骗去了,如果真的很大,怎么算?其实没有,长度从1开始,我们逐个找规律:
n = 1:所有字符串长度都为1,单个字符本身也算回文,所以所有的字符串都含有一个回文子串,所有总数为26.
n = 2:总的来看,字符串只有aa
、ab
两种情况,这里的a和b都是虚指。不论是aa
:回文子串有a、aa;还是ab
:回文子串有a、b,其回文子串的数目都是2,所以答案就是26 * 26 = 676.
n = 3:字符串可以分成aaa
、aab
、aba
、abc
三类 四类,,(日常写错)。那么不论是aaa
:回文子串有a、aa、aaa;aab
:回文子串有a、aa、b;aba
:其回文子串有a、b、aba;还是abc
:其回文子串有a、b、c;所有的字符串的回文子串数目均为3,所以答案就是26 * 26 * 26。
再往后,连字符串的分类都费劲起来了,但是我聪明的队友马上发现一个突破口abcabcabc
,这样的一个字符串,不论长度是多少,其回文子串都只有a、b、c(这里的a、b、c都是虚指,代表不同的三个字符),回文子串的数目为3。那么当n ≥ 4时,时代变了 ,长度怎么变,其回文子串数目最少的字符串都符合abcabcabc
的循环规律,而这个的数量其实就是abc
的排列个数26 * 25 *24
,这个就是答案了。
#include<iostream>
using namespace std;
int t, n;
int main(){
scanf("%d", &t);while(t--){
scanf("%d", &n);
if(n == 1) printf("26\n");
if(n == 2) printf("%d\n", 26 * 26);
if(n == 3) printf("%d\n", 26 * 26 * 26);
if(n >= 4) printf("%d\n", 26 * 25 * 24);
}
return 0;
}
C - Leading Robots【单调栈】
题意: 给你n个机器人和他们的初始位置和加速度,沿一条无限长的直线前进,问你有多少个可以成为独一无二的领头羊的机器人。
Sstee1XD的代码
题解: 先通过 v = 1 2 ∗ a ∗ t 2 v = \frac 12 * a * t^2 v=21∗a∗t2我们可以计算出位置在后面但是加速度比上一个有希望成为领头羊的机器人大的机器人超过他成为领头羊的时间,然后再和前一个机器人成为领头羊的时间进行比较,维护一个单调栈,最后的结果再减去有多个相同位置和加速的机器人的数量,就得出了答案。这里直接比较 1 2 t 2 \frac 12t^2 21t2来减少误差。刚开始做的时候在维护单调队列时就删去了有多个相同位置和加速的机器人,导致答案错误。
#include<bits/stdc++.h>
using namespace std;
#define outtime() cerr<<"User Time = "<<(double)clock()/CLOCKS_PER_SEC<<endl
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define bug printf("bug**")
#define coutlf(x) cout << fixed << setprecision(x)
#define uncoutlf cout.unsetf(ios::fixed)
#define endl "\n"
#define fi first
#define se second
#define mp(a,b) make_pair(a,b)
int gcd(int a,int b) { return b?gcd(b,a%b):a;}
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<ll,ll> pII;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-11;
const int maxn = 5e4 + 7;
map<pair<double, double>, int> mp;
int n;
template<class T> inline void read(T &x){
int f=0;x=0;char ch=getchar();
for(;!isdigit(ch);ch=getchar())f|=(ch=='-');
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
if(f)x=-x;
}
struct R{
double p, a;
}r[maxn];
int cmp(R x, R y) {
if (x.p == y.p) return x.a > y.a;
return x.p > y.p;
}
void solve() {
read(n);
mp.clear();
for (int i = 1; i <= n; ++i) {
scanf("%lf %lf", &r[i].p, &r[i].a);
mp[{r[i].p, r[i].a}]++;
}
sort(r + 1, r + 1 + n, cmp);
stack<double> st;
int ans = 0;
int pre[maxn];
st.push(0);
pre[1] = 1;
for (int i = 2; i <= n; ++i) {
if (r[i].p == r[pre[st.size()]].p || r[i].a <= r[pre[st.size()]].a) continue;
while (!st.empty()){
if (st.top() < (r[pre[st.size()]].p - r[i].p) / (r[i].a - r[pre[st.size()]].a)) break;
st.pop();
}
st.push((r[pre[st.size()]].p - r[i].p) / (r[i].a - r[pre[st.size()]].a));
pre[st.size()] = i;
}
for (int i = 1; i <= st.size(); ++i) {
if (mp[{r[pre[i]].p, r[pre[i]].a}] == 1) ans++;
}
printf("%d\n", ans);
}
int main(){
int _T;
read(_T);
while (_T--) solve();
return 0;
}
lllllan的代码
题解: 运用单调栈的思想,栈里存放的是能成为领头的机器人。
- 先给所有的机器人排序,按加速度从小到大,如果加速度相同则按初始位置从小到大。然后我们从前往后遍历,第一个机器人则直接进栈。
- 后面所有的机器人,则和栈顶的机器人进行比较,因为加速度是从小到大排序的,所以参与比较的机器人肯定会在某一时刻超过栈顶的机器人或者是一直就在栈顶机器人的前面。
- 如果该机器人的初始位置就比栈顶机器人的初始位置大,那么栈顶机器人就没有可能成为领头,把栈顶机器人扔出栈。
- 如果该机器人追上栈顶机器人的时间,小于栈顶机器人追上上一个栈顶机器人的时间,则说明栈顶机器人没有可能成为领头,把栈顶机器人扔出栈。
- 执行这两个比较,一直扔掉所有不可能成为领头的栈顶机器人之后,把参与比较的机器人放到栈顶。
- 题目还有一个要求就是成为领头的要求是在某一时刻,只有一个机器人在最前面。如果存在有两个机器人的初始位置和加速度都相同,这是比较特殊的情况,执行前面的操作以后,他们有可能会进入栈里,但是由于他们没有单独领先,所以不是严格意义上的领头,需要删去。
#include<iostream>
#include<algorithm>
#include<utility>
#include<map>
using namespace std;
const int N = 5e4 + 100;
typedef long long ll;
typedef pair<int, int> pii;
#define x first
#define y second
pii a[N];
map<pii, int> mp;
int t, n, top, ans, sta[N];
bool cmp(pii a, pii b){ return a.x == b.x? a.y < b.y : a.x < b.x;}
bool check(pii a, pii b, pii c){ return (ll)(b.x - a.x) * (ll)(c.y - b.y) - (ll)(b.y- a.y) * (ll)(c.x - b.x) >= 0;}
int main(){
scanf("%d", &t);while(t--){
scanf("%d", &n);
mp.clear(), top = ans = 0;
for(int i = 1; i <= n; i++) scanf("%d%d", &a[i].y, &a[i].x), mp[a[i]]++;
sort(a + 1, a + n + 1, cmp);
for(int i = 1; i <= n; i++){
while(top && a[sta[top]].y <= a[i].y || top > 1 && check(a[sta[top - 1]], a[sta[top]], a[i])) top--;
sta[++top] = i;
}
ans = top;
for(int i = top; i; i--)
if(mp[a[sta[i]]] > 1) ans--;
printf("%d\n", ans);
}
return 0;
}
E - Lead of Wisdom 【爆搜】
题意: 给你n个装备,其中有k种类型,每个装备有
a
i
a_i
ai,
b
i
b_i
bi,
c
i
c_i
ci,
d
i
d_i
di的属性,每种类型的装备最多拿一种,求结果
(
100
+
∑
i
∈
s
a
i
)
∗
(
100
+
∑
i
∈
s
b
i
)
∗
(
100
+
∑
i
∈
s
c
i
)
∗
(
100
+
∑
i
∈
s
d
i
)
(100+\sum_{i\in\mathbb s} {a_i}) * (100+\sum_{i\in\mathbb s} {b_i}) * (100+\sum_{i\in\mathbb s} {c_i}) * (100+\sum_{i\in\mathbb s} {d_i})
(100+i∈s∑ai)∗(100+i∈s∑bi)∗(100+i∈s∑ci)∗(100+i∈s∑di)
题解: 刚开始以为是道数学题,稍微算了下时间复杂度感觉暴搜能过,队友写了结构体T了,于是上了数组重打了代码。中间加了个优化,没有出现过的类型就不进行遍历。
Sstee1XD的代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
int n, k, f, _T;
int num[60], t, nxt[60];
ll ans;
ll a[60][60], b[60][60], c[60][60], d[60][60];
void dfs(int u, ll aa, ll bb, ll cc, ll dd) {
for (int i = 1; i <= num[nxt[u]]; ++i) {
ll fa = aa + a[nxt[u]][i], fb = bb + b[nxt[u]][i], fc = cc + c[nxt[u]][i], fd = dd + d[nxt[u]][i];
if (u == f)
ans = max(ans, (100 + fa) * (100 + fb) * (100 + fc) * (100 + fd));
else
dfs(u + 1, fa, fb, fc, fd);
}
}
void solve() {
for (int i = 1; i <= 50; ++i) num[i] = 0;
f = 0;
ans = 100000000;
cin >> n >> k;
ll aa, bb, cc, dd;
for (int i = 1; i <= n; ++i) {
cin >> t >> aa >> bb >> cc >> dd;
if (!num[t]) nxt[++f] = t;
num[t]++;
a[t][num[t]] = aa, b[t][num[t]] = bb, c[t][num[t]] = cc, d[t][num[t]] = dd;
}
sort(nxt + 1, nxt + 1 + f);
dfs(1, 0, 0, 0, 0);
cout << ans << '\n';
}
int main() {
IOS;
cin >> _T;
while (_T--) solve();
return 0;
}
lllllan的代码
需要跳过一些没有装备的类别,就是因为这个问题搞得递归层数过度然后TLE了。
TLE的代码
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
ll ans;
int t, n, k;
struct node{ ll a, b, c, d;};
vector<node> v[55];
void dfs(int x, node tem){
if(x == k + 1) return;
for(int i = 0; i < v[x].size(); i++){
node a = v[x][i];
tem.a += a.a, tem.b += a.b, tem.c +=a.c, tem.d += a.d;
ll sum = tem.a * tem.b * tem.c * tem.d;
ans = max(ans, sum);
dfs(x + 1, tem);
tem.a -= a.a, tem.b -= a.b, tem.c -=a.c, tem.d -= a.d;
}
if(v[x].empty()) dfs(x + 1, tem);
}
int main(){
scanf("%d", &t);while(t--){
ans = 0;
scanf("%d%d", &n, &k);
while(n--){
int x;node tem;
scanf("%d%lld%lld%lld%lld", &x, &tem.a, &tem.b, &tem.c, &tem.d);
v[x].push_back(tem);
}
node tem;tem.a = tem.b = tem.c = tem.d = 100;
dfs(1, tem);
printf("%lld\n", ans);
}
return 0;
}
改版后通过的代码一
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll ans;
int t, n, k, x, id;
struct node{ ll a, b, c, d;}tem;
vector<node> v[55];
map<int, int>m;
void dfs(int x, node tem){
if(x == id){
ll sum = tem.a * tem.b * tem.c * tem.d;
ans = max(ans, sum);
return;
}
for(int i = 0; i < v[x].size(); i++){
node a = v[x][i];
a.a += tem.a, a.b += tem.b, a.c += tem.c, a.d += tem.d;
dfs(x + 1, a);
}
}
int main(){
scanf("%d", &t);while(t--){
ans = 0; id = 1;m.clear();
scanf("%d%d", &n, &k);
for(int i = 0; i <= n; i++) v[i].clear();
while(n--){
scanf("%d%lld%lld%lld%lld", &x, &tem.a, &tem.b, &tem.c, &tem.d);
if(m[x] == 0) m[x] = id++;
v[m[x]].push_back(tem);
}
tem.a = tem.b = tem.c = tem.d = 100;
dfs(1, tem);
printf("%lld\n", ans);
}
return 0;
}
改版后通过的代码二
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a, b, c, d, ans;
int T, n, k, x, id;
struct node{ ll a, b, c, d;};
vector<node> v[55];
map<int, int>m;
void dfs(int x, ll a, ll b, ll c, ll d){
if(x == id){
ans = max(ans, a * b * c * d);
return ;
}
for(int i = 0; i < v[x].size(); i++){
node t = v[x][i];
dfs(x + 1, a + t.a, b + t.b, c + t.c, d + t.d);
}
}
int main(){
scanf("%d", &T);while(T--){
ans = 0; id = 1;m.clear();
scanf("%d%d", &n, &k);
for(int i = 0; i <= n; i++) v[i].clear();
while(n--){
scanf("%d%lld%lld%lld%lld", &x, &a, &b, &c, &d);
if(m[x] == 0) m[x] = id++;
v[m[x]].push_back({a, b, c, d});
}
dfs(1, 100, 100, 100, 100);
printf("%lld\n", ans);
}
return 0;
}
F - The Oculus 【哈希】
题意: 一个斐波那契数列,每一位是可取或不可取,这取决于输入的b的大小,为0不可取,为1可取,这可以称为广义斐波那契数列。给A、B、C的广义斐波那契数列,使得C = A*B,然后改变C中数列中的一个bi使其从1变为0,并把这三个数列给你,问C中是第几bi改变了,输出它。
题解: 其实这题算是误打误撞给我卡过去了,因为构造的广义斐波那契额数列很长,百万级别,笨应该是需要哈希 来做的,但是我只用了longlong,甚至都没有mod处理,给定一个足够大的范围然后让他自己去计算斐波那契数列,结果肯定是导致了数字的溢出,但是这个数字的自然溢出吧,却刚好也能完成这个题目的要求(可能是题目的数据较弱)。
自然溢出哈希
#include<iostream>
using namespace std;
typedef long long ll;
const int N = 3e6 + 10;
int t, n, x;
ll f[N] = {1, 1};
ll readsum(){
ll ans = 0;
scanf("%d", &n);
for(int i = 1; i <= n; i++){
scanf("%d", &x);
if(x) ans += f[i];
}
return ans;
}
int main(){
for(int i = 2; i < N; i++) f[i] = f[i - 1] + f[i - 2];
scanf("%d", &t);while(t--){
ll A = readsum();
ll B = readsum();
ll C = readsum();
ll c = A * B;
for(int i = 1;; i++){
if(C + f[i] == c){
printf("%lld\n", i);
break;
}
}
}
return 0;
}
G - Total Eclipse【并查集】
题意: 给出一个无向带权图,每个点都有权值,现在可以每次在一个连通块中选择权值最小的点并将所有点减去这个权值,该点变为0,花费为这个最小权值,问将所有点都变为0最小的花费.
题解: 看完题目能很容易想到运用并查集来做,可是难道每次先去找最小权值的点,然后每个点减去这个权值,然后继续找继续减,最后得到答案吗?这个思路光是想想都觉得很麻烦。所以有时候正着来解决办法,可以试试看逆着思考。
先把所有点的权值拿来求和,显然只有图中没有连通块的时候,这个才会是答案,那么我门就要思考,我们重复求和了哪些权值?
- 比如
3-2
两个权值分别为2、3的点相互连通,直接求和为5,但实际答案为3. - 比如
3-2-3
三个权值分别为3、2、3,相邻两个点相互连通,直接求和为8,但实际答案为4
或许我们已经能够找到规律了,直接求和的答案中,重复计算了相互连接的两个点中的较小权值。那么我们可以把所有的点按权值从大到小排序,然后直接求所有权值之和。然后我们按从大到小的顺序去遍历每一个点,如果某个点的相互连通的点中,有某个点的权值要大于它,说明此处会出现一个重复计算,需要减去这个点的权值。判断是否连通可以采用并查集。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 1e5 + 10;
int T, m, n, w;
int vis[N], s[N];
vector<int> v[N];
priority_queue<pii> q;
int find(int x){ return s[x]==x? x: s[x]=find(s[x]);}
int main(){
scanf("%d", &T);while(T--){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++){
s[i] = i, vis[i] = 0, v[i].clear();
scanf("%d", &w); q.push({w, i});
}
while(m--){
int a, b;scanf("%d%d", &a, &b);
v[a].push_back(b);
v[b].push_back(a);
}
ll ans = 0;
while(!q.empty()){
pii u = q.top();q.pop();
vis[u.second] = 1;
ans += (ll)u.first;
for(int i = 0; i < v[u.second].size(); i++)
if(vis[v[u.second][i]]){
int fi = find(v[u.second][i]);
if(u.second != fi){
s[fi] = u.second;
ans -= (ll)u.first;
}
}
}
printf("%lld\n", ans);
}
return 0;
}
H - Tokitsukaze and Multiple【前缀和】
题意: 给出一个序列,最多能选出几个子段,使得每段和恰好是p的倍数。
题解: 怎么都没想到前缀和这么好用,试想一下,我们在计算前缀和的时候,如果某个前缀和,减去在它前面的某个前缀和,其值为p的倍数,就符合。但我们可以简化这个算法,每次计算前缀和的时候,就取模。问题就变成了,某个前缀和sum
是否在这之前出现过,和容易想到两个相同的前缀和之间的所有数的和,其实就是p的倍数。那么按照这个思路代码就很简单了。
#include<bits/stdc++.h>
using namespace std;
int t, n, p;
int main(){
scanf("%d", &t);while(t--){
int x, sum = 0, ans = 0;
set<int> s;s.insert(0);
scanf("%d%d", &n, &p);
while(n--){
scanf("%d", &x);
sum = (sum + x) % p;
if(s.count(sum)){
ans++;
sum = 0;
s.clear();
s.insert(0);
}else s.insert(sum);
}
printf("%d\n", ans);
}
return 0;
}
I - Little W and Contest
题意: 给定n个点,有两种点,权值分别为1和2,初始时,n个点互不相连。初始时,n个点互不相连。接着会加入n−1条边,保证每次加入的边的两个端点事先是不相连通的。接着会加入n−1条边,保证每次加入的边的两个端点事先是不相连通的。要从中选择3个点,满足3个点的权值之和不少于5,且3个点之间互不相连,计算出不同的选择方案的数量。要从中选择3个点,满足3个点的权值之和不少于5,且3个点之间互不相连,计算出不同的选择方案的数量。每加入一条边,都要输出当前连通状态下,不同的选择方案的数量。每加入一条边,都要输出当前连通状态下,不同的选择方案的数量。
题解: 参考点这里
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
const int mod = 1e9 + 7;
int T, n, ans, w[N], s[N];
int p1[N], p2[N], cnt[3];
int find(int x){ return s[x]==x? x: s[x]=find(s[x]);}
int main(){
scanf("%d", &T);while(T--){
scanf("%d", &n);
ans = cnt[1] = cnt[2] = 0;
for(int i = 1; i <= n; i++){
s[i] = i;
scanf("%d", w + i);
if(w[i] == 1) p1[i] = 1, p2[i] = 0, cnt[1]++;
else p1[i] = 0, p2[i] = 1, cnt[2]++;
}
if(cnt[2] > 1) ans = (ans + (ll)cnt[2] * (ll)(cnt[2] - 1) / 2 * cnt[1]) % mod;
if(cnt[2] > 2) ans = (ans + (ll)cnt[2] * (ll)(cnt[2] - 1) * (ll)(cnt[2] - 2) / 6) % mod;
printf("%d\n", ans);
for(int i = 1; i < n; i++){
int u, v, k = 0;
scanf("%d%d", &u, &v);
int su = find(u), sv = find(v);
k = (k + (ll)p2[su] * p2[sv] * (cnt[2] - p2[su] - p2[sv])) % mod;
k = (k + (ll)p2[su] * p2[sv] * (cnt[1] - p1[su] - p1[sv])) % mod;
k = (k + (ll)p2[su] * p1[sv] * (cnt[2] - p2[su] - p2[sv])) % mod;
k = (k + (ll)p1[su] * p2[sv] * (cnt[2] - p2[su] - p2[sv])) % mod;
ans = (ans - k + mod) % mod;
s[sv] = su;
p1[su] += p1[sv], p2[su] += p2[sv];
p1[sv] = p2[sv] = 0;
printf("%d\n", ans);
}
}
return 0;
}
J - Parentheses Matching 【模拟】
题意: 这个还比较好玩,通过把'*'
变成'('
或')'
达成字符串中左右括号成对的目的,如果刚好能够成对,则输出现有情况的括号,如果不能,输出No solution!。
题解: 还是花了些时间去构思,但结果是用了两三个循环之后,TLE了,然后改用了stack和vector求解。
#include<iostream>
#include<cstring>
#include<stack>
#include<vector>
using namespace std;
int T;
char str[100005];
int main(){
scanf("%d", &T);
while(T--){
stack<int>t; //'('
vector<int>s; //'*'
scanf("%s", str);
int len = strlen(str), flag = 1;
for(int i = 0; i < len; i++){
if(str[i] == '*') s.push_back(i);
else if(str[i] == '(') t.push(i);
else{
if(!t.empty()) t.pop();
else if(!s.empty()){
str[s.front()] = '(';
s.erase(s.begin());
}else{
flag = 0;
break;
}
}
}
while(!t.empty()){
if(!s.empty() && s.back() > t.top()){
str[s.back()] = ')';
s.pop_back();
t.pop();
}else{
flag = 0;
break;
}
}
if(!flag) printf("No solution!\n");
else{
for(int i = 0; i < len; i++)
if (str[i] != '*')
printf("%c", str[i]);
printf("\n");
}
}
return 0;
}