每日吐槽
昨晚进行了寒假训练赛02,白天还打了牛客的训练营,被暴打!!!晚上又来csoj受虐。这场发挥较上次好。rank18,A了三题。(不过第三题调试了好久,因为样例实在太少了。)终于会写二分(以前每次都想不到二分)!
题解
A.红包接龙
题意:有n-1轮,每次编号为
a
i
a_i
ai的人给编号为
a
i
+
1
a_{i+1}
ai+1的人发i的红包,让你去计算收入最多的人收获的了多少?
思路:直接模拟就可以,但注意到下标比较大,直接拿数组存不下,就需要离散化或者哈希来处理。
代码:
void solve(){
int n;
cin>>n;
map<int,int> mp;
vector<int> a(n);
for(auto &x:a){
cin>>x;
}
for(int i=1;i<n;i++){
mp[a[i-1]]-=i;
mp[a[i]]+=i;
}
int ans=0;
for(auto &[k, v]:mp){
ans=max(ans,v);
}
cout<<ans<<endl;
}
B.最后一班
题意:初始值为0,你有三种操作,对于第i次操作:
1.你可以在原有值之上加上i。
2.你可以在原有值之上减去i。
3.你可以选择不变
问你至少多少次操作可以使值变为n。
思路:通过分析,我们只需要找到最小的t满足
1
+
2
+
3
+
.
.
.
t
≥
n
1+2+3+...t\geq n
1+2+3+...t≥n即可,因为我们一定可以通过不选一些数(第三种操作),去凑出n。所以直接用高斯求和解决即可。这里我二分了一下。
void solve(){
LL n;
cin>>n;
LL l=1,r=1e9;
while(l<r){
LL mid=l+r>>1;
if((mid*(mid+1))>=2*n) r=mid;
else l=mid+1;
}
cout<<l<<endl;
}
C.勇者兔
题意:给你n个区间,每次区间上有一个宽度是区间宽度的怪兽,你可以发动一次技能,宽度为w,消灭被技能碰到的怪兽,问你消灭所有怪兽最少需要发动多少次技能?
思路:贪心,我们对所有区间按照右端点排序,每次都在[r,r+w]释放技能。每次记录上一次释放技能的区间,如果这一区间的左端点小于等于r或者这个区间是释放技能的子区间。那么都是可以被上一次释放得到技能直接一起消灭的,不用计算。
代码:
vector<PLL> a;
bool cmp(PLL a,PLL b){
if(a.ss!=b.ss) return a.ss<b.ss;
return a.ff<b.ff;
}
void solve(){
LL n,w;
cin>>n>>w;
for(int i=1;i<=n;i++){
int b,l,d,r;
cin>>b>>l>>d>>r;
a.pb({l,r});
}
sort(all(a),cmp);
LL cnt=0;
LL l=0,r=0;
for(auto x:a){
if(x.ff<=l||x.ff>=l&&x.ff<=r) continue;
cnt++;
l=x.ss;
r=x.ss+w-1;
}
cout<<cnt<<endl;
}
D.兔兔爱消除
题意:给我们一个矩阵,矩阵里面有一些数字,现在我们可以将相同的数字进行消除,每次消除可以得到
∣
x
1
−
x
2
∣
+
∣
y
1
−
y
2
∣
|x_1-x_2|+|y_1-y_2|
∣x1−x2∣+∣y1−y2∣的分数,求最大能够得到的分数。
思路:我们发现对每种类型的物品,会从 个物品开始,每次消除一个,直到剩下 1 个为止,如果把这个过程反过来:从 1 个物品开始,每次添加一个物品,直到物品个数达到 。发现这就是一个最大生成树(将Kruskal的排序改一下即可)的过程。所以初始化一下整张图,跑一遍最大生成树即可。
代码:
/Kruskal算法求最小生成树 模板
int p[N],a[55][55];
int n;
struct EAGE{
int a,b,w;
};
vector<EAGE> eages;
bool cmp(EAGE A,EAGE B){//注意这里排序需要反过来
return A.w>B.w;
}
int find(int x){//并查集查找
if(p[x]!=x) p[x]=find(p[x]);
return p[x];
}
int kruskal(){
sort(eages.begin(),eages.end(),cmp);
for(int i=0;i<=n*n;i++) p[i]=i;
int res=0,cnt=0;
for(int i=0;i<eages.size();i++){
int a=eages[i].a,b=eages[i].b,w=eages[i].w;
a=find(a),b=find(b);
if(a!=b){
p[a]=b;
res+=w;
cnt++;
}
}
if(cnt==n*n) return INF;
else return res;
}
void solve(){
cin>>n;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
cin>>a[i][j];
}
}
auto id=[&](int i,int j){//匿名函数,给每个位置一个独特的下标
return i*n+j;
};
for(int i=0;i<n;i++){//将相同的点之间进行建边,权值就是分数
for(int j=0;j<n;j++){
for(int ii=0;ii<n;ii++){
for(int jj=0;jj<n;jj++){
if(a[i][j]==a[ii][jj]&&(i!=ii||j!=jj)){
eages.push_back({id(i,j),id(ii,jj),abs(i-ii)+abs(j-jj)});
}
}
}
}
}
int ans=kruskal();
cout<<ans<<endl;
}
E.吃席兔
题意:给你一棵树,并且标记一些节点,对于每一个节点,可以选择一个已经被标记的节点,并且向他的方向走一步。每次选择的节点不能相同,最后询问对于每个节点是否能够被标记或者走到被标记的结点。
思路:用
c
n
t
i
cnt_i
cnti表示结点i的子树中有多少个结点被标记了。我们先考虑一个结点只向自己的子树内部移动,那么如果该节点能够走到被标记结点只有三种情况:1.该节点一开始就被标记了。2.有直接与该节点相连的结点被标记了。3.存在i的某个子节点j满足
c
n
t
j
≥
2
cnt_j \geq 2
cntj≥2并且j能够走到被标记的点中。(那么i一定可以到达j,然后按照j的走法接着走剩下的部分,就一定可以到达)。对于完整的问题。我们可以再次dfs一次,把父节点当做子节点进行转移。
代码:
#include<iostream>
#include<cstring>
using namespace std;
const int N = 1e5 + 10, M = 2 * N;
int h[N], e[M], ne[M], idx; //邻接表存图,无向图,开两倍空间。
int n, a[N], ok[N], cnt[N];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dfs(int u, int fa) {
//第一种情况:当前点已经被标记
if (a[u]) ok[u] = 1, cnt[u] = 1;
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (j == fa) continue;
dfs(j, u);
//第二种情况:有直接与u结点相连的被标记的结点
if (a[j]) ok[u] = 1;
//第三种情况:有一个结点j满足cnt_j>=2&&ok[j]=1。
if (cnt[j] > 1 && ok[j]) ok[u] = 1;
cnt[u] += cnt[j];
}
}
void dfs2(int u, int fa) {
//cnt[1]-cnt[u]就是倒着看的情况下的cnt[fa]
if (u != 1 && cnt[1] - cnt[u] > 1 && ok[fa]) ok[u] = 1;
if (a[fa]) ok[u] = 1;
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (j == fa) continue;
dfs2(j,u);
}
}
int main() {
cin >> n;
memset(h, -1, sizeof h);
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i < n; i++) {
int a, b;
cin >> a >> b;
add(a, b);
add(b, a);
}
dfs(1, 1);
dfs2(1, 1);
for (int i = 1; i <= n; i++) cout << ok[i] << " \n"[i == n];
return 0;
}