题解 假人
Posted 欢迎,Administrator-09
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了题解 假人相关的知识,希望对你有一定的参考价值。
像我这样的小蒟蒻并不能在没有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}\\) 也没有凸性,所以无法直接分治
但是神仙题解有神仙切入点……
于是可以按余数分组合并了
复杂度 \\(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;
}
以上是关于题解 假人的主要内容,如果未能解决你的问题,请参考以下文章