Nameless Contest(1)

22 年某模拟赛。

本人也没有参加,为了造福大众就公开题面了。

反正也没什么人看这个博客

会选择性略去一些部分分条件。

简单题

\(\text{Time Limit: 2 s}\)

\(\text{Memory Limit: 256 MB}\)

给定 \(n,x,y\)\(a,b,c,d\),求有多少个长度为 \(n\) 的正整数序列 \(\{s\}\),满足 \(\forall i \in [1,n] \cap \mathbb{Z}\)\(s_i \in [x,y]\),且 \(\sum_{i=1}^n s_i \in [a,b]\)\(\sum_{i=1}^n s_i^2 \in [c,d]\),对 \(\mathbf{998244353}\) 取模。

对于 \(40 \%\) 的数据,\(n \le 5\)

对于 \(100\%\) 的数据,\(n \le 28\)\(1 \le x \le y < 1000\)\(y-x \le 9\)\(a,b,c,d \in [1,10^9]\)

部分分就是直接暴搜 \(n\) 个数填什么。

注意到值域非常小,考虑搜索每个数用了多少次,方案数用多重集全排列算。

复杂度?

不限制物品个数的多重集组合数,方案数 \(\binom{n+m-1}{m-1}\),其中 \(m=y-x+1\)

\(10^7\) 级别的。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define num s-'0'
int read(){
	int x;
    char s;
    x=0;
    bool flag=0;
    while(!isdigit(s=getchar()))
        (s=='-')&&(flag=true);
    for(x=num;isdigit(s=getchar());x=x*10+num);
    (flag)&&(x=-x);
	return x;
}
const int maxn = 2333;
const int mod = 998244353;
int n;
int a,b,c,d,x,y;
int jc[maxn];
int ni[maxn];
int ak[maxn],ans;
int ksm(int x,int y){
	int ans = 1;
	while(y){
		if(y&1) ans *= x,ans %= mod;
		x *= x,x %= mod,y>>=1;
	}
	return ans;
}
void dfs(int q,int w,int e,int z,int fn)
{
	if(q == y){
		ak[y] = n - w;
		e += y * ak[y];
		z += y * y * ak[y];
		if(a <= e && e <= b && c <= z && z <= d){
			ans += fn * ni[ak[y]] % mod;
		}
		return;
	}
	for(int i=0;w+i<=n;++i){
		dfs(q+1,w+i,e+i*q,z+i*q*q,fn * ni[i] % mod);
	}
}
signed main()
{
	n = read();
	x = read(),y = read();
	a = read(),b = read(),c = read(),d = read();
	jc[0] = 1;
	for(int i=1;i<=n;++i) jc[i] = jc[i-1] * i % mod;
	for(int i=1;i<=n;++i) ni[i] = ksm(jc[i],mod-2);
	ni[0] = 1;
	dfs(x,0,0,0,jc[n]);
	printf("%lld\n",ans % mod);
}

套路题

\(\text{Time Limit: 2 s}\)

\(\text{Memory Limit: 512 MB}\)

给出一个拓扑序为 \(1 \sim n\) 的 DAG,对于任意 \((i,j)\)\(i<j\),满足存在一条从 \(i\)\(j\) 的边,长度为 \(a_i \times (n-i) + b_j \times j + c \times \lfloor \frac{j}{i} \rfloor\)。另外还有 \(m\) 条特殊有向边,第 \(i\) 条长度为 \(d_i\)

\(1\)\(n\) 的最短路。

对于 \(100 \%\) 的数据,\(0 \le a_i,b_i,c_i \le 10^4\)\(1 \le d_i \le 10^9\)

对于 \(\frac{1}{6}\) 的数据,\(n,m \le 300\)\(c \le 1000\)

对于 \(\frac{1}{3}\) 的数据,\(n,m \le 6000\)\(c \le 1000\)

对于另外 \(\frac{1}{3}\) 的数据,\(c=0\).

对于 \(100\%\) 的数据,\(n,m \le 2 \times 10^5\)\(0 \le c \le 10000\)

