分治杂题 Solution Set
还是集训拉的题单。
CF gym101471D
考虑看成一个点 \((x,y)\) 表示起供时间和成本,那么对于两个点 \((x_p,y_p),(x_q,y_q)\) 满足 \(x_p < x_q\) 且 \(y_p<y_q\),那么 \(q\) 就没用了!考虑客户,差不多一样。
然后是一个选矩形两个端点让面积最大的东西。假设 \(p_i\) 的最优决策是 \(q_j\),那么 \(q_{j-1}\) 一定不是 \(p_i\) 的最优决策,那么 \(p_{i+1}\) 的最优决策不是 \(q_{j-1}\),可用反证加简单不等式技巧证明。既然满足决策单调性,向下递归做就好了。
不能考虑每次递归下去三分做,因为有平台。上面那个东西本质上也算一个三分吧!只不过比较高级。
代码里面有些阴间小细节,可以注意一下!
Motivation: 去掉无用决策。单调性分析。
/*
他决定要“格”院子里的竹子。于是他搬了一条凳子坐在院子里,面对着竹子硬想了七天,结果因为头痛而宣告失败。
DONT NEVER AROUND . //
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double DB;
typedef unsigned long long ULL;
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++)
LL read()
{
LL x=0;
char c=getchar();
while(c<'0' || c>'9') c=getchar();
while(c>='0' && c<='9') x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
return x;
}
void write(LL x)
{
if(x>9) write(x/10);
putchar(x%10+'0');
}
LL n,m;
struct node{
LL v,c,t;
inline bool operator < (node ano) const {return v<ano.v || (v==ano.v && t>ano.t);}
}a[500005],b[500005],s[1000005];
const LL inf=(1ll<<60);
inline LL calc(LL x,LL y){return b[y].c<a[x].c && b[y].v<a[x].v?-inf:(b[y].c-a[x].c)*(b[y].v-a[x].v);} // !!!!!!!!
LL ans;
void Solve(LL l,LL r,LL L,LL R)
{
if(l>r || L>R) return ;
LL mid=(l+r)>>1,p=L;
for(LL i=L+1;i<=R;++i) if(calc(mid,p)<calc(mid,i)) p=i;
ans=max(ans,calc(mid,p));
Solve(l,mid-1,L,p),Solve(mid+1,r,p,R);
}
int main(){
n=read(),m=read();
for(LL i=1;i<=n;++i) a[i].v=read(),a[i].c=read(),a[i].t=0;
for(LL i=1;i<=m;++i) b[i].v=read(),b[i].c=read(),b[i].t=1;
sort(a+1,a+1+n),sort(b+1,b+1+m);
static node stk[500005];
LL top=0;
for(LL i=1;i<=n;++i) if(!top || a[i].c<stk[top].c) stk[++top]=a[i];
n=top;
for(LL i=1;i<=n;++i) a[i]=stk[i];
top=0;
for(LL i=m;i;--i) if(!top || b[i].c>stk[top].c) stk[++top]=b[i];
m=top;
for(LL i=1;i<=m;++i) b[i]=stk[i];
reverse(b+1,b+1+m);
Solve(1,n,1,m);
write(ans);
return 0;
}
CF gym103202L
彩蛋:我在 pbb 发了一个今日牛逼题,结果被放放秒了!
首先考虑 DP。你定义 \(dp_{i,j}\) 表示处理 \(i\) 个数分了 \(j\) 段,直接转移当然是 \(O(n^2)\) 的。注意到一点是我们要求最大值,那么一段随便挑两个值出来相减的最大值刚好是最大值减最小值,那么追加一维 \(\{0,1,2\}\) 表示当前这一段一个值也没选,选了一个最大值已造成正贡献,选了一个最小值已造成负贡献。这样转移是 \(O(1)\) 的,状态比较显然。
(Flying2018 有个好博客讲 wqs 分治的,看下那个。)
然后考虑分治下去算,再追加一维 \(\{0,1,2\}\) 分别表示最左边一段和最右边一段的选值情况。先看空间复杂度用动态数组是 \(O(n \log n)\)。因为 DP 数组凸性显然,两个都是凸性合并是一个 \((\max,+)\) 卷积可以用闵可夫斯基和做。
彩蛋:
小牛天!
Motivation: DP 的转移,分治,凸性。
/*
他决定要“格”院子里的竹子。于是他搬了一条凳子坐在院子里,面对着竹子硬想了七天,结果因为头痛而宣告失败。
DONT NEVER AROUND . //
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double DB;
typedef unsigned long long ULL;
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++)
LL read()
{
LL x=0;
char c=getchar();
while(c<'0' || c>'9') c=getchar();
while(c>='0' && c<='9') x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
return x;
}
void write(LL x)
{
if(x>9) write(x/10);
putchar(x%10+'0');
}
#define len(x) (LL(x.size()))
const LL inf=1e15;
void merge(vector<LL> F,vector<LL> G,LL mvd,vector<LL>& chg) // 小牛天 Sum.
{
LL f=0,g=0;
while(f<len(F) && g<len(G))
{
if(f+g+mvd<len(chg)) chg[f+g+mvd]=max(chg[f+g+mvd],F[f]+G[g]);
if(f<len(F)-1 && (g==len(G)-1 || F[f+1]-F[f]>G[g+1]-G[g])) ++f;
else ++g;
}
}
vector<LL> dp[400005][3][3];
LL n,a[200005],cnt;
LL Solve(LL l,LL r)
{
LL u=++cnt;
for(LL i=0;i<=2;++i) for(int j=0;j<=2;++j) dp[u][i][j]=vector<LL>(r-l+2,-inf);
if(l==r)
{
dp[u][0][0][0]=dp[u][0][0][1]=0;
dp[u][1][0][0]=dp[u][0][1][0]=a[l];
dp[u][2][0][0]=dp[u][0][2][0]=-a[l];
return u;
}
LL mid=(l+r)>>1;
LL lc=Solve(l,mid),rc=Solve(mid+1,r);
for(LL p=0;p<=2;++p)
{
for(LL q=0;q<=2;++q)
{
for(LL i=0;i<=r-l+1;++i)
{
if(i<len(dp[lc][p][q])) dp[u][p][q][i]=max(dp[u][p][q][i],dp[lc][p][q][i]);
if(i<len(dp[rc][p][q])) dp[u][p][q][i]=max(dp[u][p][q][i],dp[rc][p][q][i]);
}
merge(dp[lc][p][0],dp[rc][0][q],0,dp[u][p][q]);
merge(dp[lc][p][1],dp[rc][2][q],1,dp[u][p][q]);
merge(dp[lc][p][2],dp[rc][1][q],1,dp[u][p][q]);
}
}
return u;
}
int main(){
n=read();
for(LL i=1;i<=n;++i) a[i]=read();
Solve(1,n);
for(LL i=1;i<=n;++i) write(dp[1][0][0][i]),puts("");
return 0;
}
UOJ592 新年的聚会
这个题在 WC Solution Set 里面写过,大概是说因为独立集个数级别是 \(O(\sqrt m)\) 级别的,这样找出所有独立集,然后判断两个独立集间是否有边,有边就大的拆成一半向下递归。
Motivation:复杂度分析。
CF1442D
显然最多一个数组选了一些没选完。Exchange argument 乱拼一下就好。
然后就是分治做,分治 01 背包优化总会吧!每次掏掉一个数就不处理叶子结点对应的那个点就好了。
Motivation:简单的结论观察。
/*
他决定要“格”院子里的竹子。于是他搬了一条凳子坐在院子里,面对着竹子硬想了七天,结果因为头痛而宣告失败。
DONT NEVER AROUND . //
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double DB;
typedef unsigned long long ULL;
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++)
LL read()
{
LL x=0;
char c=getchar();
while(c<'0' || c>'9') c=getchar();
while(c>='0' && c<='9') x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
return x;
}
void write(LL x)
{
if(x>9) write(x/10);
putchar(x%10+'0');
}
vector<LL> a[3005];
LL n,k,sum[3005];
LL dp[3005],len[3005];
LL ans;
void Solve(LL l,LL r)
{
if(l==r)
{
LL pre=0;
for(LL i=0;i<=min(len[l],k);++i) ans=max(ans,pre+dp[k-i]),pre+=(i==len[l]?0:a[l][i]);
return ;
}
LL mid=(l+r)>>1;
LL tmp[3005];
for(LL i=0;i<=k;++i) tmp[i]=dp[i];
for(LL i=l;i<=mid;++i) for(LL j=k;j>=len[i];--j) dp[j]=max(dp[j],dp[j-len[i]]+sum[i]);
Solve(mid+1,r);
for(LL i=0;i<=k;++i) dp[i]=tmp[i];
for(LL i=mid+1;i<=r;++i) for(LL j=k;j>=len[i];--j) dp[j]=max(dp[j],dp[j-len[i]]+sum[i]);
Solve(l,mid);
}
int main(){
n=read(),k=read();
for(LL i=1;i<=n;++i)
{
LL l=read();
len[i]=l;
a[i].resize(l);
for(LL j=0;j<l;++j) sum[i]+=(a[i][j]=read());
}
Solve(1,n);
write(ans);
return 0;
}
CF343E
一眼顶针鉴定为最小割树,最大收益就是树边和。想构造出来这样一个方案,怎么办?你从一个根开始找每次扩展最大的边满足每次扩展的边不比前面的大,找到一个可行的就好了。
当然我是抄的,什么时候抄的我也不记得了。反正能做就好了,代码就不给了!
Motivation:最小割树,基于 \(n\) 很小的脑瘫构造法。
CF1553H
建出 01trie。然后深度为 \(d\) 的层有 \(2^{k-d}\) 个本质不同的询问,最多每层有 \(2^d\) 个点。每层暴力做就是 \(O(2^kk^2)\),而且跑不满。
Motivation:很自然阿。
/*
他决定要“格”院子里的竹子。于是他搬了一条凳子坐在院子里,面对着竹子硬想了七天,结果因为头痛而宣告失败。
DONT NEVER AROUND . //
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double DB;
typedef unsigned long long ULL;
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++)
int read()
{
int x=0;
char c=getchar();
while(c<'0' || c>'9') c=getchar();
while(c>='0' && c<='9') x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
return x;
}
void write(int x)
{
if(x>9) write(x/10);
putchar(x%10+'0');
}
int n,k,a[530005];
int cnt=1;
int tr[10600005][2];
void insert(int v)
{
int rt=1;
for(int i=k-1;~i;--i)
{
int u=(v>>i)&1;
if(!tr[rt][u]) tr[rt][u]=++cnt;
rt=tr[rt][u];
}
}
int queryMin(int u,int v,int d)
{
if(d==k) return 0;
int c=(v>>(k-d-1))&1;
if(tr[u][c]) return queryMin(tr[u][c],v,d+1);
return queryMin(tr[u][c^1],v,d+1)+(1<<(k-d-1));
}
int queryMax(int u,int v,int d)
{
if(d==k) return 0;
int c=(v>>(k-d-1))&1;
if(tr[u][c^1]) return queryMax(tr[u][c^1],v,d+1)+(1<<(k-d-1));
return queryMax(tr[u][c],v,d+1);
}
int ans[21][530005];
void dfs(int u,int p)
{
if(!u) return ;
if(tr[u][0] && tr[u][1])
{
for(int i=0;i<(1<<(k-p));++i)
{
int c=(i>>(k-p-1))&1;
ans[p][i]=min(ans[p][i],queryMin(tr[u][c^1],i,p+1)-queryMax(tr[u][c],i,p+1)+(1<<(k-p-1)));
}
}
dfs(tr[u][0],p+1),dfs(tr[u][1],p+1);
}
int main(){
n=read(),k=read();
for(int i=1;i<=n;++i) a[i]=read(),insert(a[i]);
memset(ans,63,sizeof ans);
dfs(1,0);
for(int i=0;i<(1<<k);++i)
{
int ret=1e9;
for(int j=0;j<k;++j) ret=min(ret,ans[j][i&((1<<(k-j))-1)]);
write(ret),putchar(' ');
}
return 0;
}