2023/7/8 模拟赛

2023/7/8 模拟赛 赛后总结

赛时 T4 蛮可惜的,推的其实差不多了,就是偏了一点点…… T2 忘了骗分了,有点可惜。

T1 礼物

题目描述

小T要给他的妹妹买礼物。他会不断的买,直到自己身上没有足够的钱来买任何一件礼物为止,他想知道有多少种方案符合他买礼物的方式。

我们认为两种选择方案不同当且仅当它们选取的的物品的集合不同。

因为答案可能很大,你只需要输出答案对\(10^7+7\)取模的结果即可。

思路

首先这道题有一点很重要:必须买到买不了其他物品为止。我们就从这里入手。我们考虑,如果买东西买到买不了其他物品,当且仅当当前未购买的物品中,价值最小的物品的花费要大于剩余钱数。我们直接去做背包显然不现实,因为我们无法确定是否到达最后的状态;这时,我们发现第二个性质,就是如果无法继续购买,那么价值小于(或者某些等于)当前未购买的价值最小的物品的所有物品都应该已经被买走。
这就启发我们,将物品按价值从小到大排序,然后让我们去从小到大枚举这个最后买不了的东西,这样,它之前的所有的物品都应该已经被买走,而它本身未被买走只能说明是后面的物品购买造成了影响。这时,我们对后面的物品做背包就好。我们令 \(f_i\) 表示买 \(i\) 元钱的东西的方案数,枚举后面的每个价值 \(c_j\),有 \(f_i = \sum f_{i-c_j}\)。最后,我们需要计入答案的就是剩余钱数小于当前枚举物品价值的 \(f_i\)
但是,如果每次都做一遍背包,这样总复杂度就是 \(n^2m\)虽然随机数据有人水过去了。我们考虑优化。机房另一位大佬提供了一个思路,就是从后往前枚举,这样可以不用每次重新做背包,只需要去不断更新就好。我赛场上反正没想到,但是有了一种也不失优秀,并且比较巧妙的做法。
我们发现,我们每次都清空 \(f\) 比较浪费,那有没有办法只去除没有用的贡献呢?我们考虑 dp 过程:每次,我们都是通过前面的某个 \(f_i\) 值转移到 \(f_j\) 这里,那是不是,我们每次把 \(f_j\) 减走原来 \(f_i\) 造成的贡献就行了?又因为我们是从左到右枚举进行的 dp,那么我们再从左到右枚举,每次遇到一个非零的值,就把它造成的转移贡献减走就可以了。这样做后,复杂度变成了优秀的 \(nm\)
代码:

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1050, mod = 10000007;

inline int read(){
	int x = 0, f = 1; char ch = getchar();
	while(ch<'0' || ch>'9'){if(ch == '-') f = -1; ch = getchar();}
	while(ch>='0'&&ch<='9'){x = x*10+ch-48, ch = getchar();}
	return x * f;
}

int n, m;
int f[N];
int a[N];
ll sum;
int ans;
int main(){
	n = read(), m = read();
	for(int i = 1; i<=n; ++i){
		a[i] = read();
		sum+=a[i];
	}
	if(sum<=m){//特判一,没啥好说的(
		puts("1");
		return 0;
	}
	sort(a+1, a+n+1);
	int st = 1;
	while(a[st] == 0){//特判二,注意去掉开头的 0(否则会有 bug)
		++st;
	}
	f[0] = 1;
	for(int i = st; i<=n; ++i){
		for(int j = m; j>=a[i]; --j){
			f[j] = (1ll*f[j]+f[j-a[i]])%mod;
		}
	}
	int lst = m;
	for(int i = st; i<=n; ++i){
		for(int j = 0; j<=m; ++j){//去除贡献
			if(f[j] && j+a[i]<=m){
				f[j+a[i]] = ((f[j+a[i]]-f[j])%mod+mod)%mod;
			}
		}		
		for(int j = m; j>max(m-a[i], -1); --j){//统计答案
			ans = (1ll*ans+f[j])%mod;
		}
		m-=a[i];
		if(m<0) break;
	}
	ans = (ans%mod+mod)%mod;
	printf("%lld\n", ans);
	return 0;
}

