AtCoder Beginner Contest 409
AtCoder Beginner Contest 409
A题
知识点:枚举
题意为给了两个长度为 N N N的字符串S1
和S2
,字符串仅包含o
和x
字符,问两个字符串是否存在一个位置i
使得S1[i]==S2[i]&&S1[i]=='o'
。时间复杂度为 O ( n ) O(n) O(n)。
void solve(){int n;cin >> n;string s1,s2;cin >> s1 >> s2;for(int i=0;i<n;i++){if(s1[i]==s2[i]&&s1[i]=='o'){cout << "Yes" << endl;return;}}cout << "No" << endl;
}
B题
知识点:枚举,排序+二分查找
题目陈述
给定一个长度为 N N N的非负整数序列 A = ( A 1 , A 2 , … , A N ) A = (A_1, A_2, \ldots, A_N) A=(A1,A2,…,AN)。找出满足以下条件的最大非负整数 x x x:
- 在 A A A中,大于或等于 x x x的元素出现次数(包括重复元素)至少为 x x x次。
约束条件
- 1 ≤ N ≤ 100 1 \leq N \leq 100 1≤N≤100
- 0 ≤ A i ≤ 10 9 0 \leq A_i \leq 10^9 0≤Ai≤109
- 所有输入值均为整数。
解法:由于题目中要找大于等于 x x x的元素个数,所以可以对序列 A A A进行升序排序。序列最大长度不超过100,所以我们可以从0到100枚举 x x x,看那一个符合条件即可。可以使用lower_bound()
函数获得大于等于 x x x的元素个数。时间复杂度为 O ( n × log n ) O(n\times \log{n}) O(n×logn)。
void solve(){int n;cin >> n;vector<int> a(n+1);for(int i=1;i<=n;i++){cin >> a[i];}sort(a.begin()+1,a.end());int x=0;for(int i=1;i<=n;i++){//枚举xint idx=lower_bound(a.begin()+1,a.end(),i)-a.begin();if(n-idx+1>=i) x=i;}cout << x << endl;
}
C题
知识点:思维,枚举
题目陈述
有一个周长为 L L L的圆,圆上放置了点 1 , 2 , … , N 1, 2, \ldots, N 1,2,…,N。对于 i = 1 , 2 , … , N − 1 i = 1, 2, \ldots, N - 1 i=1,2,…,N−1,点 i + 1 i + 1 i+1位于点 i i i沿顺时针方向 d i d_i di距离的位置上。
请找出满足以下两个条件的整数三元组 ( a , b , c ) (a, b, c) (a,b,c)( 1 ≤ a < b < c ≤ N 1 \leq a < b < c \leq N 1≤a<b<c≤N)的数量:
- 点 a a a、 b b b、 c c c都处于不同的位置。
- 以点 a a a、 b b b、 c c c为顶点的三角形是等边三角形。
约束条件
- 3 ≤ L , N ≤ 3 × 10 5 3 \leq L, N \leq 3 \times 10^5 3≤L,N≤3×105
- 0 ≤ d i < L 0 \leq d_i < L 0≤di<L
- 所有输入值均为整数。
解法:要出现等边三角形,肯定三个点中任意两个点之间的圆弧长度为 L 3 \frac{L}{3} 3L,点1的位置为0,每个点在圆周上的位置一定是一个整数,所以如果3不能整除 L L L,那么一定不存在这样的等边三角形,输出为0。否则我们计算每一个点在圆周上的位置,并且用一个 c n t cnt cnt记录每个位置点的数量,之后枚举每一个位置,计算另外两个点的位置,统计答案。这样会有一个问题就是每一个等边三角形会被重复统计,比如枚举到a点时,找到另外两个点b和c,那么枚举到b时,会找到a和c,枚举到c时,会找到a和b,那么就统计了三次,所以最后对答案除以3就是最终的答案了。时间复杂度为 O ( n ) O(n) O(n)。
void solve(){int n,L;cin >> n >> L;vector<int> d(n);for(int i=1;i<n;i++) cin >> d[i];if(L/3*3!=L){cout << 0 << endl;return;}vector<int> loca(n+1);int s=0;loca[1]=s;for(int i=1;i<n;i++){s=(s+d[i])%L;loca[i+1]=s;}vector<int> cnt(L,0);for(int i=1;i<=n;i++){cnt[loca[i]]++;}int ans=0,deg=L/3;//枚举每一个位置for(int i=0;i<L;i++){int p=(i+deg)%L,q=(i+2*deg)%L;ans+=cnt[i]*cnt[p]*cnt[q];}cout << ans/3 << endl;
}
D题
知识点:贪心
问题陈述
给定一个长度为 N N N、由小写英文字母组成的字符串 S = S 1 S 2 … S N S = S_1S_2\ldots S_N S=S1S2…SN。你需要对 S S S恰好执行一次以下操作:
- 选择 S S S的一个长度至少为 1 的连续子串,将其循环左移 1 位。也就是说,选择整数 1 ≤ ℓ ≤ r ≤ N 1 \leq \ell \leq r \leq N 1≤ℓ≤r≤N,把 S ℓ S_\ell Sℓ插入到 S S S的第 r r r个字符右侧,然后删除 S S S的第 ℓ \ell ℓ个字符。
找出执行该操作后,所有可能得到的字符串中字典序最小的那个。
你会拿到 T T T组测试用例,请分别解决每个测试用例。
约束条件
- 1 ≤ T ≤ 10 5 1 \leq T \leq 10^5 1≤T≤105
- 1 ≤ N ≤ 10 5 1 \leq N \leq 10^5 1≤N≤105
- S S S是长度为 N N N、由小写英文字母组成的字符串。
- T T T和 N N N是整数。
- 单个输入文件中所有测试用例的 N N N之和最多为 10 5 10^5 105。
解法:说到字典序,我们就会想到贪心的思想。在循环左移一次后,要使得字符串字典序是操作后最小的那个,那么肯定是找到第一个 s i > s i + 1 s_i>s_{i+1} si>si+1的位置 l l l,循环左移后 s i = s i + 1 s_i=s_{i+1} si=si+1。现在考虑字串的右端点 r r r,要将 s l s_l sl移动到后面某个位置,且满足在 l + 2 l+2 l+2到结束的某个位置 j j j使得 s j > s l s_j>s_l sj>sl,那么 r = j − 1 r=j-1 r=j−1。如果不存在大于 s l s_l sl的,那么就把 s l s_l sl放在最后。这样一定是最小的,也很容易证明。最后答案就是拼接就好,可以看到上面操作至少包含了3个字符,所以对于 n ≤ 2 n \leq 2 n≤2的情况,我们单独处理。时间复杂度为 O ( n ) O(n) O(n)。代码写的不是太好。
void solve(){int n;cin >> n;string s;cin >> s;if(n<=2){if(n==1){cout << s << endl;}else{if(s[0]>s[1]){reverse(s.begin(),s.end());}cout << s << endl;}return;}string ans;bool find=false;for(int i=0;i<n-1;i++){if(s[i]>s[i+1]){find=true;//从i+2去找第一个大于当前字符的char c=s[i];ans=s.substr(0,i);ans+=s[i+1];int r=n-1,f=0;for(int j=i+2;j<n;j++){if(s[j]>c){r=j;f=1;break;}}if(f){for(int k=i+2;k<=r-1;k++) ans+=s[k];ans+=c;for(int k=r;k<n;k++) ans+=s[k];}else{for(int k=i+2;k<n;k++) ans+=s[k];ans+=c;}break;}}if(!find) cout << s << endl;else cout << ans << endl;
}
E题
知识点:树上dp
问题陈述
给定一棵有 N N N个顶点的树。顶点编号为 1 , 2 , … , N 1, 2, \ldots, N 1,2,…,N,边编号为 1 , 2 , … , N − 1 1, 2, \ldots, N - 1 1,2,…,N−1。边 j j j双向连接顶点 u j u_j uj和 v j v_j vj,且权重为 w j w_j wj。此外,顶点 i i i被赋予一个整数 x i x_i xi。如果 x i > 0 x_i > 0 xi>0,则在顶点 i i i放置 x i x_i xi个正电子;如果 x i < 0 x_i < 0 xi<0,则在顶点 i i i放置 − x i -x_i −xi个电子;如果 x i = 0 x_i = 0 xi=0,则顶点 i i i不放置任何粒子。已知 ∑ i = 1 N x i = 0 \sum_{i=1}^N x_i = 0 ∑i=1Nxi=0。
沿边 j j j移动一个正电子或电子需要消耗能量 w j w_j wj。当正电子和电子处于同一顶点时,它们会等量相互湮灭。
请找出使所有正电子和电子湮灭所需的最小能量。
约束条件
- 2 ≤ N ≤ 10 5 2 \leq N \leq 10^5 2≤N≤105
- ∣ x i ∣ ≤ 10 4 |x_i| \leq 10^4 ∣xi∣≤104
- ∑ i = 1 N x i = 0 \sum_{i=1}^N x_i = 0 ∑i=1Nxi=0
- 1 ≤ u j < v j ≤ N 1 \leq u_j < v_j \leq N 1≤uj<vj≤N
- 0 ≤ w j ≤ 10 4 0 \leq w_j \leq 10^4 0≤wj≤104
- 给定的图是一棵树。
- 所有输入值均为整数。
这道题按照给出的示例模拟一下,可以发现这个就是一道树上dp的题,所以思路也很简单了,定义 d p [ i ] dp[i] dp[i]表示以 i i i为根的子树将 i i i的所有孩子向上移动能够获得的代价,由于正负电子相遇会抵消,所以要用一个 c n t [ i ] cnt[i] cnt[i]来记录每一个节点的的电子情况。之后就是列状态转移方程以及初始化等问题,可以在代码中看到。时间复杂度为 O ( n ) O(n) O(n)。
示例:
5
-2 -8 10 -2 2
3 5 1
1 3 5
2 5 0
3 4 6
模拟一遍即可。
const int maxn=1e5+9,mod=1e9+7;
//a数组就是每个节点的电子情况,dp[i]表示以i为根的子树的花费,cnt[i]记录节点i的电子情况
int a[maxn],dp[maxn],cnt[maxn];
//tr存树
vector<pair<int,int>> tr[maxn];void dfs(int u,int p){//初始化:开始每个节点为dp[u]=0,cnt[u]=a[u]dp[u]=0;cnt[u]=a[u];for(auto[v,w]:tr[u]){if(v==p) continue;dfs(v,u);//注意这里cnt[v]要加绝对值dp[u]+=dp[v]+w*abs(cnt[v]);//湮灭电子cnt[u]+=cnt[v];}
}
void solve(){int n;cin >> n;for(int i=1;i<=n;i++) cin >> a[i];for(int i=1;i<n;i++){int u,v,w;cin >> u >> v >> w;tr[u].push_back({v,w});tr[v].push_back({u,w});}dfs(1,0);cout << dp[1] << endl;
}