2 2025 02 09 模拟赛总结

成绩表

这次不如上次不过TZK也挂飞了
![[photo/Pasted image 20250210130505.png]]

做题情况

  • T1:反悔贪心,之前没有做过这类的题,这次遇到了也没有想出正解,直接按照自己的思路乱搞,最后花费了一个多小时,过了大样例,但只有30pts(30)
  • T2:考场上基本把正解想了个七七八八,但是时间没有看好,最后也没有写完,赛后写完发现交上去0pts,这提醒我比赛时要根据时间以及自己对这个题有多少把握来选择写暴力还是想正解
  • T3:比赛时没有仔细看
  • T4:没有看,但听了讲解,好像是有点像国王游戏

赛时心态

这次的T1比上次模拟赛要难一些,而且这种反悔贪心我接触的不多,看到后也没有想出来,就硬去想,然后写,最后花了一个多小时。这是我对T1没有敬畏之心的后果,我认为T1的这100pts是我必拿的,但最后其他题的暴力也没有打上。打T2时也有点上头,我应该估算一下我连写带调大概要花多长时间,而不是有点思路就闷头写。

总结

优点

死磕精神?

缺点

做题上头不看时间,对自己的实力没有清晰的认识

题面及题解

T1

题面

原题:https://codeforces.com/gym/104128/problem/G
![[photo/Pasted image 20250210133443.png]]
样例:

6
7
1 1 1 -1 1 1 -1
4
1 0 -1 0
4
0 -1 -1 0
1
0
2
0 0
1
-1

输出:

3 2
3 1
-1
1 1
2 1
-1

题解

只需要反悔贪心,记录之前选择过的献祭事件的数量,然后在不够减的时候再反悔就可以了。

代码

AC code:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
using ll  = long long;
using namespace std;

const int N = 1e6 + 10;

int n, a[N];

int read(){
	int ret = 0, f = 1;
	char c = getchar();
	while(c < '0' || c > '9'){
		if(c == '-') f = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9'){
		ret = ret * 10 + c - '0';
		c = getchar();
	}
	return ret * f;
}
int gcd(int x, int y){
	return y == 0 ? x : gcd(y, x % y);
}

void solve(){
	n = read();
	for(int i = 1; i <= n; i++) a[i] = read();
	int cnt = 1, sum = 1;
	int t = 0;//之前自由事件中选择献祭的事件数
	for(int i = 1; i <= n; i++){
		if(a[i] == 1) sum++, cnt++;
		if(a[i] == -1) cnt--;
		if(a[i] == 0){
			if(cnt > 1) t++, cnt--;
			else cnt++, sum++;
		}
		if(cnt <= 0){
			if(t) cnt += 2, sum++, t--;
			else{
				printf("-1\n");
				return;
			}
		}
	}
	int gc = gcd(sum, cnt);
	printf("%d %d\n", sum / gc, cnt / gc);

}

int main(){
	//freopen("test.in","r",stdin);

	int T = read();
	while(T--) solve();



	return 0;
}

T2

题面

原题:P7150 [USACO20DEC] Stuck in a Rut S
![[photo/Pasted image 20250210164015.png]]
样例:

6
E 3 5
N 5 3
E 4 6
E 10 4
N 11 1
E 9 2

输出:

0
0
1
2
1
0

题解

注意题中非常重要的一句(赛时没有注意到):所有x坐标互不相同,所有y坐标互不相同,也就是说,每个生物不会被与其行进方向相同的生物阻挡。
这个题不难想到要考虑各个交点,但令人头疼的是这些可能的交点中有些并不是真正的交点,而是虚假的,因为其中一些射线被阻挡而变成了线段。所以我们要考虑这些交点的先后顺序。
很显然,一条射线被阻挡一定是被横纵坐标更小的点阻挡,这样,我们就可以将这些交点按横纵坐标为第一、二关键字对交点进行排序。
而后依次考虑每个交点,这样就能判断出哪些射线被阻挡,将其标记,后面若其再出现,这个交点便不会出现,也就不用再考虑

代码

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<vector>

using namespace std;

