【题解】Educational Codeforces Round 144(CF1796)
被 C 卡了。
最后被 E 的各种分讨劝退,但是同时也学习到了一种换根 dp 的简单写法。
评价:It's educational for me.
A.Typical Interview Problem
题目描述:
有一个包含 F 和 B 的字符串,最开始是空的。我们开始从 向后遍历所有整数,按照下面的规则构成字符串:
- 如果该整数能被 整除,则在该字符串后加入 F
- 如果该整数能被 整除,则在该字符串后加入 B
特别的,如果该整数能同时被 和 整除,则先加入 F 再加入 B。
现在有 次询问,每个给出一个长度为 的字符串 ,请回答该字符串是否是上面字符串的子串(需要连续)。(保证给出的字符串仅包含 F 和 B)
题目分析:
构成的字符串显然是有循环节的,所以直接打表出来几个循环节,然后判断 是否出现就可以了,可以暴力判断。
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
string s,t;
bool chk(int pos,string t){
for(int i=0; i<t.size(); i++){
if(t[i] != s[pos + i]) return false;
}
return true;
}
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
for(int i=1; i<=300; i++){
if(i % 3 == 0) s = s + 'F';
if(i % 5 == 0) s = s + 'B';
}
int T;scanf("%d",&T);
while(T--){
int len;
cin>>len>>t;
bool flag = false;
for(int i=0; i<s.size(); i++){
if(i + len - 1 < s.size() && chk(i,t)) flag = true;
}
if(flag) printf("Yes\n");
else printf("No\n");
}
return 0;
}
B.Asterisk-Minor Template
题目描述:
定义一个字符串的模板 ,其中仅包含小写字母和可以代替任意字符串(包括空字符串)的通配符 '*'。一个合法的模板需要满足 '*' 的数量小于等于小写字母的数量。
有一个仅包含小写字母的字符串 ,如果我们可以选择出一个合法的模板 ,可以通过替换所有的 '*' 使其等于 ,则称字符串 与模板 匹配。
现在有 组询问,每次给出两个仅包含小写字母的字符串 ,请给出一个合法的模板 使其能同时匹配两个字符串,或输出无解 "NO"。
题目分析:
对于一个模板 ,其一定可以表示为 *c
、c*
、*cd*
的形式,所以可以对于每一个都判断一下就好了。
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 100;
char s[N],t[N];
bool flag1[N][N];
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int T;scanf("%d",&T);
while(T--){
scanf("%s%s",s+1,t+1);
int n = strlen(s+1);
int m = strlen(t+1);
bool flag = false;
for(int i=1; i<n; i++) flag1[s[i]-'a'][s[i+1]-'a'] = true;
for(int i=1; i<m; i++){
if(flag1[t[i]-'a'][t[i+1]-'a']){
flag = true;
printf("Yes\n*%c%c*\n",t[i],t[i+1]);
break;
}
}
if(s[1] == t[1] && !flag) printf("Yes\n%c*\n",s[1]),flag = true;
if(s[n] == t[m] && !flag) printf("Yes\n*%c\n",t[m]),flag = true;
if(!flag) printf("No\n");
for(int i=1; i<n; i++) flag1[s[i]-'a'][s[i+1]-'a'] = false;
}
return 0;
}
C.Maximum Set
题目描述:
定义一个集合 是合法的,当且仅当集合中任意两个整数 和 满足 被 整除或 被 整除。
有 次询问,每次给出整数 ,每次询问给出两个回答,第一个是在集合 中选出整数组成的合法集合的最大大小,第二个是大小最大的合法集合的个数。
题目分析:
集合里面的数肯定是由最小值每次乘某个数然后扩展得到的。
为了使得集合大小最大,显然只能乘 或者少量的 ,因为如果乘 就显然不如乘两次 ,这样可以让集合大小加 。
所以可以直接通过 一直乘 进行扩展,这样就可以得到集合最大的大小,然后一个个地将乘 替换为乘 ,计算并计算有多少个最小值符合条件。
这样为什么是合法的,考虑在我们一步步替换的过程中集合的大小不会变,集合大小其实就是质因数分解之后的指数之和,而我们指数最多为 所以可以暴力枚举。
可以发现若当前乘的值为 ,那么最小值 满足条件当且仅当: 且 ,化简一下就是 。
需要注意的一点就是,我们乘 和乘 不同的顺序会造成集合的不同,先乘 和先乘 并不一样,所以贡献要带一个组合数。
代码:
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MOD = 998244353;
int C[105][105];
int binom(int n,int m){
if(n < m || n < 0 || m < 0) return 0;
return C[n][m];
}
signed main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
C[0][0] = 1;
for(int i=1; i<=100; i++){
C[i][0] = 1;
for(int j=1; j<=i; j++){
C[i][j] = (C[i-1][j] + C[i-1][j-1])%MOD;
}
}
int T;scanf("%lld",&T);
while(T--){
int l,r;scanf("%lld%lld",&l,&r);
int tmp = 1,cnt = 0;
while(tmp * l <= r) tmp *= 2,cnt++;
--cnt;tmp /= 2;
int ans = 0;
ans = (ans + r/tmp - l + 1)%MOD;
for(int i=1; i<=cnt; i++){
tmp = tmp / 2 * 3;
if(l * tmp > r) break;
ans = (ans + (r/tmp - l + 1) * binom(cnt,i))%MOD;
}
printf("%lld %lld\n",cnt+1,ans);
}
return 0;
}
D.Maximum Subarray
题目描述:
你得到了一个序列 ,由 个整数组成。并且你还得到了两个整数 。
你需要执行一次操作:选择恰好 个不同的位置加上 ,其余位置减去 。
比如说:如果 ,然后我们选择第一个位置,操作之后的 。
定义 为 序列的子串的最大可能和。 的子串是 的一部分,即序列 对于某个 。同时,空数组也应该被考虑,它的和为 。
让 为 操作后的序列,输出 的最大可能值。
题目分析:
认为 一定为正数,因为若 为负数显然可以 ,就能让 变为正数。
若钦定区间 为我们选择的最优区间,则若区间长度小于 则只能加区间长度个 ,否则只能加 个 并且还有一些位置必须加 。
所以下面的一个想法就是,因为要考虑每一个子串,所以枚举右端点 。
这样我们就可以对于不同的区间的长度进行分类讨论不同的贡献。
若区间长度小于 ,注意到 的区间和可以写为 ,所以只需要预先对每个位置都加 ,这样一个长度小于等于 的区间的权值和就对了。
若区间长度大于等于 ,因为减 的数量与区间长度有关,而加 的数量一定,所以不妨将每个位置都减 ,这样只需要将对应的区间和加 就是正确的权值和。
每一个都相当于一个一定长度的最大子段和,就相当于 中固定了 求 在一定范围内的 的最小值。
这个可以使用 ST 表维护,复杂度 。
代码:
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5+5;
const int INF = 1e18+5;
int st1[N][22],st2[N][22];
int pre1[N],pre2[N];
int query1(int l,int r){
if(l > r) return INF;
int len = log2(r-l+1);
return min(st1[l][len],st1[r-(1<<len)+1][len]);
}
int query2(int l,int r){
if(l > r) return INF;
int len = log2(r-l+1);
return min(st2[l][len],st2[r-(1<<len)+1][len]);
}
signed main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
memset(st1,0x3f,sizeof(st1));
memset(st2,0x3f,sizeof(st2));
int T;scanf("%lld",&T);
while(T--){
int n,k,x;scanf("%lld%lld%lld",&n,&k,&x);
if(x < 0) x = -x,k = n - k;
st1[0][0] = 0,st2[0][0] = 0;
for(int i=1; i<=n; i++){
int a;scanf("%lld",&a);
pre1[i] = pre1[i-1] - x + a;
pre2[i] = pre2[i-1] + x + a;
st1[i][0] = pre1[i];
st2[i][0] = pre2[i];
}
for(int i=1; i<=20; i++){
for(int j=0; j+(1<<i)-1<=n; j++){
st1[j][i] = min(st1[j][i-1],st1[j+(1<<(i-1))][i-1]);
st2[j][i] = min(st2[j][i-1],st2[j+(1<<(i-1))][i-1]);
}
}
int ans = 0;
for(int i=1; i<=n; i++){
if(i >= k){
int tmp = query1(0,i-k);
ans = max(ans,pre1[i] - tmp + 2 * x * k);
}
int tmp = query2(max(i-k+1,0ll),i);
ans = max(ans,pre2[i] - tmp);
}
printf("%lld\n",ans);
for(int i=0; i<=20; i++){
for(int j=0; j+(1<<i)-1<=n; j++){
st1[j][i] = INF;
st2[j][i] = INF;
}
}
}
return 0;
}
E.Colored Subgraphs
题目描述:
Alice有一棵 个节点的树。
他要选择一个节点 ,然后:
- 设一个数组 , 表示 点到 点的距离。
- 并把所有节点都染个色。
但染色必须要满足两个条件:
- 对于任意被染了相同颜色的点对 ,从点 到点 的路径上所有点(包括点 和点 )的颜色都一样。
- 对于任意被染了相同颜色的点对 ,条件 必须都满足。
现在,定义一个染色方案的代价为一种颜色的最小出现次数,即 。
现在,他让你选择一个最优的节点 ,使得染色方案的最小代价最大。
题目分析:
要解决这个问题显然需要换根 ,所以现在的问题就是给定一个 怎么求解答案。
考虑一个贪心的想法就是每种颜色从叶子向上扩展,当某一个节点有多个儿子,则选择所在的链长度最短的一个儿子的颜色作为当前点的颜色,然后向上扩展这一条链。
这个过程放到 下理解就是,设 表示以 为根的子树按照上述过程操作之后的 所在的链长度。
转移的话显然就是从儿子里选一个最小的转移,没被选择的部分对答案造成贡献,而显然对答案造成贡献的就是所有儿子的次小值,所以只需要记一下次小值。
而换根的话就是考虑根由 ,那么就将 对 的贡献删去,然后加入 对 的贡献,为了维护次小值就必须同时维护第三小值,就比较爆炸。
一个很好写的做法就是,对于每个点 维护 表示 所有儿子所在链的长度,这样对 父亲的贡献就是 中的最小值,对答案的贡献就是 中的次小值,关键是换根的之后只需要在 中删去 的贡献然后在 中加入 的贡献即可,省去了所有的分类讨论,产生的影响只是复杂度多了一个 ,而在一般的换根 中 与 显然没区别。
要注意的就是根对答案的贡献为 中的最小值。
在代码实现的时候我默认 中的所有状态不包含 这个点,所以要注意一下细节。
代码:
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF = 0x3f3f3f3f;
const LL mod = 1e9 + 7;
const int N = 200005;
vector<int> G[N];
multiset<int> f[N];
multiset<int> se;
int ans;
int getlen(int u) {
return f[u].size() == 0 ? 1 : *f[u].begin() + 1;
}
void add(int u, int val) {
if (f[u].size() >= 2) se.erase(se.find(*next(f[u].begin())));
f[u].insert(val);
if (f[u].size() >= 2) se.insert(*next(f[u].begin()));
}
void del(int u, int val) {
if (f[u].size() >= 2) se.erase(se.find(*next(f[u].begin())));
f[u].erase(f[u].find(val));
if (f[u].size() >= 2) se.insert(*next(f[u].begin()));
}
void dfs1(int u, int fa) {
for (auto v : G[u]) {
if (v == fa) continue;
dfs1(v, u);
f[u].insert(getlen(v));
}
if (f[u].size() >= 2) se.insert(*next(f[u].begin()));
}
void dfs2(int u, int fa) {
ans = max(ans, min(getlen(u), se.empty() ? INF : *se.begin()));
for (auto v : G[u]) {
if (v == fa) continue;
del(u, getlen(v));
add(v, getlen(u));
dfs2(v, u);
del(v, getlen(u));
add(u, getlen(v));
}
}
int main() {
int _;
scanf("%d", &_);
while (_--) {
int n;
scanf("%d", &n);
se.clear();
for (int i = 1; i <= n; i++) G[i].clear(), f[i].clear();
for (int i = 1; i < n; i++) {
int u, v;
scanf("%d%d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
}
ans = 0;
dfs1(1, 0);
dfs2(1, 0);
printf("%d\n", ans);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律