多校联测第四场复盘总结

本场比赛心态爆炸,寄完了,但是发现了不少问题

时间安排

8:00 - 8:10:看完T1, 数学题,感觉可以搞(这场比赛爆炸的开始)
8:10 - 8:30: 打完暴力,开始思考,首先每个数的 f ( x ) f(x) f(x)不会超过50(关键性质)
8:30 - 9:00: 考虑每种数出现次数 * val, 但是好像需要容斥一下,然后就卡了
9:00 - 9:20: 尝试打表找规律
9:20 - 9:40 : 貌似找到了规律? 冲!
9:40 - 10:00 : 小样例过了? 大样例错了,寄,没时间了
10:00 - 10:30: 看T2, 发现专门给了一档只有行, 得到性质 只选行与顺序无关,只选列与顺序无关(关键性质),大胆猜测,行和列放一起也与顺序无关
10:30 - 10:50: T2码完,小样例过,大样例没过, 寄, 猜测打错了,查错…
11:00 - 11:20:T2调不出来,扔了
11:20 - 12:00:没时间了,寄, T3乱搞了随机化, T4乱搞了链和菊花,然后就寄完了

期望得分:60 + 50 + 20 + 20 = 150
实际得分: 60 + 50 + 0 + 0 = 110;
可拿的分: 100 + 100 + 40 + 50 = 290

订题情况

  • T1
  • T2
  • T3
  • T4

T1 奶牛的数学题

在这里插入图片描述
在这里插入图片描述
看题: 看到数据范围就应该想到这题是一道数学题,或者(矩阵乘法), 但显然无法写出线性递推式,所以应该往数学上思考
1.如果一个数的 f ( x ) = i f(x) = i f(x)=i, 则 x x x最小为 l c m ( 1   i ) lcm(1~i) lcm(1 i), 可得 f ( x ) < = 50 f(x) <= 50 f(x)<=50
2.将 n u m [ i ] ∗ i = ∑ i = 1 i < = 50 ∑ j = i j < = 50 n u m [ j ] num[i] * i = \sum _{i = 1}^{i<=50}\sum_{j=i}^{j<=50} num[j] num[i]i=i=1i<=50j=ij<=50num[j]
3. 所以题目转化为了对于每一个 i ∈ 50 i \in50 i50 f ( x ) ≥ i f(x) \ge i f(x)i的个数, 如何计算个数,通过1可得,若 f ( x ) ≥ i f(x) \ge i f(x)i x x x必须为 l c m { 1 , 2 , 3... ( i − 1 ) } lcm\left \{ 1,2,3...(i-1) \right \} lcm{1,2,3...(i1)}的倍数,计算即可

#include<bits/stdc++.h> //将贡献拆为单位贡献 
using namespace std;
#define LL long long 
const int MAX = 1e4 + 70;
const int MOD = 1e9 + 7;
int main() {
	freopen("math.in","r",stdin);
	freopen("math.out","w",stdout);
	int t; scanf("%d", &t);
	while(t, t--) {
		LL n; scanf("%lld", &n);
		LL SUM = 1, ans = n % MOD;
		for(int i = 2; i <= 50; i++) {
			if(SUM > n) break;
			ans = (ans + (n / SUM)) % MOD;
			SUM = SUM * i / __gcd(SUM, (LL)i); 
		}
		cout<<ans<<endl;
	}
	return 0;
}

T2路遇矩阵

