目录
过了四题,第一次排名进前1000,打比赛确实可以带来进步,继续加油!
C 智乃买瓜(another version)
原题传送门
官方题解:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll ;
const int N = 1e3 + 10, mod = 1e9 + 7;
ll dp[N];
int n, m;
vector<int> ans;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
dp[0] = 1;
cin>>m;
for(int i = 1; i <= m; i ++ ){
cin>>dp[i];
}
for(int i = 1; i <= m; ++ i){
//这里的i代表的是基础质量,就是说如果存在dp[i],那么说明得出dp[m]的过程中用到了dp[i],进而就考虑dp[i]和那些数相加得到了dp[m]
while(dp[i]){
ans.push_back(i * 2);//因为质量都是偶数,所以都考虑用了一半的
for(int j = 0; j <= m; ++ j){//这里的j枚举的是质量
if(i + j <= m) dp[i + j] -= dp[j];
//此时背包的基础容量是i,如果i+j<=m就代表m可以装下i+j质量的西瓜,考虑方案数dp[i]时考虑了dp[j],所以要减去dp[j]的方案数
while(i + j <= m && dp[i + j] < 0) dp[i + j] += mod;
if(i + i + j <= m) dp[i + i + j] -= dp[j];
//此时背包的基础容量是i,如果i+i+j<=m就代表m可以装下i+i+j质量的西瓜,考虑方案数dp[i]时考虑了dp[j],所以要减去dp[j]的方案数
while(i + i + j <= m && dp[i + i + j] < 0) dp[i + i + j] += mod;
}
}
}
cout<<ans.size()<<endl;
for(int i = 0; i < ans.size(); ++ i){
cout<<ans[i]<<" ";
}
return 0;
}
E 智乃的数字积木(easy version)
原题传送门
官方题解:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10, mod = 1e9 + 7;
int n, m, k;
int col[N];
int nu[N];
char s[N];
int P, Q;
ll cal(){
for(int l = 1, r = 1; l <= n; l = r + 1, r = l){
//数组切段,将一块连续区间内的颜色相同的区域从大到小进行排序,因为一直移动的是l,所以要保证l<=n
while(r < n && col[r + 1] == col[l]) ++ r;
//col记录的是颜色,当区间右端一格的颜色和区间左端颜色相同时,就将区间右端继续拓展一格,但延伸的长度不能超过总区间
sort(s + l, s + 1 + r, [](const char&A, const char&B){
return A > B;//升序排序
});
}
ll res = 0;
for(int i = 1; i <= n; ++ i){
res = res * 10 + (s[i] - '0');
res %= mod;
}
return res;
}
void Ran(int u, int v){//将所有u染色成v
for(int i = 1; i <= n; ++ i){
if(col[i] == u) col[i] = v;
}
}
int main()
{
cin>>n>>m>>k;
scanf("%s", s + 1);
for(int i = 1; i <= n; ++ i){
cin>>col[i];
}
cout<<cal()<<endl;
while(k -- ){
cin>>P>>Q;
Ran(P, Q);
cout<<cal()<<endl;
}
return 0;
}
G 智乃的树旋转(easy version)
原题传送门
官方题解:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e3 + 10;
struct Node{
int fa;
int ch[2];
}t1[N], t2[N];
int n;
int main()
{
cin>>n;
for(int i = 1; i <= n; ++ i){
int a, b;
cin>>a>>b;
t2[i].ch[0] = a;
t2[i].ch[1] = b;
if(a) t2[a].fa = i;
if(b) t2[b].fa = i;
}
for(int i = 1; i <= n; ++ i){
int a, b;
cin>>a>>b;
t1[i].ch[0] = a;
t1[i].ch[1] = b;
if(a) t1[a].fa = i;
if(b) t1[b].fa = i;
}
for(int i = 1; i <= n; ++ i){
for(int j = 1; j <= n; ++ j){
if(t1[i].fa == j && t2[j].fa == i){
cout<<1<<endl;
cout<<i<<endl;
return 0;
}
}
}
cout<<0<<endl;
}
H 智乃的树旋转(hard version)
原题传送门
官方题解:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
struct Tree{
int fa;
int ch[2];
}a[N], t[N];
int n;
vector<int>ans;
bool vis[N];
int input(Tree* t, int n){
vector<bool> v(n + 1, true);
int x, y;
for(int i = 1; i <= n; ++ i){
cin>>x>>y;
v[x] = v[y] = false;//只有根节点没有进行过这个操作
t[x].fa = i;
t[y].fa = i;
if(x) t[i].ch[0] = x;
if(y) t[i].ch[1] = y;
}
for(int i = 1; i <= n; ++ i){
if(v[i]) return i;//最终返回的是根节点的下标
}
return 0;
}
void col(int root){
int da = t[root].fa;
int dada = t[da].fa;
int t1 = (root == t[da].ch[0]);//root为父节点的左子节点时t1为1
int t2 = (da == t[dada].ch[0]);//da为父节点的左子节点时t2为1
int h1 = t[root].ch[0 ^ t1];//h1就是树旋转时应该移动的哪个节点,如果root是父节点的左节点,ch就是root的右节点,反之相同
//下面这些代码段的含义可根据题中的动图理解
t[root].fa = dada;
t[dada].ch[1 ^ t2] = root;
t[da].ch[1 ^ t1] = h1;
t[h1].fa = da;
t[root].ch[0 ^ t1] = da;
t[da].fa = root;
}
void splay(int root){
//当对变化后的树进行节点旋转至原树状态时,下面while括号内的条件将不满足
while(!vis[t[root].fa]){//注意这里是变形之后的树
//注意这里是while,就是说root会一直被旋转,直到他被旋转到和原树相同的节点位置
//只有被旋转到和原树相同位置的节点的vis值为1
ans.push_back(root);//将这一步旋转的轴加入ans
col(root);//当root没有到原树位置时,就会一直作为旋转轴进行旋转
}
}
void dfs(int root){
splay(root);
vis[root] = true;//代表这个点已经旋转到原树位置
if(a[root].ch[0]) dfs(a[root].ch[0]);
if(a[root].ch[1]) dfs(a[root].ch[1]);
return ;
}
//代码整体的逻辑:先从原树的根节点开始深搜,旋转时对变化后的树进行旋转操作,当原根节点置于根节点之后,就将他的vis标记更新为true,表示已经到达原位置
//然后按原树的顺序继续深搜,当把原根节点的左子节点(假设为i)以自身为轴旋转至原根节点的左子节点时(此时原根节点就是现根节点),
//因为i的父节点已经打上了标记,所以不会继续进行旋转,此时i已经恢复到原树的位置,将i也打上标记,继续沿子节点进行深搜
// 这样每一个节点进行旋转操作时,都已经保证他的父节点已经旋转到原树的位置,一层一层下来,所有的节点就都能回复原位
//当旋转到原树的最后一个子节点时,整个树已经恢复原状,且每个节点旋转的次数不会超过N次,所以总体操作数不会超过N^2
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
//在进行旋转操作时,一直是按照变形前的树的顺序进行旋转,转动的是变形之后的树,这就是说
//将变形之后的树按照变形之前的树的顺序依次进行旋转,将每个节点都旋转到原树的位置
cin>>n;
vis[0] = true;//0节点不能用于搜索,作为根节点搜索时的边界
int rta = input(a, n);
int rtt = input(t, n);
dfs(rta);//深搜变形之前的树
cout<<ans.size()<<endl;
for(int i = 0; i < ans.size(); ++ i){
cout<<ans[i]<<endl;
}
return 0;
}
I 智乃的密码
原题传送门
官方题解:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int l, r, n;
int limit[4];//记录四种密码元素的最短区间左边界
int a[4][N];
char s[N];
ll ans;
int type(char c){
if(c >= 'A' && c <= 'Z') return 1;
else if(c >= 'a' && c <= 'z') return 2;
else if(c >= '0' && c <= '9') return 3;
else return 0;
}
void presum(int a[]){
for(int i = 1; i <= n; ++ i){
a[i] += a[i - 1];
}
}
int has(int a[], int l, int r){//判断l~r还有没有a[]对应的元素
return a[r] - a[l - 1] > 0;
}
int get_d(){
int temp[4];
memcpy(temp, limit, sizeof temp);//此时limit内装的是i对应的四种密码的最小包含区间
//temp[0]记录的是包含大写字母的区间的左端点
//temp[1]记录的是包含小写字母的区间的左端点
//temp[2]、temp[3]此时的含义与上同
sort(temp , temp + 4);
//排完序后
//temp[0]记录的是四个最小区间中最靠左的左端点,因为只需三种必要元素就可以得到密码,所以temp[0]战略性放弃
return temp[1];//返回可以构成密码的最小区间的左端点
}
int cal(int i){
int L = 1, R = n;
L = max(L, i - r + 1);//L此时记录的是密码长度最长的情况下最左端的端点值
R = min(R, i - l + 1);//R此时记录的是密码长度最短的情况下最左端的端点值
R = min(get_d(), R);//R此时的值更新为能构成区间的最左端的点
return R - L >= 0 ? R - L + 1 : 0;//R-L>0表示以i为右端点,向左延伸长度为r的字符串中,左边部分的无关元素数量大于0,这些无关元素的数量就是可以构成的密码数量
//如果五官元素的数量小于0,表示不能构成密码,返回构成的密码数量为0
}
//妙在利用a数组,以前缀和的形式判断这个区间内有没有密码对应的元素
int main()
{
cin>>n>>l>>r;
scanf("%s", s + 1);
for(int i = 1; i <= n; ++ i){
a[type(s[i])][i] ++ ;//标记s[i]是那种密码
}
for(int i = 0; i < 4; ++ i){
presum(a[i]);//找出每种密码的数量前缀和
}
for(int i = 1; i <= n; ++ i){
for(int j = 0; j < 4; ++ j){//分别判断这四种密码的最小存在区间的区间左端点
while(has(a[j], limit[j] + 1, i)) limit[j] ++ ;//字符串是从1开始记录的,limit数组内的值默认为0,应该+1开始
}
//分别找到i对应的包含四种密码的最短区间
ans += cal(i);//找到以i为右端点的合法的密码数量,遍历每个i,加在一起就是总的密码数量
}
cout<<ans<<endl;
return 0;
}
J 智乃的C语言模除方程
原题传送门
这个解题思路是看一个大佬的,比官方要简洁的多
大佬原题解
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
int p, l, r, L, R;
ll ans;
int J(int a, int b, int c, int d){//寻找[a ,b]和[c, d]的区间交集长度
if(c > b || d < a) return 0;
else return min(b, d) - max(a, c) + 1;
}
int qoq(int v){//查询0~v有多少个数出现过,分为两个部分,一个是0~p-1,个数=v%p,另一部分是p~v,个数是v/p*(p-1)
if(v < 0) return (int)abs(v / p) * J(-p + 1, 0, l, r) + J(v % p, 0, l, r);
else return (int)(v / p) * J(0, p - 1, l, r) + J(0, v % p, l, r);
}
int main()
{
cin>>p>>l>>r>>L>>R;
p = abs(p);
//下面代码段利用前缀和画一画就明白
if(L <= 0 && R >= 0) ans = qoq(L) + qoq(R) - qoq(0);
else if(L < 0 && R < 0) ans = qoq(L) - qoq(R + 1) ;
else ans = qoq(R) - qoq(L - 1);
cout<<ans<<endl;
return 0;
}