题解 粉丝

传送门

考场上真的尽力了,但两张草稿纸只换来一个 \(n^3\) DP系列

  • 类似本题可以通过把两个不同的做法拼起来以降低复杂度的思路?

首先根本不用枚举轮数,如果只记 \(f_{i, j}\) 为当前选数下限为 \(I\) ,总和为 \(j\) 的方案数,转移枚举这个数选的个数的话,前缀和优化就是 \(n^2\) 的
然后考虑另一个DP
如果令 \(g_{i, j}\) 表示选了 \(i\) 个数,总和为 \(j\) 的方案数,那这个东西如何转移呢?
显然转移必需保证选的数单调,要不然会重
所以考虑转移时插入一个大小为零的数或令之前选过的数全部 \(+1\) ,就保证了高度单调递减
(?)这里我说的对吗 神仙思路,想不到
所以转移是 \(g_{i, j}=g_{i-1, j}+g_{i, j-i}\)
这两个DP都是 \(n^2\) 的,但发现枚举的东西不一样
如果让f只处理限制小于 \(\sqrt n\) 的情况,那剩下的数都大于 \(\sqrt n\) ,最多被分成 \(\sqrt n\) 个,总复杂度就变成了 \(O(n\sqrt n)\)
合并就枚举值域断点合并即可

Code:

#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 100010
#define ll long long 
#define fir first
#define sec second 
#define make make_pair
//#define int long long 

char buf[1<<21], *p1=buf, *p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
inline int read() {
	int ans=0, f=1; char c=getchar();
	while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
	while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
	return ans*f;
}

int x, y, n;
ll p;
inline void md(ll& a, ll b) {a+=b; a=a>=p?a-p:a;}

namespace force{
	ll ans;
	ll dfs(int tot, int lst) {
		//cout<<"dfs "<<tot<<' '<<lst<<endl;
		if (tot>n) return 0;
		if (tot==n) return 1;
		ll tem=0;
		for (int i=lst; i<=n; ++i) 
			tem = (tem+dfs(tot+i, i))%p;
		return tem;
	}
	void solve() {
		for (int i=x; i<=y; ++i) {
			ans = (ans+dfs(i, i))%p;
		}
		printf("%lld\n", ans%p);
		exit(0);
	}
}

namespace task1{
	ll dp[500][500][500], ans;
	void solve() {
		if (y==n) ++ans;
		for (int i=x; i<=y; ++i) dp[1][i][i]=1;
		for (int i=2; i<=n; ++i)
			for (int j=1; j<=n; ++j)
				for (int k=j; k<=n; ++k) {
					for (int h=1; h<=j; ++h)
						dp[i][j][k] = (dp[i][j][k]+dp[i-1][h][k-j])%p;
					if (k==n) ans=(ans+dp[i][j][k])%p;
				}
		printf("%lld\n", ans);
		exit(0);
	}
}

namespace task2{
	ll ans;
	struct pair_hash{inline size_t operator () (pair<int, int> p) const {return hash<int>()(p.fir*13131+p.sec);}};
	unordered_map<pair<int, int>, ll, pair_hash> mp{5000, pair_hash()};
	ll dfs(int tot, int lst) {
		//cout<<"dfs "<<tot<<' '<<lst<<endl;
		if (tot>n) return 0;
		if (tot==n) return 1;
		if (mp.find(make(tot, lst))!=mp.end()) return mp[make(tot, lst)];
		ll tem=0;
		for (int i=lst; i<=n; ++i) 
			tem = (tem+dfs(tot+i, i))%p;
		mp[make(tot, lst)]=tem;
		return tem;
	}
	void solve() {
		for (int i=x; i<=y; ++i)
			ans = (ans+dfs(i, i))%p;
		printf("%lld\n", ans%p);
		exit(0);
	}
}

namespace task3{
	ll f[320][N], sum[N], g[320][N], ans, sum2[N];
	void solve() {
		int lim=sqrt(n);
		for (int i=x; i<=lim; ++i) {
			if (i<=y) f[i][i]=1;
			for (int j=i; j<=n; ++j) {
				f[i][j] = (f[i][j]+sum[j-i])%p;
				sum[j] = (sum[j]+f[i][j])%p;
			}
		}
		g[0][0]=1;
		for (int i=1; i<=lim; ++i)
			for (int j=max(x-lim, 1); i*lim+j<=n; ++j) if (j>=i) {
				cout<<i<<' '<<j<<endl;
				g[i][j] = (g[i][j]+g[i-1][j-max(x-lim, 1)]+g[i][j-i])%p;
				sum2[j+i*lim] = (sum2[j+i*lim]+g[i][j])%p;
			}
		cout<<"sum2: "; for (int i=1; i<=n-lim; ++i) cout<<sum2[i]<<' '; cout<<endl;
		memset(g, 0, sizeof(g));
		g[0][0]=1;
		for (int i=1; i<=lim; ++i)
			for (int j=max(y+1-lim, 1); i*lim+j<=n; ++j) if (j>=i) {
				g[i][j] = (g[i][j]+g[i-1][j-max(y+1-lim, 1)]+g[i][j-i])%p;
				sum2[j+i*lim] = (sum2[j+i*lim]-g[i][j])%p;
			}
		cout<<"sum: "; for (int i=1; i<=n; ++i) cout<<sum[i]<<' '; cout<<endl;
		cout<<"sum2: "; for (int i=1; i<=n; ++i) cout<<sum2[i]<<' '; cout<<endl;
		for (int i=0; i<=n; ++i) 
			ans = (ans+sum[i]*sum2[n-i]%p)%p;
		printf("%lld\n", (ans%p+p)%p);
		exit(0);
	}
}

namespace task{
	ll f[N], g[N], sum[N];
	ll calc(int x) {
		memset(f, 0, sizeof(f));
		memset(g, 0, sizeof(g));
		memset(sum, 0, sizeof(sum));
		int lim=max(x, (int)sqrt(n));
		f[0]=1;
		for (int i=x; i<lim; ++i)
			for (int j=i; j<=n; ++j)
				md(f[j], f[j-i]);
		g[0]=1; sum[0]=1;
		for (int i=1,t; i<=n/lim; ++i) {
			t=i*lim;
			for (int j=i; t+j<=n; ++j) md(g[j], g[j-i]);
			for (int j=0; t+j<=n; ++j) md(sum[t+j], g[j]);
		}
		ll ans=0;
		for (int i=0; i<=n; ++i) md(ans, f[i]*sum[n-i]%p);
		return ans;
	}
	void solve() {
		ll ans=calc(x)-calc(y+1);
		printf("%lld\n", (ans%p+p)%p);
		exit(0);
	}
}

signed main()
{
	x=read(); y=read(); n=read(); p=read();
	//force::solve();
	//task1::solve();
	//task2::solve();
	task::solve();
	
	return 0;
}
上一篇:Python编程基础 第五章 编程练习 编写程序实现以下功能:使用选择排序算法将列表中的元素按照升序方式排列。


下一篇:python 迷宫求路(递归方法)