![在这里插入图片描述](https://img-blog.csdnimg.cn/ebf8c6c09c734061994736bda3de3ef3.png
在这里插入图片描述

在这里插入图片描述
1.20pts的部分分是一个很好的切入点,我们发现只有行和只有列顺序无关,贪心的选一定最优

2.但是行和列放在一起,肯定不能贪心,因为如果选的次数 k k k非常大,行数小,列数恰好为 k k k,那么选 k k k列反而最优
如何解决? 枚举选多少个行,多少个列即可

//1. 删除顺序与答案统计无关,答案只与选择有关 
//2. 只删行与只删列满足贪心性质 
#include<bits/stdc++.h> 
using namespace std;
#define LL long long 
const int MAX = 1e3 + 70;
int n, m, k, p;
int a[MAX][MAX];
LL ans = 0, ANS = -1e18, H[1100000], L[1100000];
multiset<LL> h, l;
int main() {
	freopen("matrix.in","r",stdin);
	freopen("matrix.out","w",stdout);
	scanf("%d%d%d%d", &n, &m, &k, &p);
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= m; j++)
			scanf("%d", &a[i][j]);
	for(int i = 1; i <= n; i++) {
		LL sum = 0;
		for(int j = 1; j <= m; j++) sum += a[i][j];
		h.insert(sum);
	}
	for(int j = 1; j <= m; j++) {
		LL sum = 0;
		for(int i = 1; i <= n; i++) sum += a[i][j];
		l.insert(sum);
	}
	LL sum = 0;
	for(int i = 0; i <= k; i++) { //控制删i个行
		H[i] = sum;
		sum += *h.rbegin();
	 	LL NOW = *h.rbegin();
		set<LL>::iterator it = h.end();
	 	it--;
	 	h.erase(it);
	 	h.insert(NOW - (LL)p * m);
	}
	sum = 0;
	for(int i = 0; i <= k; i++) {
		L[i] = sum;
		sum += *l.rbegin();
	 	LL NOW = *l.rbegin();
		set<LL>::iterator it = l.end();
	 	it--;
	 	l.erase(it);
	 	l.insert(NOW - (1LL * p * n));
	}
	for(int i = 0; i <= k; i++) {
		ANS = max(ANS, L[i] + (H[k - i] - (1LL * p * i * (k - i))));
	}
	cout<<ANS<<endl;
	return 0;
}

T3:奶牛的括号匹配
在这里插入图片描述
在这里插入图片描述
一眼状压,但是如何设计状态呢?
首先套路的设定 f ( i ) f(i) f(i)表示选定集合 i i i所能产生的最大前缀匹配个数

但是若将 j j j加入集合 i i i必须满足当前的 i i i集合的最大方案是可以拓展的

那我们规定 f ( i ) f(i) f(i)表示 i i i 集合的最大答案,且当前的排列顺序保证可以拓展 即 ( ( ( 的个数 始终 ≥ \ge ) ) )

那么如何计算 j j j对于集合 i i i的贡献,如果集合 i i i的剩余 ( {\color{Green} {\LARGE (} } 括号的个数为 k k k, 那么 j j j所能贡献的数量即为 c n t j k cnt_jk cntjk表示第 j j j个串前缀 ) {\color{Green} {\LARGE )} } 个数为 k k k的数量位置

//状压DP, 状态设计
// f[i] 表示集合i最大答案, 转移
// 往i中添加新的字符串j, 考虑j的贡献 
// 若集合i匹配完仍剩OP个( 计算j的贡献 
#include<bits/stdc++.h>
using namespace std;
#define LL long long 
const int MAX = 22;
const int oo = 114514123;
int n, ans, cnt[MAX][410000]; //表示第i个串,出现j的个数 
int f[(1 << MAX)];
int maxx[MAX], END[410000]; //表示第j个的最大的)  和 每个串最后的值 
string s[MAX];
int num[(1 << MAX)]; //表示集合i的剩余(个数 
void prework(int id, string S) {
	int sum = 0;
	maxx[id] = -oo;
	for(int i = 0; i < S.size(); i++) {
		if(S[i] == '(') sum--;
		else sum++;
		maxx[id] = max(maxx[id], sum); 
		if(sum >= maxx[id]) cnt[id][sum]++;
	}
	END[id] = sum;
}
int main() {
	freopen("seq.in","r",stdin);
	freopen("seq.out","w",stdout);
	scanf("%d", &n);
	for(int i = 0; i < n; i++) cin>>s[i]; // 第i个串
	for(int i = 0; i < n; i++) prework(i, s[i]); //预处理第i个串的信息 
	for(int i = 0; i <= (1 << 21) - 1; i++) f[i] = -oo;
	f[0] = 0;
	for(int i = 0; i <= (1 << n) - 1; i++) { //枚举 i集合
		for(int j = 0; j < n; j++) { //选择第j个填加 
			if( (i >> j ) & 1) continue;
			if(num[i] >= maxx[j]) { //说明可以更新下一个f集合 
				f[i | (1 << j)] = max(f[i | (1 << j)], f[i] + cnt[j][num[i]]);
				num[i | (1 << j)] = num[i] - END[j];
				ans = max(ans, f[i | (1 << j)]);
			} else { //不可以更新f集合但是可以统计答案 
				ans = max(ans , f[i] + cnt[j][num[i]]);				
				num[i | (1 << j)] = num[i] - END[j];
			}	
		}
	} 
	cout<<ans<<endl;
	return 0;
} 	