T2 课程

考场上没想出来……有人写状压+骗分拿了 70 pts,正解是随机化。
还没改出来,咕咕咕~

T3 火车

题面描述

A国有 \(n\) 个城市,城市之间有一些双向道路相连,并且城市两两之间有唯一路径。现在有火车在城市 \(a\) ,需要经过 \(m\) 个城市。火车按照以下规则行驶:每次行驶到还没有经过的城市中在 \(m\) 个城市中最靠前的。现在想知道火车经过这 \(m\) 个城市后所经过的道路数量。

参考洛谷 P3258 松鼠的新家,树链剖分+树上差分随便搞搞就行。实测 $n \log^2n $ 拿线段树整可过。
代码:

点击查看代码
#include<bits/stdc++.h>
#define ls tr<<1
#define rs tr<<1 | 1
#define ll long long
using namespace std;
const int N = 500050, M = 400040;

inline int read(){
	int x = 0, f = 1; char ch = getchar();
	while(ch<'0' || ch>'9'){if(ch == '-') f = -1; ch = getchar();}
	while(ch>='0'&&ch<='9'){x = x*10+ch-48, ch = getchar();}
	return x * f;
}

int head[N], tot;
struct node{
	int nxt, to;
}edge[N<<1];
void add(int u, int v){
	edge[++tot].nxt = head[u];
	edge[tot].to = v;
	head[u] = tot;
}
int dep[N], siz[N], dfn[N], top[N], totd, fa[N], son[N];
void dfs1(int u, int fath){
	siz[u] = 1;
	fa[u] = fath;
	dep[u] = dep[fath]+1;
	for(int i = head[u]; i; i = edge[i].nxt){
		int v = edge[i].to;
		if(v == fath) continue;
		dfs1(v, u);
		siz[u]+=siz[v];
		if(siz[son[u]]<siz[v]) son[u] = v;
	}
}
void dfs2(int u, int Top){
	top[u] = Top;
	dfn[u] = ++totd;
	if(!son[u]) return;
	dfs2(son[u], Top);
	for(int i = head[u];i ; i = edge[i].nxt){
		int v = edge[i].to;
		if(!dfn[v]) dfs2(v, v);
	}
}

bool tree[N<<2], lazy[N<<2];
void push_down(int tr){
	if(lazy[tr]){
		tree[ls] = 1;
		tree[rs] = 1;
		lazy[ls] = lazy[rs] = 1;
		lazy[tr] = 0;
	}
}
void modify(int tr, int L, int R, int lq, int rq, int val){
	if(lq<=L && R<=rq){
		tree[tr] = val;
		lazy[tr] = val;
		return;
	}
	push_down(tr);
	int mid = (L+R)>>1;
	if(lq<=mid) modify(ls, L, mid, lq, rq, val);
	if(mid<rq) modify(rs, mid+1, R, lq, rq, val);
}
bool query(int tr, int L, int R, int pos){
	if(L == R){
		return tree[tr];
	}
	push_down(tr);
	int mid = (L+R)>>1;
	if(pos <= mid) return query(ls, L, mid, pos);
	else return query(rs, mid+1, R, pos);
}
inline int LCA(int x, int y){
	while(top[x] != top[y]){
		if(dep[top[x]] < dep[top[y]]) swap(x, y);
		modify(1, 1, totd, dfn[top[x]], dfn[x], 1);
		x = fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x, y);
	modify(1, 1, totd, dfn[x], dfn[y], 1);
	return x;
}
int n, m, st;
//int city[M];
long long w[N];
ll ans = 0;
void dfs3(int u, int fath){
	for(int i = head[u]; i; i = edge[i].nxt){
		int v = edge[i].to;
		if(v == fath) continue;
		dfs3(v, u);
		w[u]+=w[v];
	}
	ans+=w[u];
}
int main(){
	n = read(), m = read(), st = read();
	for(int i = 1;i<n; ++i){
		int u = read(), v = read();
		add(u, v);
		add(v, u);
	}
	dfs1(1, 0);
	dfs2(1, 1);
	int u = st;
	for(int i = 1; i<=m; ++i){
		int v = read();
		if(query(1, 1, totd, dfn[v])) continue;
		int lca = LCA(u, v);
		w[u]+=1;
		w[v]+=1;
		w[lca]-=2;
		u = v;
	}
	dfs3(1, 0);
	printf("%lld\n", ans);
	return 0;
}