拓扑序为 \(1 \sim n\),那么设 \(f_i\) 为从 \(1\)\(i\) 的最短路,之后用 \(f_i\) 沿着从 \(i\) 连出的特殊有向边更新其他点即可。

对于前两个 subtask 就直接暴力做。

对于 \(c=0\) 的情况,\(j\) 到任何点的代价都不会改变,维护 \(g_j = f_j + b_j \times (n-j)\) 的前缀最小值即可。

考虑扩展上述做法,当 \(i-1 \rightarrow i\) 时,什么样的 \(j\) 的代价会改变呢?

当且仅当 \(\lfloor \frac{i-1}{j} \rfloor \neq \lfloor \frac{i}{j} \rfloor\),这样的 \(j\) 一定是 \(i\) 的约数。因此只需要枚举 \(i\) 的约数,暴力修改。再用线段树维护最小值。

枚举约数的过程是 \(O(\sqrt n)\) 的,使用倍数法可以在 \(O(n \log n)\) 的复杂度内求出 \(1 \sim n\) 所有数的倍数,所以复杂度大概是 \(O( n \log n + n \sigma_0 (n) \log_2 n + m)\)

常数挺小的,应该能卡过。但是 std 采用的是枚举约数。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 200005;
struct stu{
	int to,nxt,w;
}se[maxn];
int f[maxn],hd[maxn],cnt;
int a[maxn],b[maxn],c,m,n;
#define num s-'0'
void read(int &x){
    char s;
    x=0;
    bool flag=0;
    while(!isdigit(s=getchar()))
        (s=='-')&&(flag=true);
    for(x=num;isdigit(s=getchar());x=x*10+num);
    (flag)&&(x=-x);
}
void jia(int x,int y,int z){
	se[++cnt].to = y,se[cnt].nxt = hd[x],hd[x] = cnt,se[cnt].w = z;
}
int minn[maxn * 4 + 1];
void pu(int p)
{
	minn[p] = min(minn[p<<1],minn[p<<1|1]);
}
void build(int l,int r,int p)
{
	if(l == r){
		minn[p] = f[l] + a[l] * (n-l);
		return;
	}
	int mid = (l+r)>>1;
	build(l,mid,p<<1),build(mid+1,r,p<<1|1),pu(p);
}
void gai(int l,int r,int p,int pos,int v)
{
	if(l == r){
		minn[p] = v;
		return;
	}
	int mid = (l+r)>>1;
	if(pos <= mid) gai(l,mid,p<<1,pos,v);
	else gai(mid+1,r,p<<1|1,pos,v);
	pu(p);
}
signed main()
{
	read(n); read(m); read(c);
	for(int i=1;i<=n;++i) read(a[i]),read(b[i]);
	for(int i=1;i<=m;++i) {int x,y,z; read(x),read(y),read(z); jia(x,y,z);}
	for(int i=2;i<=n;++i) f[i] = 1e18;
	build(1,n,1);
	for(int i=1;i<=n;++i){
		if(i != 1){
			for(int j=1;j*j<=i;++j){
				if(i % j == 0){
					gai(1,n,1,j,f[j] + a[j]*(n-j) + c * (i/j));
					if(j*j != i && j!=1)
					gai(1,n,1,i/j,f[i/j] + a[i/j]*(n-i/j) + c * j);
				}
			}
		}
		f[i] = min(f[i],minn[1] + b[i] * i);
		for(int j=hd[i];j;j=se[j].nxt){
			int v = se[j].to;
			f[v] = min(f[v],f[i] + se[j].w);
		}
		gai(1,n,1,i,f[i] + a[i] * (n-i) + c);
	}
	printf("%lld\n",f[n]);
}

基础题

\(\text{Time Limit: 1 s}\)

\(\text{Memory Limit: 256 MB}\)