T4: 润不掉了

在这里插入图片描述
在这里插入图片描述
一步一步分析

20pts:爆搜,但是不好打

40pts:我们分析,如果对于当前的根为 r o o t root root

对于 r o o t root root的若干子树,什么子树需要贡献1(被一个点看守)的答案呢?

那么应该是子树 i i i中的叶节点到 r o i ro_i roi的最小距离 ≤ \le d i s ( x , r o i ) dis(x,roi) dis(x,roi),

我们预处理叶子节点到其他点的最小距离,枚举每一个点为根,向下递归答案即可

#include<bits/stdc++.h>
using namespace std;
#define LL long long 
const int MAX = 7e4 + 70;
int n, tot, head[MAX], du[MAX], len[MAX];
vector<int> son[MAX]; // 
queue<int> q;
void BFS() {
	while(!q.empty()) {
		int now = q.front(); q.pop();
		for(auto y : son[now]) {
			if(len[y] > len[now] + 1) {
				len[y] = len[now] + 1;
				q.push(y);
			}
		}
	}
}
int dfs(int now, int fa, int dis) {
	if(len[now] <= dis) return 1;
	if(du[now] == 1) return 1;
	int sum = 0;
	for(auto y : son[now]) {
		if(y == fa) continue;
		sum = sum + dfs(y, now, dis + 1);
	} 
	return sum;
}
int main() {
	freopen("run.in","r",stdin);
	freopen("run.out","w",stdout);
	scanf("%d", &n);
	for(int i = 1; i < n; i++) {
		int u, v; scanf("%d%d", &u, &v);
		son[u].push_back(v);
		son[v].push_back(u); //存边 
		du[u] += 1; du[v] += 1;
	} 
	memset(len, 0x3f, sizeof(len));
	for(int i = 1; i <= n; i++) if(du[i] == 1) len[i] = 0, q.push(i);
	BFS() ; // 处理 
	for(int i = 1; i <= n; i++) {
		int ans = dfs(i, 0, 0) ; // 第i 个点向子树跑 
		printf("%d\n", ans);
	}
	return 0;
}

我们想,40pts没有拓展性,因为无论怎样,枚举根的操作已经限制了整个算法,如何拓展呢?

我们想对于 x x x有贡献子树的每个点都满足 g ( i ) ≤ d i s ( i , x ) ) g(i) \le dis(i,x)) g(i)dis(i,x)),

如果我们将整棵子树的贡献设为1的话,那么就是计算点对问题,显然淀粉质就可以了

下面思考树的子树的性质

∑ d u = 2 s i z − 1 \sum{du}=2siz-1 du=2siz1
变形
∑ d u − 2 s i z = 1 \sum{du}-2siz=1 du2siz=1
∑ d u − 2 = 1 \sum{du-2}=1 du2=1
所以我们将每个点的val设为 d u − 2 du -2 du2对于点 x x x的ans即为满足

g ( i ) ≤ d i s ( i , x ) g(i)\le dis(i,x) g(i)dis(i,x)的所有点的val之和
r o ro ro为分治重心,统计过 r o ro ro的点的答案
g ( i ) ≤ d i s ( r o , i ) + d i s ( r o , x ) g(i)\le dis(ro,i)+dis(ro,x) g(i)dis(ro,i)+dis(ro,x)
g ( i ) − d i s ( r o , i ) < = d i s ( r o , x ) g(i)-dis(ro,i)<=dis(ro,x) g(i)dis(ro,i)<=dis(ro,x)
树状数组维护