const int N = 1e3 + 10;
struct node{
	int x, y, id;
}c[N];
struct point{
	int x, y;
	int numx, numy;
};
vector<node> nth, est;
vector<point> p;
int n, ans[N];
int del[N];
bool cmp(point a, point b){
	return a.x == b.x ? a.y < b.y : a.x < b.x;
}
int main(){
	//freopen("test.in","r",stdin);
	scanf("%d", &n);
	for(int i = 1; i <= n; i++){
		char s[2];
		int x, y;
		scanf("%s%d%d", s, &x, &y);
		if(s[0] == 'E'){
			c[i] = {x, y, i};
			est.push_back({x, y, i});
		}else{
			c[i] = {x, y, i};
			nth.push_back({x, y, i});
		}
	}
	for(auto [x1, y1, i] : est){
		for(auto [x2, y2, j] : nth){
			if(x1 > x2 || y2 > y1) continue;
			p.push_back({x2, y1, i, j});
		}
	}
	sort(p.begin(), p.end(), cmp);
	for(auto [x, y, i, j] : p){
		if(del[i] || del[j]) continue;
		int ti = x - c[i].x;
		int tj = y - c[j].y;
		if(ti == tj) continue;
		if(ti > tj){
			del[i] = 1;
			ans[j] = ans[i] + ans[j] + 1;
		}else{
			del[j] = 1;
			ans[i] = ans[i] + ans[j] + 1;
		}
	}
	for(int i = 1; i <= n; i++){
		printf("%d\n", ans[i]);

	}
	return 0;
}

T3

题面

原题:P8973 『GROI-R1』 继续深潜,为了同一个梦想
![[photo/Pasted image 20250210165811.png]]

题解

换根dp板子?TZK好像直接秒了,但是因为细节挂了100……
定义:\(f_x\)表示在以\(x\)为根的子树中,以\(x\)为链一个端点,且\(x\)在点集中的合法的点集的个数
当x为树根时:

\[ans_x = {f_x \choose 2} - \sum_{s \in x_{son}} {2f_s + 1 \choose 2} + f_x \]

\({f_x \choose 2}\)表示在所有符合条件的链中随便选两条链拼起来的数量(也就是\(x\)不在链的一端,而是在链的中间,但一定要包含\(x\))。
但是因为有可能选到同一颗子树中的两条链,所以我们需要减去在每个子树选两个的数量。
最后的\(f_x\)表示以\(x\)为链的一端的数量。
\(f_x\)

\[f_x = \sum_{s \in x_{son}}2f_s + 1 \]

\(f_s\)乘2是因为对于节点s我们可以选也可以不选,也就是点集可能为\({…,s,x}\)或者\({…,x}\)\(+1\)表示\({s,x}\)的情况。
换根的时候要将\(f_x\)修改为没有统计子节点\(s\)之前的状态。\(\sum{2f_s + 1 \choose 2}\)用数组预处理即可
![[photo/Pasted image 20250210192731.png|500]]

代码

#include<iostream>  
#include<cstring>  
#include<algorithm>  
#include<cstdio>  
#include<cstdlib>  
#include<vector>  
  
#define ll long long  
using namespace std;  
  
const int N = 5e5 + 10;  
//m2为模mod意义下2的逆元,在模mod意义下 * mod等同于 / 2
const ll mod = 1e9 + 7, m2 = 5e8 + 4;  
ll n, f[N], ans[N], s[N];  
vector<int> e[N];  
//计算x中选2个
ll c(ll x) { return x * (x - 1 + mod) % mod * m2 % mod; }  
  
void dfs(int x, int fa){  
    for(auto to : e[x]){  
        if(to == fa) continue;  
        dfs(to, x);  
        //加上子树的贡献
        f[x] += f[to] * 2 + 1;  
        f[x] %= mod;
        
        s[x] += c(f[to] * 2 + 1);  
        s[x] %= mod;  
    }  
}  
  
//以x为根,统计方案数  
void dfs2(int x, int fa){  
    ans[x] = ((c(f[x]) - s[x] + mod) % mod + f[x]) % mod;  
  
    for(auto to : e[x]){  
        if(to == fa) continue;  
        ll fx = f[x], fto = f[to];  
        ll sum = s[to];  
        //减去之前to 加给 x 的  
        f[x] -= f[to] * 2 + 1;  
        f[x] = (f[x] + mod * 2) % mod;  
		//现在to是新的根节点,要将x对to的贡献加到f_to
        f[to] += f[x] * 2 + 1;  
        f[to] % mod;  
        //同理,以to为新的根节点,加上x对to的贡献
        s[to] += c(f[x] * 2 + 1);  
        s[to] %= mod;  
		//以to为根节点,dfs
        dfs2(to, x);  
		//还原回以x为根节点的状态  
        f[x] = fx, f[to] = fto;  
        s[to] = sum;  
    }  
}  
int main(){  
    scanf("%d", &n);  
    for(int i = 1; i < n; i++){  
        int u, v;  
        scanf("%d%d", &u, &v);  
        e[u].push_back(v);  
        e[v].push_back(u);  
    }  
    dfs(1, 0);  
    dfs2(1, 0);  
    ll ret = 0;  
    for(int i = 1; i <= n; i++) ret ^= ans[i] * i;  
    printf("%lld\n", ret);  
    return 0;  
}