小曹和他的妹子玩取石子游戏,轮流取,小曹先手。 第一轮只能取 \(1\) 个石子,若第 \(i-1\) 轮取了 \(x\) 个石子,则第 轮只能取 \(2x\) 个或者 \(2x+1\) 个石子,谁没法再取谁就输了。 由于小曹非常调皮,想欺负他的妹子,于是他快速的计算出了假设初始有 个石子,他有多少种方案(奇数轮取上一轮的两倍还是两倍多一个),使得他的妹子无论怎么取,他都能获得胜利。 他想考考你,对于所有的 \(n \in [l,r]\),让他获得胜利的方案数的和。 两种方案不同,当且仅当存在某一轮,小曹在一种方案中取了上一轮的两倍,而在另一种方案中取了上一轮的两倍多一个,与曹队的妹子怎么取无关。

不会做。

看题解感觉还是比较人类智慧的。

咕咕咕。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int read()
{
	char c; int x = 0;
	int f = 1;
	c = getchar();
	while(c<'0'||c>'9'){
		if(c == '-') f = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9'){
		x = x * 10 + c - '0';
		c = getchar();
	}
	return x * f;
}
const int maxn = 200005;
int a[maxn];
//1 2 4 8
//1 3 6 12
//1 + 2 + 4 = 7
//1 3 7 15...
int gai[maxn];
int jisuan(int x,int qwq)
{
	int ans = 0;
	if(qwq == 1)
	{
		gai[1] = 1;
		for(int i=2;i<=18;++i) gai[i] = (1ll<<(2*i-1)) - 1;
	}
	else
	{
		gai[1] = 3;
		for(int i=2;i<=18;++i) gai[i] = (1ll<<(2*i)) - 1;
	}
	for(int j=0;j<(1<<18);++j){
		int aa = 0;
		for(int k=0;k<18;++k){
			if((j>>k)&1){
				aa += gai[k+1];
			}
		}
		if(x >= aa)
		ans += x - aa + 1;
	}
	return ans;
}
int solve(int x)
{
	if(x <= 2) return x;
	if(x <= 3) return 2;
	int ans = 2;
	for(int i=1;i<=38;++i){
		if(a[i] > x) return ans;
		int y = 1,z = 1,qwq;
		if(i % 2 == 0)
		{
			for(int j=1;j<=i;++j){
				if(j%2 == 1) z = z * 2 + 1;
				else z *= 2;
				y += z;
			}
			qwq = min(a[i+1] - 1,x) - y + 1;
			qwq --;
			if(qwq < 0) continue;
			ans += jisuan(qwq,1);
		}
		else
		{
			for(int j=1;j<=i;++j){
				if(j%2 == 1) z *= 2;
				else z *= 2,z +=1;
				y += z;
			}
			y --;
			qwq = y - a[i] + 1;
			if(x < y){
				ans -= jisuan(y-x-1,2);
			}
			qwq --;
			if(qwq < 0) continue;
			ans += jisuan(qwq,2);
		}
	}
}
signed main()
{
	int l = read(),r = read();
	int x = 1;
	a[0] = 1;
	for(int i=1;i<=38;++i){
		x *= 2; a[i] = a[i-1] + x;
	}
	int ans = solve(r) - solve(l - 1);
	printf("%lld\n",ans);
	return 0;
}

不配压轴的题

\(\text{Time Limit: 1.5 s}\)

\(\text{Memory Limit: 1 GB}\)

给定一棵有 \(n\) 节点的树,满足 \(2 \mid n\)。定义两个点配对时,它们路径上的点都会被覆盖 \(1\) 次。求将 \(n\) 个点两两配对,且每条边都至少被覆盖过 \(1\) 次的方案数。对 \(P\) 取模。

对于 \(25\%\) 的数据,\(n \le 16\)

对于 \(50\%\) 的数据,\(n \le 300\)

对于 \(75\%\) 的数据,\(n \le 5000\)

对于 \(100\%\) 的数据,\(n \le 10000\)

对于 \(100\%\) 的数据,\(P \le 1.1 \times 10^9\)

原题是 ARC101E

对于 subtask 1,直接暴搜即可,总匹配方案数是 \(10^6\) 级别的。

\(\texttt{Observation}\)