#include<bits/stdc++.h>
using namespace std;
#define LL long long 
const int MAX = 7e4 + 70;
int n, tot, head[MAX], du[MAX], len[MAX], val[MAX];
bool v[MAX];  
vector<int> son[MAX]; // 
queue<int> q;
int ro, maxx_tr, f[MAX], dis[MAX], siz[MAX], g[MAX], NUM; 
int tree[MAX << 1], ans[MAX]; 
int lowbit(int x) { return x & (-x); }
void add(int x, int val) { for(int i = x; i <= 2 * n; i += lowbit(i)) tree[i] += val; } 
int Find(int x) { int sum = 0; for(int i = x; i; i -= lowbit(i)) sum += tree[i]; return sum; }
void get_root(int x, int fa) { //求重心 
	siz[x] = 1, f[x] = 0;
	for(auto To : son[x]) {
		if(To == fa || v[To]) continue;
		get_root(To, x);
		siz[x] += siz[To];
		f[x] = max(f[x], siz[To]);
	}
	f[x] = max(f[x], NUM - siz[x]);
	if(f[x] < maxx_tr) {
		maxx_tr = f[x];
		ro = x;
	}
}
void Clear() { maxx_tr = n + 1; }
void get_size(int x, int fa) {
	siz[x] = 1;
	for(auto y : son[x]) {
		if(y == fa || v[y]) continue;
		get_size(y, x);
		siz[x] += siz[y];
	}
}
void calc(int x, int fa) {
	dis[x] = dis[fa] + 1;
	ans[x] = ans[x] + Find(dis[x] + n); //加上n的偏移量 
	for(auto y : son[x]) {
		if(y == fa || v[y]) continue;
		calc(y, x); 
	}
}
void change(int x, int fa, int zf) {
	add(n + g[x] - dis[x], zf * val[x]);
	for(auto y : son[x]) {
		if(y == fa || v[y]) continue;
		change(y, x, zf);
	}
}
void work(int x, int id) {
	dis[x] = 0;
	if(id == 1) add(g[x] + n, val[x]); //端点有x,x的影响 
	for(auto y : son[x]) {
		if(v[y]) continue;		
		calc(y, x); //统计当前这颗子树的答案 
		change(y, x, 1); //将影响加上去 
	}
	if(id == 1) ans[x] += Find(n);//计算端点值 
	
	for(auto y : son[x]) { //倒着做一次 
		if(v[y]) continue;
		change(y, x, -1); 
	}
	if(id == 1) add(g[x] + n, -val[x]); 
}
void slove(int x) {  //计算过x的点对之间的答案 
	v[x] = 1;
	work(x, 1); 
	reverse(son[x].begin(), son[x].end());
	work(x, 2);
	for(auto y :son[x]) {
		if(v[y]) continue;
		Clear();
		get_size(y, x); 
		NUM = siz[y];
		get_root(y, x);  //分治下去 
		slove(ro); 
	}
}
void BFS() {
	while(!q.empty()) {
		int now = q.front(); q.pop();
		for(auto y : son[now]) {
			if(g[y] > g[now] + 1) {
				g[y] = g[now] + 1;
				q.push(y);
			}
		}
	}
}
int main() {
//	freopen("run.in","r",stdin);
//	freopen("run.out","w",stdout);
	scanf("%d", &n);
	for(int i = 1; i < n; i++) {
		int u, v; scanf("%d%d", &u, &v);
		son[u].push_back(v);
		son[v].push_back(u); 
		du[u] += 1; du[v] += 1;
	} 
	memset(g, 0x3f, sizeof(g));
	for(int i = 1; i <= n; i++) if(du[i] == 1) q.push(i), g[i] = 0;  //多源最短路 
	BFS();
	for(int i = 1; i <= n; i++) val[i] = 2 - du[i]; //问题转化, 
	Clear();
	get_size(1, 0);
	NUM = siz[1];
	get_root(1, 0); 
	slove(ro);
	for(int i = 1; i <= n; i++) {
		if(du[i] == 1) printf("1\n");
		else printf("%d\n", ans[i]);
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值