2023.8.17模拟赛

赛时记录

lb说不按难度排序 于是先遍历四道题 结果发现都不会

然后回来看T1 看到菊花图的部分分 想到直接组合数 然后再想想就秒了
结果在求方案数被卡住了 然后lb说忘取模数了 那没事了
但是感觉复杂度是平方 后来一想发现是线性

结果发现为啥我运行4.5s读入用了4s 心态有点崩了

然后就在T2和T3之间选一道做 实际感觉T2更有思路 但是明显T3应该更好切入
然后大概想了一会就也出来了 然后胡了个dfs的写法上去 反正大样例是过的
ps.实际上那个写法有割点就寄了 但是恰巧老师造数据的方法会使得数据不存在割点

然后看到T4发现不就是高消吗 但是我不会啊 尝试手动高消
然后判了inf种无解情况 码了一个多点 结果最后还是写挂了

大概离考试结束还剩50min 然后大概还剩40min的时候想出了 O(nnlogn) 的做法 脑子一热就莽线段树去了
10min码完挂了结果发现是主函数没写init 改完大样例就过了

然后也没什么能干的了

预计分数:100 + 100 + 100 + 0
实际分数:60 + 50 + 100 + 0


赛后总结

其实挂的很惨 只是因为大家好像都挂的挺惨的所以显得还好

好的方面是有的 比如快结束的时候能10min码完线段树+离线 能感觉到码力确实提升了不少

具体怎么挂的会在解析中写到

还是吧 注意钓鱼题 还有图论题注意重边


补题

大概都是考场上的思考过程

T1

结合菊花图的部分分 发现对于一个最大为 n 的菊花图 大小为 n1 的菊花图显然就等价于从 n 条边中选 n1 条边的方案数

然后我们考虑把图中的菊花图分离出来 发现实际上对于每个点而言它连向很多点就是一个最大菊花图

所以我们直接记录每个点的度数 然后暴力枚举对于每个度数的答案就好了

复杂度看起来是 O(n2) 的 实际上是 O(i=1ndegi)
考虑每条边会给两个点度数+1 所以实际上这个复杂度就是线性

考试的时候没有考虑重边 心想不是最多就往外连 n 个点吗然后组合数啥的都是处理到 n 挂了40

code:

#include <bits/stdc++.h>
#define ll long long
using namespace std;

const int N = 2e6 + 0721;
const int mod = 1e9 + 7;
int deg[N];
ll inv[N], fac[N];
ll cnt[N];
int n, m;

void init() {
	fac[0] = fac[1] = 1;
	inv[0] = inv[1] = 1;
	for (int i = 2; i <= max(n, m); ++i) {
//		cout <<i << " ";
		fac[i] = fac[i - 1] * i % mod;
		inv[i] = (mod - mod / i * inv[mod % i] % mod) % mod;
	}
	for (int i = 2; i <= max(n, m); ++i) inv[i] = inv[i - 1] * inv[i] % mod;
}

ll C(int m, int n) {
//	cout << n << " " << m << " " << fac[n] * inv[m] % mod * inv[n - m] % mod << "\n";
	return fac[n] * inv[m] % mod * inv[n - m] % mod;
}

int main() {
	freopen("mondstadt.in", "r", stdin);
	freopen("mondstadt.out", "w", stdout);
	
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; ++i) {
		int x, y;
		scanf("%d%d", &x, &y);
		++deg[x];
		++deg[y];
	}
	
	init();
//	cout << "init complete!\n";
	
	for (int i = 1; i <= n; ++i) {
//		cout << i << " ";
		for (int j = 2; j <= deg[i]; ++j) cnt[j] = (cnt[j] + C(j, deg[i])) % mod;
	}
	
	ll ans = 0;
	for (int i = 2; i <= n - 1; ++i) ans ^= cnt[i];
	
	printf("%lld", ans);
	
	return 0;
}
/*
4 6
1 2
1 3
1 4
2 3
2 4
3 4
*/

T2

做的很曲折的一道题

刚开始看见不带修直接离线 然后发现每次新加进来一个数对于区间内答案的影响就是这个区间内能与它匹配的点数
然后想到莫队
但是找与它匹配的点数是 O(n) 的 也就是说端点移动的复杂度就是 O(n) 的 复杂度就炸了

然后发现我可以预处理出 2105 以内的完全平方数 然后对于每个数枚举与之匹配的点就是 O(n)
这样移动就是 O(n) 结果发现询问数是 105 左端点移动复杂度会炸掉

无果 考虑枚举左端点

发现如果我们设 fi 表示从 i 开始到当前左端点区间内与 ai 匹配的点的数量
那么当左端点移动到 l 的时候 对于所有 [l,r] 的询问 答案就是 i=lrfi

我们考虑每次加入左端点对于它右边的点的影响 发现它会让右边所有能与它匹配的点的答案+1

所以我们枚举能与它匹配的点 判断在不在它右边即可

分析复杂度 每次查询是 O(logn)
每次移动左端点时 要枚举 O(n) 个数 对于每个数的修改是 O(logn)
总复杂度就是 O(nnlogn+qlogn)

那么就是一个单点修改区间求和 考场上脑子一热直接拍的线段树 然后就被卡常了

image

改成 BIT 就过了

code:

#include <bits/stdc++.h>
#define ll long long
#define ls (k << 1)
#define rs (k << 1 | 1)
#define mid (l + r >> 1)
using namespace std;

const int N = 1e5 + 0721;
int a[N], loc[N];
vector<int> v[N];
int n, m;
ll ans[N];

struct tree {
	ll tr[N];
	
	inline int lowbit(int x) {
		return x & (-x);
	}
	