T4 计算

题面描述

给定 \(n\),求合法的 \((x_1,x_2,x_3,...,x_{2m})\) 组数。一组 \(x\) 是合法的,当且仅当

\(\forall i\in [ 1,2m ] ,x_i \in Z^+,x_i | n\)

\(\prod _{i=1}^{2m}x_i\le n^m\)

合法的 \((x_1,x_2,x_3,\dots,x_{2m})\) 可能很多,请你输出方案数 \(\mod\ 998244353\)

思路

这个题可能是最有意思的一道题。首先我们来推式子。
我们设 \(F(x) = \prod _{i=1}^{2m}x_i\)\(G(x) = \prod _{i=1}^{2m} \frac{n}{x_i}\),显然 \(F(x)G(x) = n^{2m}\),而 \(F(x) \leq n^{2m}\),移项得 $G(x) \geq n^{2m} $。
我们令 \(S_1\) 表示 $F(x) < n^{2m} $ 的方案数,\(S_2\) 表示 $F(x) = n^{2m} $ 的方案数,\(S_3\) 表示 \(F(x)> n^{2m}\) 的方案数。
由我们上面推得的性质,对于每一个 \(S_1\) 中的方案,必然对应着一个 \(S_3\) 中的方案,也就是说,\(S_1 = S_3\),这样,我们只需要处理 \(S_2\) 就行了。
我们又发现,对于每个 \(n\) 的质因数都可以分开考虑,也就是,我们去考虑怎么把质因数去填到 这 \(2m\)\(x_i\) 上。我们设 \(f_{i, j}\) 表示当前填到第 \(i\)\(x\),填了 \(j\) 个质因数 \(p\),有转移 \(f_{i, j} = \sum_{k=0}^{k \leq m} f_{i-1, j-k}\)
代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 6600, mod = 998244353;

int f[205][N];


int n, m;
int don = 1;
int s2 = 1;
void solve(int x, int num){
	memset(f, 0, sizeof(f));
	f[0][0] = 1;
	for(int i = 1; i<=m*2; ++i){
		for(int j = 0; j<=num*m; ++j){
			for(int k = 0; k<=min(num, j); ++k){
				f[i][j] = (f[i][j]+f[i-1][j-k])%mod;
			}
		}
	}
	don = (1ll*don*(num+1))%mod;
	s2 = (1ll*s2*f[m*2][num*m])%mod;
}

inline int fpow(int a, int b){
	a%=mod;
	int ret = 1;
	while(b){
		if(b & 1){
			ret = (1ll*ret*a)%mod;
		}
		b>>= 1;
		a = (1ll*a*a)%mod;
	}
	return ret;
}
signed main(){
	scanf("%d%d", &n, &m);
	int cnt;
	int nn = n;
	for(int i = 2; i*i<=nn; ++i){
		if(n%i == 0){
			cnt = 0;
			while(n%i == 0){
				n/=i;
				cnt++;
			}
			solve(i, cnt);
		}
	} 
	if(n>1){
		solve(n, 1);
	}
	int ans = fpow(don, 2*m);
	ans = (1ll*ans+s2)%mod;
	ans = ((ans%2)?(ans+mod)/2:ans/2);//注意这里会除以二,需要特判。
	printf("%d\n", ans%mod);
	return 0;
}

总结:这次一道 ds,一道乱搞(bush),两道计数,考得还算满意吧,虽然也有些地方有遗憾就是了。

posted @ 2023-07-08 21:50  霜木_Atomic  阅读(19)  评论(0编辑  收藏  举报