文章目录
The First Week
雄关漫道真如铁,而今迈步从头越。 ————毛泽东
一、前言
2场PTA,4场CF,2场牛客。
rating:codeforces 1225——1339 ; 牛客:1105——1257;
补题进行时
二、算法
感觉这周补了无数道暴力,穷举,构造,算法都补不起…
<1>(CF Round1000 C)
题解:
给定一棵树有n个节点,进行如下操作俩次,选定一个节点v,移除跟v相连的所有边包括v节点本身,输出此时可能的最大连通块数。
可以想象,删除一个节点v,增加的连通块数是它的度数-1,所以应该每次尽可能删除最大度数点,且尽可能保证剩下的节点的最大度数尽可能大,具体见代码。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define PII pair<int,int>
const int N = 2e5+10;
const int mod = 998244353;
int n,m;
int k;
vector<int>a[N];
bool st[N];
vector<PII>ans;
map<PII,int>mp;
bool cmp(PII x,PII y) {
return x.first>y.first;
}
void solve() {
cin>>n;
mp.clear();
ans.clear();
for(int i=1;i<=n;i++) {
st[i]=0;
a[i].clear();
}
for(int i=0;i<n-1;i++) {
int x,y;
cin>>x>>y;
a[x].push_back(y);
a[y].push_back(x);
mp[{x,y}]=1;
mp[{y,x}]=1;
}
queue<int>q;
q.push(1);
while(!q.empty()) {//用ans存所有点的度,并从大到小排序
int x=q.front();
q.pop();
st[x]=1;
ans.push_back({a[x].size(),x});
for(int i=0;i<a[x].size();i++) {
int y=a[x][i];
if(st[y]) continue;
q.push(y);
}
}
sort(ans.begin(),ans.end(),cmp);
int asn=0;
//在这里,我们可以构造一下结果,如果选择的两个点是相邻是的,ans=d1+d2-2,不相邻ans=d1+d2-1
//由两个ans可知,度最大的点必选,另一个点必定是度第一大或者第二大的点,也就是1&1,或者1&2,
//且两种情况都最好找不相邻的点,下面在找答案
for(int i=1;i<ans.size();i++) {
if(ans[i].first==ans[1].first) {
if(mp.count({ans[0].second,ans[i].second})==0) asn=max(asn,ans[i].first+ans[0].first-1);
else asn=max(asn,ans[i].first+ans[0].first-2);
}
else {
break;
}
}
if(ans[0].first==ans[1].first) {
for(int i=2;i<ans.size();i++) {
if(ans[i].first==ans[1].first) {
if(mp.count({ans[1].second,ans[i].second})==0) asn=max(asn,ans[i].first+ans[0].first-1);
else asn=max(asn,ans[i].first+ans[1].first-2);
}
else {
break;
}
}
}
cout<<asn<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
<2>(牛客训练营2 C)
这场比赛的d题和c题是相对应的,d题是找规律算答案,c题是根据答案给出可能字符串。做不出来其实是不应该的,后来看了一眼样例都已经通过百分之九十几了…
题解:
满足存在一个长度为k的连续子串a和一个长度为k的不连续子序列b,满足a=b,就可以说字符串的可爱度是k。询问是否存在长度为n,仅由小写字母构成的字符串t的可爱度恰好等于m,如果存在则输出。
如果n-m大于26,说明26个字母之后不可以存在相同的字母,显然是不可能的,所以直接n-m个字母循环即可。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
void solve() {
cin >> n >> m;
if(n-m > 26 || n== m) {
cout << "NO" << endl;
}
else { char x = 'a';
cout << "YES" << endl;
for (int i = 1; i <= n-m; i++) {
cout << x; x++;
}x--; char y = 'a';
for (int i = 1; i <= m; i++) {
cout << y;
if(y == x) y = 'a';
else y++;
}
}
cout << endl;
}
signed main() {
int t = 1;
cin >> t;
while(t--) {
solve();
}
}
<3>(牛客训练营1 M)
题解:
给定一个数组,请选择一个非空区间,将其中所有的数字都乘以2使得数组的极差最小,输出最小的极差。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5+10;
int a[N],n;
#define PII pair<int,int>
vector<PII>c;
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
c.push_back({a[i],i});
}sort(c.begin(),c.end());
int l = c[0].second,r = c[0].second;
int ma = c[n-1].first;
ma = max(ma,a[l]*2);
int x = 0;
int ans = ma-min(c[1].first,c[0].first*2);
c.push_back({3e9+10,n+1});
if(n == 1) {
cout << 0 << endl;
return ;
}
while(x < n-1) {
x++;
while(c[x].second < l) {
l--;
ma = max(ma,a[l]*2);
}
while(c[x].second > r) {
r++;
ma = max(ma,a[r]*2);
}
ans= min(ans,ma-min(c[0].first*2,c[x+1].first));
} cout << ans << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t = 1;
// cin >> t;
while(t--) solve();
}
1.线段树
//单点修改,区间查询
int a[100000];
int n;
int tree[4*1000];
void build (int p ,int l, int r) {
//编号,左右区间
if(l == r) {
tree[p] = a[l];
//只剩一个数字
return ;
}
int mid = (l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
tree[p] = tree[p*2] + tree[p*2+1];
//节点所代表的区间和
}
void change(int p,int l,int r, int pos, int num) {
if(l == r) {
tree[p] += num;
return ;
}
int mid = (l+r)/2;
if(pos <= mid) {
//只在左子树,就往它里面修改
change(p*2,l,mid,pos,num);
}
else change(p*2+1,mid+1,r,pos,num);
//只在右子树,就在它里面修改
tree[p] = tree[p*2] + tree[p*2+1];
//修改后再次更新区间和的值
}
int calc(int p,int l,int r, int x, int y) {
if(x <= l && r <= y) {
//完整包含在区间内,直接返回和
return tree[p];
}
int mid = (l+r)/2;
if(y <= mid) return calc(p*2,l,mid,x,y);
//完整的在这个区间左边
if(x > mid) return calc(p*2+1,mid+1,r,x,y);
//完整的在这个区间右边
return calc(p*2,l,mid,x,mid) + calc(p*2+1,mid+1,r,mid+1,y);
}
signed main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
build(1,1,n);
//1号节点代表的是1到n
for (int i = 1; i <= n; i++) {
int x,y,z;
cin >> x >> y >> z;
if(x == 1) {
change(1,1,n,y,z);
//第y个位置的数字加上z
}
else {
cout << calc(1,1,n,y,z) << endl;
//查询区间y到z的和
}
}
return 0;
}
//区间修改,区间查询
int a[100000];
int n;
int tree[4*1000];
//这个点的区间和
void build (int p ,int l, int r) {
//编号,左右区间
if(l == r) {
tree[p] = a[l];
//只剩一个数字
return ;
}
int mid = (l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
tree[p] = tree[p*2] + tree[p*2+1];
//节点所代表的区间和
}
int lazy[100000];
void pushdown(int p,int l,int r) {
int mid = (r+l)/2;
lazy[p*2] += lazy[p];
lazy[p*2+1] += lazy[p];
//俩个儿子的lazy赋值
tree[p*2] += lazy[p] * (mid-l+1);
tree[p*2+1] += lazy[p] * (r-(mid+1)+1);
//更改俩个儿子区间和
lazy[p] = 0;
//俩个儿子已经知道了,我就不用了
}
void change(int p,int l,int r, int x,int y,int num) {
if(x <= l && r <= y) {
tree[p] += num*(r-l+1);
lazy[p] += num;
return ;
}
if(lazy[p] != 0) {
pushdown(p,l,r);
}
int mid = (l+r)/2;
if(x <= mid) change(p*2,l,mid,x,y,num);
if(y > mid) change(p*2+1,mid+1,r,x,y,num);
tree[p] = tree[p*2] + tree[p*2+1];
//修改后再次更新区间和的值
}
int calc(int p,int l,int r, int x, int y) {
if(x <= l && r <= y) {
return tree[p];
}
if(lazy[p] != 0) {
pushdown(p,l,r);
//把标记往下传
}
int mid = (l+r)/2;
// if(y <= mid) return calc(p*2,l,mid,x,y);
// //完整的在这个区间左边
// if(x > mid) return calc(p*2+1,mid+1,r,x,y);
// //完整的在这个区间右边
// return calc(p*2,l,mid,x,mid) + calc(p*2+1,mid+1,r,mid+1,y);
int ans = 0;
if(x <= mid) ans += calc(p*2,l,mid,x,y);
if(y > mid) ans += calc(p*2+1,mid+1,r,x,y);
return ans;
}
signed main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
build(1,1,n);
//1号节点代表的是1到n
for (int i = 1; i <= n; i++) {
int x,y,z;
char op;
cin >> op;
if(op == 'C') {
cin >> x >> y >> z;
change(1,1,n,x,y,z);
//第y个位置的数字加上z
}
else {
cin >> y >> z;
cout << calc(1,1,n,y,z) << endl;
//查询区间y到z的和
}
}
return 0;
}
<1>([JSOI2008]最大数MAXNUMBER)
题解:
维护一个数组,Q L表示查询末尾L个数中的最大值,A n表示将n加上最近一次查询操作的答案对d取模后插入数列末尾。
线段树的思维,因为最多有m个节点,所以可以直接建一颗有m个节点的树,然后进行修改操作即可。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int m,d;
const int N = 2e5+5;
vector<int>a;
int tree[4*N];
void build(int p,int l,int r) {
if(l == r) {
tree[p] = 0;
return ;
}
int mid = (l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
tree[p] = max(tree[p*2],tree[p*2+1]);
}
void add(int p,int l,int r,int pos,int num) {
if(l == r) {
tree[p] = num;
return ;
}
int mid = (l+r)/2;
if(pos <= mid) add(p*2,l,mid,pos,num);
else add(p*2+1,mid+1,r,pos,num);
tree[p] = max(tree[p*2],tree[p*2+1]);
}
int calc(int p,int l,int r,int x,int y) {
if(x <= l && y <= r) return tree[p];
int mid = (l+r)/2;
if(y <= mid) return calc(p*2,l,mid,x,y);
if(x > mid) return calc(p*2+1,mid+1,r,x,y);
return max(calc(p*2,l,mid,x,mid),calc(p*2+1,mid+1,r,mid+1,y));
}
signed main() {
cin >> m >> d;
int p = 0;
int t = 0;
build(1,1,m);
for (int i = 1; i <= m; i++) {
char op;
cin >> op;
int x; cin >> x;
if(op == 'A') {
p++;
x = x+t;
x = x%d;
add(1,1,m,p,x);
}
else {
t = calc(1,1,m,p-x+1,p)%d;
cout << t << endl;
}
}
return 0;
}
三、总结
总体而言不太满意,感觉最近打比赛缺点手感。很多能做出来的题目都没做出来,补题有点拖沓。另外有关几何的算法学习没有掌握,下周过年放假,可以趁这段时间再巩固一下相关算法。
附:有些不该错的题目补了之后没放上去,写在周报里的主要是需要提升能力做出来的题目。