	void update(int x, int val) {
		while (x <= n) {
			tr[x] += val;
			x += lowbit(x);
		}
	}
	
	ll query(int x) {
		ll ret = 0;
		while (x) {
			ret += tr[x];
			x -= lowbit(x);
		}
		return ret;
	}
	
	ll ask(int l, int r) {
		return query(r) - query(l - 1);
	}
} BIT;

struct node {
	int l, r, id;
	friend bool operator<(node x, node y) {
		return x.l > y.l;
	}
} q[N];

void init() {
	for (int i = 1; i <= n * 2; ++i) {
		int k = sqrt(i);
		if (k * k == i) v[0].push_back(i);
	}
	for (int i = 1; i <= n; ++i) {
		for (int j = 0; j < v[0].size(); ++j) {
			int x = v[0][j];
			if (x - i <= 0) continue;
			v[i].push_back(x - i);
//			cout << i << " " << x - i << "\n";
		}
	}
} 

void solve() {
	int prs = 1;
	for (int i = n; i >= 1; --i) {
		for (int k = 0; k < v[a[i]].size(); ++k) {
			int j = v[a[i]][k];
			if (loc[j] > i) BIT.update(loc[j], 1);
		}
		while (q[prs].l == i && prs <= m) {
			ans[q[prs].id] = BIT.ask(q[prs].l, q[prs].r);
			++prs;
		}
	}
}

int main() {
	freopen("liyue.in", "r", stdin);
	freopen("liyue.out", "w", stdout);
	
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) {
		scanf("%d", &a[i]);
		loc[a[i]] = i;
	}
	scanf("%d", &m);
	for (int i = 1; i <= m; ++i) {
		scanf("%d%d", &q[i].l, &q[i].r);
		q[i].id = i;
	}
	
	init();
	sort(q + 1, q + 1 + m);
	solve();
	
	for (int i = 1; i <= m; ++i) printf("%lld\n", ans[i]);
	
	return 0;
}
/*
8
5 7 4 1 8 6 2 3
10
4 5
2 6
1 8
2 7
4 8
3 8
4 7
1 5
2 5
3 7
*/

T3

看起来就要考虑仙人掌的特殊性质

然后通过观察他给的仙人掌的示意图发现可以把仙人掌看作是树上加一些边
然后结合样例 我们发现对于树上的一个环 我们最优的方案一定是首先不用持续时间最小那个
然后等次小的用完了用省下来那个给它续上
这样一个环上的最长持续时间就是 min+,

对于不在环上的边 也没有能给它续命的方式 与答案直接取 min 即可

然后进一步观察样例可知 对于重边 我们把他们的边权加一起 即用完一条再续命是最优的

然后胡了个搜索 实际上因为很幸运老师构造数据的方法恰好不会出现多个环交与同一点的情况就过了

正确做法应该是拿 tarjan 求割边

代码贴的赛时的:

#include <bits/stdc++.h>
#define ll long long
using namespace std;

const int N = 1e6 + 0721;
const ll inf = 0x3f3f3f3f3f3f3f3f;
int head[N], nxt[N << 2], to[N << 2], cnt;
ll val[N << 2];
int st[N << 2];
bool vis[N], calc[N];
int n, m, tot, top;
ll ans = inf;

struct node {
	int u, v;
	ll w;
	friend bool operator<(node x, node y) {
		if (x.u != y.u) return x.u < y.u;
		else if (x.v != y.v) return x.v < y.v;
		else return x.w < y.w;
	}
} edge[N << 2];

inline void add_edge(int x, int y, ll z) {
	to[++cnt] = y;
	nxt[cnt] = head[x];
	head[x] = cnt;
	val[cnt] = z;
}

ll cal(int i) {
	ll minn = val[i], minnn = inf, minnnn = inf;
	int prs = top;
	while (prs) {
		if (to[prs] == to[i]) break;
		int v = val[prs];
		if (v < minn) {
			minnnn = minnn;
			minnn = minn;
			minn = v;
		} else if (v < minnn) {
			minnnn = minnn;
			minnn = v;
		} else if (v < minnnn)
			minnnn = v;
		calc[prs] = 1;
		--prs;
	}
	return min(minn + minnn, minnnn);
}

void dfs(int x, int fa) {
	vis[x] = 1;
	for (int i = head[x]; i; i = nxt[i]) {
		int y = to[i];
		if (y == fa) continue;
		if (vis[y]) {
			ans = min(ans, cal(i));
			continue;
		}
		
		st[++top] = i;
		dfs(y, x);
		if (!calc[top]) ans = min(ans, val[st[top]]);
		--top;
	}
}

int main() {
	freopen("inazuma.in", "r", stdin);
	freopen("inazuma.out", "w", stdout);
	
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; ++i) {
		int x, y;
		ll z;
		scanf("%d%d%lld", &x, &y, &z);
		edge[++tot] = (node){ x, y, z };
		edge[++tot] = (node){ y, x, z };
	}	
	
	sort(edge + 1, edge + 1 + tot);
	
	for (int i = 1; i <= tot; ) {
		int prs = i + 1;
		while (edge[prs].u == edge[i].u && edge[prs].v == edge[i].v) {
			edge[i].w += edge[prs].w;
			++prs;
//			cout << prs << "\n";
		}
		add_edge(edge[i].u, edge[i].v, edge[i].w);
		i = prs;
	}
	
	dfs(1, 0);
	printf("%lld", ans);
	
	return 0;
}
/*
5 6
1 2 3
1 2 2
2 3 3
2 4 5
2 5 5
3 4 2
*/

T4

题解没看懂 别人说的数学做法也没看懂
既然是原神模拟赛 那么我只能说

前面的区域 以后再来探索吧!

posted @   Steven24  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
点击右上角即可分享
微信分享提示