一条边 \(\Big(x,fa(x) \Big)\) 没有被覆盖的充要条件是以 \(x\) 为根的子树中,没有点和外面的点匹配。

考虑一个暴力,设 \(f(x,i)\) 为以 \(x\) 为根的子树中,有 \(i\) 个点要和外面的点匹配的方案数。 \[ f(x,i) f(y,j) \binom{i}{k} \binom{j}{k} k! \rightarrow g_{i+j-2k} \] 复杂度 \(O(n^3)\)

 

考虑正解。

把这些没有被覆盖的边看作特殊边,那么整棵树就被若干特殊边划分成了若干连通块。我们要求的是不含任何特殊边的匹配方案。

考虑容斥。钦定一个边集 \(S\),表示 \(S\) 内的边一定是特殊边。根据子集反演,容斥系数为 \((-1)^{|S|}\)

用树形背包维护连通块,设 \(f_{x,i,j}\) 为以 \(x\) 为根的子树中,\(x\) 所在连通块大小为 \(i\),其中特殊边集大小为 \(j\) 的方案数,容斥系数就是 \((-1)^j\)。转移就是讨论 \((x,y)\) 这条边要不要加入特殊边集。如果加入,那么以 \(y\) 为根的连通块闭合,在这里可以计算任意两两匹配的方案数。

然而这样复杂度过高,瓶颈在于 \(j\) 这一维。一种解决方案只记录 \(j\) 的奇偶性,不过更好的做法则是把这个容斥系数放进 DP 值里面。具体地,对 \(j\) 这一维做前缀和,设 \(f_{x,i}\) 为以 \(x\) 为根的子树,\(x\) 所在连通块大小为 \(i\) 的方案数。每有一个连通块闭合,就有一条边没有被覆盖,要多乘一个 \(-1\),对应到实现就是这部分的方案做减法。

\(2n\) 个点两两匹配的方案是 \[ \frac{\binom{2n}{n}n!}{2^n} \]

或者说 \[ h_{2n} = h_{2n-2} \times (2n-1) \]

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define uint unsigned long long
#define PII pair<int,int>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define eb emplace_back
#define SET(a,b) memset(a,b,sizeof(a))
#define CPY(a,b) memcpy(a,b,sizeof(b))
#define rep(i,j,k) for(int i=(j);i<=(k);++i)
#define per(i,j,k) for(int i=(j);i>=(k);--i)
int read() {
	int a=0, f=1; char c=getchar();
	while(!isdigit(c)) {
		if(c=='-') f=-1;
		c=getchar();
	}
	while(isdigit(c)) a=a*10+c-'0', c=getchar();
	return a*f;
}
const int N=5005, mod=1e9+7;
int n, f[N][N], g[N], sz[N], h[N];
vector<int> p[N];
void dfs(int x,int fa) {
	f[x][1]=sz[x]=1;
	for(auto y:p[x]) if(y!=fa) {
		dfs(y,x);
		for(int i=1;i<=sz[x];++i) {
			int t=0;
			for(int j=1;j<=sz[y];++j) {
				(g[i+j]+=f[x][i]*f[y][j]%mod)%=mod;
				(g[i]-=f[x][i]*f[y][j]%mod*h[j]%mod-mod)%=mod;
			}
		}
		sz[x]+=sz[y];
		for(int i=1;i<=sz[x];++i) f[x][i]=g[i], g[i]=0;
	}
}
signed main() {
	n=read();
	rep(i,2,n) {
		int x=read(), y=read();
		p[x].pb(y), p[y].pb(x);
	}
	h[2]=h[0]=1;
	for(int i=4;i<=n;i+=2) h[i]=h[i-2]*(i-1)%mod;
	dfs(1,0);
	int ans=0;
	for(int i=2;i<=n;i+=2) (ans+=f[1][i]*h[i]%mod)%=mod;
    // 闭合最后一个连通块
	printf("%lld\n",ans);
	return 0;
}


Nameless Contest(1)
https://yozora0908.github.io/2023/contest1/
作者
yozora0908
发布于
2023年6月26日
许可协议