[NOI2024] 百万富翁
神仙题目。
写完后看题解发现没有一个用试图优化 DP 的,全都是注意力惊人。
题目链接: [NOI2024] 百万富翁 。
题目描述:
小 W 有一个长度为 n 的序列 w 。你可以经行多组询问。每一组询问里包含若干个普通询问。每一个普通询问为形如 (i,j) 的二元组,小 W 会对于每一组询问,一次性告诉你这一组里每一个普通询问 (i,j) 中 wi 和 wj 谁要更大。
你需要在不超过 T 组询问,一共不超过 S 个普通询问内找出数组的最大值。
有两个子任务:
| 子任务编号 |
T |
S |
n |
| 1 |
1 |
499500 |
1000 |
| 2 |
8 |
1099944 |
1000000 |
交互库是自适应的。
子任务一直接两两枚举即可。
交互库自适应这一点可以告诉我们,对于一组点,如果我只能询问一组的话,一定需要两两枚举询问。因为如果我们哪怕少询问了一个点,交互库也可以直接将那个点作为最优解。
所以我们必须把这些点分开,分多组求解。
也就是说,我们的策略应该是,先将这些点随意的划分为若干组,每组通过两两枚举求出一个最大值,然后让所有的最大值递归这一过程。
那么我们的问题就有两部分:如何划分最优,以及每次应该划分多少组。
先看第一个问题,如何划分。
由二次函数的性质,有 (a+b)2>a2+b2 ,所以说,为了消耗更少,我们应该尽量平均的划分。
具体来说,如果我们要将 n 个点划分为 m 组,设最小代价为 G(n,m) ,且将 k 个点划分为一组的代价为 F(k) ,那么就有:
G(n,m)=(m−nmodm)F(⌊mn⌋)+(nmodm)F(1+⌊mn⌋)
于是这一部分搞定了。
然后就是第二个问题,每次划分多少组。
你可以直接模拟退火或者搜索。
这里没有什么好方法,你可以直接 DP 做。
设 fi,j 为第 i 次分组询问,且这次分完前剩下 j 组的情况下的最小代价。
转移挺显然的,边界条件 f0,1=0,f0,i=∞ 。
但是直接做的话复杂度 O(n2) 。
你可以直接抄别人构造好的或打表。
然后,注意到这个转移式子具有决策单调性(挺显然的,理由和刚才的二次函数一样)。
于是直接做一个决策单调性就行了。复杂度 O(nlogn) 。
然而你 TLE 了,原因是每次要决策单调性 8 次,题目有 10 组询问,却只开 6s 。
注意到这个题目的子任务二的 n 是固定的,而我们的决策单调性的做法只依赖于 n 。
也就是说我们可以提前打表打出 f 的转移。所以最终还是用了打表。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| #include <bits/stdc++.h> using namespace std; #include "richest.h" using V=vector<int>; const int N=1000010; using ll=long long; const int inf=1e9; int p[9][N],up,f[9][N]; ll F(ll x){return x*(x-1)/2;} ll G(int a,int b){return (b-a%b)*F(a/b)+a%b*F(a/b+1);} void upd(int j,int i,int K){ ll t=G(i,j)+f[K-1][j]; if(t<f[K][i])f[K][i]=t,p[K][i]=j; } void cdq(int l,int r,int K){ if(l+1>=r)return ; int mid=(l+r)>>1; for(int i=p[K][l];i<=p[K][r];i++)upd(i,mid,K); cdq(l,mid,K); for(int i=l+1;i<=mid;i++)upd(i,r,K); cdq(mid,r,K); } V query(V tmp,int m){ int n=tmp.size(); int cnt1=m-n%m,cnt2=n%m; int A=n/m,B=n/m+1; V u,v,res; for(int i=0;i<cnt1;i++){ int st=i*A,ed=(i+1)*A; for(int j=st;j<ed;j++)for(int k=j+1;k<ed;k++) u.push_back(tmp[j]),v.push_back(tmp[k]); } for(int i=0;i<cnt2;i++){ int st=cnt1*A+i*B,ed=cnt1*A+(i+1)*B; for(int j=st;j<ed;j++)for(int k=j+1;k<ed;k++) u.push_back(tmp[j]),v.push_back(tmp[k]); } V ans=ask(u,v),rd(n,0); int kid=0; for(int i=0;i<cnt1;i++){ int st=i*A,ed=(i+1)*A; for(int j=st;j<ed;j++)for(int k=j+1;k<ed;k++,kid++) rd[ans[kid]==tmp[j]?k:j]++; for(int j=st;j<ed;j++)if(!rd[j]){res.push_back(tmp[j]);break;} } for(int i=0;i<cnt2;i++){ int st=cnt1*A+i*B,ed=cnt1*A+(i+1)*B; for(int j=st;j<ed;j++)for(int k=j+1;k<ed;k++,kid++) rd[ans[kid]==tmp[j]?k:j]++; for(int j=st;j<ed;j++)if(!rd[j]){res.push_back(tmp[j]);break;} } return res; } int P[9]={ 0, 500000, 250000, 125000, 62496, 20832, 3472, 183, 1 }; int richest(int n,int T,int S){ up=min({T,n,8}); if(n==1000){ for(int i=2;i<=n;i++)f[0][i]=inf; for(int i=1;i<=up;i++){ for(int j=1;j<=n;j++)f[i][j]=inf,p[i][j]=1; f[i][1]=0; upd(1,n,i); cdq(1,n,i); } int K=n; V tmp(n); for(int i=0;i<n;i++)tmp[i]=i; for(int i=up;i>=1&&K>=1;i--){ tmp=query(tmp,p[i][K]); K=p[i][K]; } return tmp[0]; } int K=n; V tmp(n); for(int i=0;i<n;i++)tmp[i]=i; for(int i=1;i<=up&&K>=1;i++){ tmp=query(tmp,P[i]); K=P[i]; } return tmp[0]; }
|