题解 假人

传送门

像我这样的小蒟蒻并不能在没有std的情况下做出此题,不管是从思路上还是从代码实现上

  • 对于一类形如 \(f_{i, j}=\max\limits_{1\leqslant p\leqslant min(k_i, j)}f_{i-1, j-p}+a_{i, p}\),且保证 \(a_{i, p}\)\(p\) 单调不升/降问题的解法:
    发现只考虑 \(i\) 本身的贡献时dp值是凸的(此时就是 \(a_{i, p}\)
    然后发现 \(i\)\(i-1\) 间其实是独立的(因为 \(p\)\(j\) 无关,可以理解为把 \(j\) 拆成几个 \(p\) 分配给不同的 \(i\)
    于是可以分治,令 \(f_{l, r, j}\) 为给 \([l, r]\) 分配了 \(j\) 的最大贡献
    合并的话因为原来的转移实际上是一个不断取max的过程,决策点形成一个凸包
    可以贪心地按每个凸包上两个决策点之间的增量归并排序
    这个东西实际上是两个点集的闵可夫斯基和,这里的点集是原凸包
    实际意义的话,大概可以理解为原凸包做闵可夫斯基和得到的图形的边界是原DP所有合并的极值
    所以新凸包的边界上的点就是新的决策点了

回到这个题,发现 \(a_{i, p}\) 没有凸性,所以 \(f_{i, i, j}\) 也没有凸性,所以无法直接分治
但是神仙题解有神仙切入点……
image
于是可以按余数分组合并了
复杂度 \(O(nk^2\frac{n}{k}logn)=O(nklogn)\)

然后代码实现上有个花了我巨久的细节:这题把范围从 \([1, 5]\) 调到了 \([0, 4]\),而我在凸包上维护长度的时候还在设法让下标从1开始
于是呢?炸,炸大个的

然后回来考虑一下题解切入用的结论
还有没有类似的满足 \(sum=2*k\),则一定能拆出 \(sum=k\) 的子集的数呢?见dalao博客

跟std基本一样的 Code:
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 100010
#define ll long long
#define pb push_back
#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 n;
int siz[N], len[N];
ll a[N][6];

namespace task1{
	ll dp[1010][5010];
	void solve() {
		int sum=0;
		for (int i=1; i<=n; ++i) {
			for (int j=i-1; j<=sum; ++j) {
				for (int k=1; k<=siz[i]; ++k) {
					dp[i][j+k]=max(dp[i][j+k], dp[i-1][j]+a[i][k]);
				}
			}
			sum+=siz[i];
		}
		for (int i=n; i<=sum; ++i) printf("%lld ", dp[n][i]);
		printf("\n");
		exit(0);
	}
}

namespace task2{
	priority_queue<ll> q;
	void solve() {
		ll ans=0;
		for (int i=1; i<=n; ++i) {
			if (siz[i]==2) q.push(a[i][2]-a[i][1]);
			ans+=a[i][1];
		}
		printf("%lld ", ans);
		while (q.size()) {
			ans+=q.top(); q.pop();
			printf("%lld ", ans);
		}
		printf("\n");
		exit(0);
	}
}

namespace task{
	struct dat{
		vector<int> v[12];
		inline vector<int>& operator [] (int t) {return v[t];}
		void put() {
			cout<<"---dat---"<<endl;
			for (int i=0; i<12; ++i) {cout<<i<<": "; for (auto it:v[i]) cout<<it<<' '; cout<<endl;}
		}
	};
	void merge(dat& a, dat& b, dat& ans) {
		cout<<"merge: "<<endl;
		a.put(); b.put(); ans.put();
		for (int i=0; i<12; ++i) if (a[i].size()) {
			for (int j=0; j<12; ++j) if (b[j].size()) {
				int dlt=(i+j>=12), k=(i+j)%12, x=0, y=0;
				while (1) {
					ans[k][x+y+dlt]=max(ans[k][x+y+dlt], a[i][x]+b[j][y]);
					if (x==a[i].size()-1 && y==b[j].size()-1) break;
					else if (x==a[i].size()-1) ++y;
					else if (y==b[j].size()-1) ++x;
					else ++((a[i][x+1]-a[i][x]>b[j][y+1]-b[j][y])?x:y);
				}
			}
		}
		cout<<"return: "<<endl;
		ans.put();
	}
	dat solve(int l, int r) {
		// cout<<"solve: "<<l<<' '<<r<<endl;
		dat ans;
		if (l==r) {
			for (int i=1; i<=siz[l]; ++i) ans[(i-1)%12].pb(a[l][i]);
			return ans;
		}
		int tem=len[r]-len[l-1], mid=(l+r)>>1;
		// for (int i=0; i<tem+r-l; ++i) ans[i%12].pb(-1);
		for (int i=0; i<12; ++i)
			for (int j=i; j<=tem; j+=12) ans[i].pb(-1);
		dat a=solve(l, mid), b=solve(mid+1, r);
		merge(a, b, ans);
		return ans;
	}
	void enter() {
		dat ans=solve(1, n);
		// cout<<"ans: "<<endl;
		// ans.put();
		for (int i=0; i<=len[n]; ++i) printf("%lld%c", ans[i%12][i/12], " \n"[i==len[n]]);
		exit(0);
	}
}

signed main()
{
	freopen("fake.in", "r", stdin);
	freopen("fake.out", "w", stdout);
	
	n=read();
	bool all_one=1, leq2=1;
	for (int i=1; i<=n; ++i) {
		siz[i]=read();
		len[i]=len[i-1]+siz[i]-1;
		if (siz[i]!=1) all_one=0;
		if (siz[i]>2) leq2=0;	
		for (int j=1; j<=siz[i]; ++j) a[i][j]=read();
	}
	// if (all_one || leq2) task2::solve();
	// else task1::solve();
	task::enter();
	
	return 0;
}
posted @ 2021-10-08 09:52  Administrator-09  阅读(59)  评论(0编辑  收藏  举报