T4

题面

原题:P5521 [yLOI2019] 梅深不见冬
![[photo/Pasted image 20250210225751.png]]
样例:

6
1 1 2 3 4
3 14 1 5 12 15

输出:

21 20 13 20 12 15

题解

这个题类似于国王游戏,要考虑每个节点的贡献。
对于一个节点 \(x\),设\(ans_x\)表示要在x节点上放梅花至少需要准备\(ans_x\)朵梅花,而\(x\)的权值为\(w_x\),我们的问题是要选出一个最优的序列,使得最终需要准备的梅花最少。这个问题的答案是按照\(ans_x - w_x\)不升序排列即可。我们可以类比国王游戏,考虑两个节点先后顺序对整体答案的影响,进而推广到整棵树。
尝试证明这个结论:
我们考虑两个节点\(i,j\),设\(a_i = ans_i - w_i,a_j = ans_j - w_j且a_i > a_j\)
假设先放\(i\)再放\(j\),那么我们需要准备的梅花数为\(max(ans_i,w_i + ans_j)\)(一式),同理,先放\(j\)再放\(i\)需要准备的梅花数为\(max(ans_j,w_j+ans_i)\)(二式)。
由于\(a_i = ans_i - w_i\),得\(ans_i=a_i+w_i\)\(ans_j\)同理。对一式二式分别提出\(w\)

\[\begin{gather} 一式=max(ans_i,ans_j+w_i)=max(a_i+w_i,ans_j+w_i)=w_i+max(a_i,ans_j)\\ 二式=max(ans_j,ans_i+w_j)=max(a_j+w_j,ans_i+w_j)=w_j+max(a_j,ans_i)\\ =w_j+max(a_j,a_i+w_i)=w_j+a_i+w_i \end{gather} \]

若一式的\(max\)\(a_i\),那么

\[一式=w_i+a_i<w_i+a_i+w_j=二式 \]

若一式的\(max\)\(ans_j\),那么

\[一式=w_i+ans_j=w_i+w_j+a_j<w_i+w_j+a_i=二式 \]

因此一式恒小于二式,先放\(i\)更优。
由此做数学归纳可得,按照\(ans-w\)的不升序排列后的序列最优。发现上述结论可以用于树上的每个节点,复杂度\(O(nlogn)\),注意特判没有子节点的情况,\(ans_i=w_i\)

代码

#include<iostream>  
#include<algorithm>  
#include<cstdio>  
#include<cstdlib>  
#include<cstring>  
#include<vector>  
using namespace std;  
  
const int N = 1e5 + 10;  
  
int n, ans[N], w[N];  
vector<int> e[N];  
bool cmp(int x, int y){  
    return ans[x] - w[x] > ans[y] - w[y];  
}  
  
void dfs(int u){  
	//记录最少需要多少梅花,也就是w_u + 所有(w_son)
    int sum = 0;  
    for(auto to : e[u]){  
        dfs(to);  
        sum += w[to];  
    }  
    //特判没有子节点的情况
    if(e[u].empty()) ans[u] = w[u];  
    else {  
        sum += w[u];  
	    //按照ans-w不升序排列
        sort(e[u].begin(), e[u].end(), cmp); 
        //now记录当前至少需要多少梅花 
        int now = 0;  
        for(auto v : e[u]){  
            ans[u] = max(ans[u], now + ans[v]);  
            now += w[v];  
        }  
        ans[u] = max(ans[u], sum);  
    }  
}  
int main(){  
    scanf("%d", &n);  
    for(int i = 1; i < n; i++){  
        int x; scanf("%d", &x);  
        e[x].push_back(i + 1);  
    }  
    for(int i = 1; i <= n; i++) scanf("%d", &w[i]);  
    dfs(1);  
    for(int i = 1; i <= n; i++) printf("%d ", ans[i]);  
    return 0;  
}

写于2025年2月10日

posted @ 2025-10-05 17:55  michaele  阅读(6)  评论(0)    收藏  举报