这次div3的数据范围好像自始至终不用开longlong也不会越界,感觉b题算是有点数据事故了,被hack了五六千个,虽然我是hack的主力军吧。浅浅记录一下人生第一次hack了三四十个人的事迹(doge)。上次的think-cell Round 1差点给我打到戒网瘾,牛马题面一点看不懂,差点要rate打div4了。所以在cf每次做题的时候要自己想一下最坏的情况下能不能被hack,尽量往最坏情况下多想一下,不过被黑掉分也是正常的,慢慢练习总会上去的。
不被hack的cfer是不完美的。
A. Thorns and Coins
简单来讲就是每次从起点出发,可以跳跃一格或者或者两格,那么问你能获得的最大金币,那么我们可以想到,每次遇到连续的两个“*”就是不符合题意的。(看错题wa两发)
void solve() {
int n;
cin >> n;
string s;
cin >> s;
int res = 0 ,ans = 0,flag=0;
for(int i = 0 ;i < n ;i ++)
{
if(s[i]!='*') flag=0;
if(s[i] == '@')ans++;
if(s[i] == '*') flag ++;
if(s[i]=='*' && flag == 2) break;
}
res = max(ans,res);
cout << res << endl;
}
B. Chaya Calendar
这个b题就是说按照顺序来找到征兆,也就是要递增的找到每个的倍数。为什么这道题被黑的人很多呢?大家在找寻前一个位置到后一个位置的倍数时,大多采取了while或者for循环每次加1的写法,那么采取最恶劣数据1,1e6,1,就意味着时间复杂度会很高,对于这个题目,由于我在hack的时候也失败好几次,就是如果赛时只想到这个思路的话,对于一个递增的式子,我们可以采取二分这个算法通过时间复杂度
来寻找,这个基础算法在此不在说明,感兴趣可以自行了解。然后最简单的算法大概就是我们会想到每次需要的时候其实就是
下一次的时间 - (前面的总时间 % 下一次的时间)
采取这个想法,我们就可以在内找到符合要求的最小数字。
void solve() {
int n;
cin >> n;
vector<int>v(n);
for(auto &i : v)
cin >> i;
int ans = v[0];
for(int i = 1 ;i < n ;i ++)
{
ans+=v[i] - ans % v[i];
}
cout << ans << endl;
}
C. LR-remainders
c题的含义大概就是对于n个数相乘,每次通过按照字符串的顺序修改值计算出按照顺序的结果。那我们可以尝试一下发现如果我们对字符串中的每个数字进行相乘,那么很显然这个范围会远远的超过数字的范围,那么我们可以想到对于已知的字符串我们可以通过对字符串进行反向运算来得到每一次改删除的位置,起始位置为,每次读取到L则左指针右移,读取到R则右指针左移对每次的位置用数组进行记录。随后按照指定的顺序进行相乘即可得到结果。
void solve() {
int n,m;
cin >> n >> m;
vector<int>v(n),a(n);
int sum = 1;
for(auto &i : v)
cin >> i;
string s;
cin >> s;
int l = 0,r=n-1;
for(int i = 0 ;i < n ;i ++)
{
if(s[i] == 'L')
{
a[i] = l;
l++;
}
else
{
a[i] = r;
r--;
}
}
reverse(a.begin(),a.end());
vector<int>ans;
for(int i = 0 ;i < n ;i ++)
{
if(sum == 0) sum = m;
sum = (v[a[i]] * sum) % m;
ans.push_back(sum);
}
reverse(ans.begin(),ans.end());
for(auto i : ans)
cout << i << " ";
cout << endl;
}
D. Card Game
这个d其实没有任何的思维难度,大概就是王牌必胜其他牌,然后其他同色牌根据大小来进行胜利判断,然后让我们复原一下过程。那么我们可以想到我们可以首先将除了王牌以外的同色进行排序,若是这个颜色的数量为奇数,那么我们就不得不调用一张王牌,此时need数量增加一,若是最后need数量大于王牌数量即不符合题意,关键难点在于对字符串的处理。
void solve()
{
int n;
cin >> n;
string king;
cin >> king;
map<char, vector<int>> mp;
for (int i = 1; i <= n * 2; i++)
{
string s;
cin >> s;
mp[s[1]].push_back(s[0] - '0');
}
int need = 0;
for (auto [c, t] : mp)
{
if (c == king[0])
continue;
need += t.size() % 2;
}
if (need > mp[king[0]].size())
{
cout << "IMPOSSIBLE\n";
return;
}
for (auto &[c, t] : mp)
{
if (c == king[0])
continue;
sort(t.begin(), t.end());
for (int j = 1; j < t.size(); j += 2)
cout << t[j - 1] << c << ' ' << t[j] << c << '\n';
if (t.size() % 2 == 1)
{
cout << t.back() << c << ' ' << mp[king[0]].back() << king[0] << '\n';
mp[king[0]].pop_back();
}
}
for (auto &[c, t] : mp)
{
if (c != king[0])
continue;
sort(t.begin(), t.end());
for (int j = 1; j < t.size(); j += 2)
cout << t[j - 1] << c << ' ' << t[j] << c << '\n';
}
}
E. Final Countdown
这道题的意思大概就是每次从10-9需要动一位,从100-99需要动两位,从1000-999要动三位。假设数为12345,个位的贡献就是12345,十位的贡献是1234(仅当个位跳转时产生贡献),百位贡献是123,后面同理。那么答案就是12345+1234+123+12+1,大概就是如下这种感觉
1 2 3 4 5
1 2 3 4
1 2 3
1 2
1
那么我们不难发现,其实对于每一位来讲,其实就是前面所有位置的前缀和。但是由于数据范围在4e5,因此我们需要模拟一下加法进行相加的过程,即对每一位取模后进位。同时我们会想到会产生进位的情况,因此在输出时我们需要当第一位不等于0后在输出后面的数字。
void solve() {
int n;
cin >> n;
string s;
cin >> s;
s = " " + s;
vector<int>res(n+1,0);
for(int i = 1 ;i <= n ;i ++)
{
res[i]=res[i-1]+s[i]-'0';
}
for(int i = n ;i >= 1 ;i --)
{
res[i - 1] += res[i] / 10;
res[i] = res[i] % 10;
}
int flag = 0;
for(int i = 0 ;i <= n ;i ++)
{
if(res[i] != 0) flag = 1;
if(flag) cout << res[i];
}
cout << endl;
}
F. Feed Cats
对于这个F题,大概就是m只猫位于一段L~R的区间,你可以任选每个阶段进行投喂,若是你选择阶段出现两次及以上都位于这只猫的区间,那么这只猫就会嗝屁。你要做的就是在猫都存活的前提下计算出自己最多能喂养多少只猫。那么我们对这道题进行判断可以发现其实是上一个阶段的选择会对这个阶段有影响,是一个动态的过程,因此我们采取动态规划,但是我们想如果我们的若是设置成哪个猫存活,那么我们在对区间进行判断是很难寻找临界点的,那么如果我们去动态每个端点能选择是否投喂,求在每一个端点的投喂后的最大猫数量。即:
dp[i]为当我最后一次选择端点为i(转移到i)时,此时具有猫的数量 .
那么我们如果要转移的话,当对dp[i]进行投喂时:此时所有包含的区间都不能进行重复投喂,因此我们找到包含
的最左端区间
,:
l[i]为出现包含位置i的左端点最小值,那么我们可以转移过来的区间就是最小值 - 1.
同时我们需要i位置上猫的数量,即cnt[i]:
cnt[i]为当选择这个端点是所会增加的猫数量
核心状态转移方程为 :
大概思路就是这样,代码里对细节进行了标注
//核心状态转移方程为 dp[i] = dp[l[i] - 1] + cnt[i];
// dp[i]为当我最后一次选择端点为i(转移到i)时,此时具有猫的数量
// l[i]为出现包含位置i的左端点最小值,那么我们可以转移过来的区间就是最小值 - 1
// cnt[i]为当选择这个端点是所会增加的猫数量
void solve() {
int n,m;
cin >> n >> m;
vector<pair<int,int>>v(m);
vector<int>cnt(n+2),l(n+2);
vector<int>dp(n+2,0);
for(auto &i : v)
{
cin >> i.first >> i.second;
cnt[i.first]++;
cnt[i.second + 1]--;
}
sort(v.begin(),v.end());
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>>q;
int now = 0; //猫的位置
for(int i = 1 ;i <= n ;i ++) // 目的是为了找到包含i的区间的最小左端点值
{
while(now < m && v[now].first == i) // 当左端点为i入队
{
q.push(v[now]);
now++;
}
while(!q.empty() && q.top().second < i) //若是右端点已经小于i说明这个区间是不合理的
{
q.pop();
}
if(q.empty())
l[i] = i; //因为dp转移式子是l[i]-1,当队列为空时说明没有这个端点没猫,直接设置成上一个端点
else
l[i] = q.top().first; //找到目标
cnt[i] += cnt[i - 1]; //顺手计算一下差分数组的前缀和
}
for(int i = 1 ;i <= n ;i ++)
{
dp[i] = max(dp[i-1],dp[l[i] - 1] + cnt[i]);
}
cout << dp[n] << endl;
}