P10786 [NOI2024] 百萬富翁

rgw2010發表於2024-08-28

思路:

先考慮 Sub1 的部分分,暴力演算法:

暴力詢問所有 \(i<j\) 的數對 \((i,j)\)
則一個 \(i\) 為最大值當且僅當 \((i,j)\) 的返回值都是 \(i\) 且在 \(i\) 之前沒有滿足此條件的位置。

則設 \(\operatorname{F}(n) = \frac{n(n-1)}{2}\) 表示暴力找出 \(n\) 個數中的最大值需要的詢問次數,注意到 \(\operatorname{F}(1000) = 499500\),故可以透過 Sub1。

對於 Sub2,直接暴力肯定是不行的,但是注意到 \(t\) 的值開大了,故我們沒必要一步到位,可以逐步縮小範圍。

定義 \(\operatorname{W}(a,b)\) 表示原先最大值候選有 \(a\) 個,經過一次請求篩到 \(b\) 個候選人的最小詢問次數;考慮分為 \(b\) 組,每組有 \(\lfloor \frac{a}{b} \rfloor \sim \lceil \frac{a}{b} \rceil\) 人,故:

\[\operatorname{W}(a,b) = (b - b\bmod a) \operatorname{F}(\lfloor \frac{a}{b} \rfloor) + (b \bmod a) \operatorname{F}(\lceil \frac{a}{b} \rceil) \]

現在我們的目的就是找到一個合理的篩檢最大值候選的序列 \(h\),使得長度 \(len \le t\)\(\sum\limits_{i=1}^{len-1} \operatorname{W}(h_i,h_{i+1}) \le s\)

爆搜經過實測可以搜到 \(t=9\) 的情況,在 \(t=8\) 時幾乎無法跑出來。

考慮動態規劃演算法,令 \(dp_{i,j}\) 表示 \(W_i = j\) 的情況下的最小詢問數,則狀態轉移方程為:

\[dp_{i,j} = \min_{k=j+1}^n dp_{i-1,k} + \operatorname{W}(k,j) \]

時間複雜度為 \(O(tn^2)\),大概有 \(10^{13}\),按照最低預算,計算機 1s 可以跑 \(10^8\),則 \(\frac{10^{13}}{10^{8}} = 10^5\),則 \(\frac{10^5}{64^2} \approx 24\),大概要跑 \(1\) 天的時間,是肯定不行的。

可以有一個猜想,每次候選人的數量至少要減半,這樣一定不會太劣,這樣我們就將範圍個縮小了,對於 \(dp_{i,j}\) 有效的 \(j\) 只有 \(\frac{n}{2^{i-1}}\),列舉的 \(k\) 大概是 \((j,2j]\),這樣大概可以縮到 \(10^{11}\) 左右,可以在 1h 內跑完。

其實也可以在確定前面幾位為 \(1000000,500000,250000,125000\) 的情況下對後面跑 \(dp\),這樣時間更會大大減少。

最後根據我們得到的 \(h\) 模擬一下分組求最大值的過程即可。

單組資料時間複雜度最多為 \(O(Nt)\)

完整程式碼:

#include<bits/stdc++.h>
#define Add(x,y) (x+y>=mod)?(x+y-mod):(x+y)
#define lowbit(x) x&(-x)
#define pi pair<ll,ll>
#define pii pair<ll,pair<ll,ll>>
#define iip pair<pair<ll,ll>,ll>
#define ppii pair<pair<ll,ll>,pair<ll,ll>>
#define fi first
#define se second
#define full(l,r,x) for(auto it=l;it!=r;it++) (*it)=x
#define Full(a) memset(a,0,sizeof(a))
#define open(s1,s2) freopen(s1,"r",stdin),freopen(s2,"w",stdout);
#define For(i,l,r) for(int i=l;i<=r;i++)
#define _For(i,l,r) for(int i=r;i>=l;i--)
using namespace std;
typedef double db;
typedef unsigned long long ull;
typedef long long ll;
const int maxn=1e6+10;
std::vector<int> ask(std::vector<int> a, std::vector<int> b);
int n;
vector<int> a,b,h;
namespace Sub1{
    int cnt=0;
    int work(){
    	a.clear(),b.clear();
    	cnt=0;
        vector<int> l(n,0),r(n,0);
        For(i,0,n-1){
            l[i]=cnt;
            For(j,i+1,n-1){
                a.push_back(i);
                b.push_back(j);
                ++cnt;
            }
            r[i]=cnt-1;
        }
        h=ask(a,b);
        For(i,0,n-1){
            bool F=1;
            For(j,l[i],r[i]){
                if(h[j]!=i){
                    F=0;
                    break;
                }
            }
            if(F)
              return i;
        }
        return 0;
    }
};
namespace Sub2{
	int cnt=0;
	int s[maxn];
	vector<int> E[maxn];
	stack<int> S;
	int p[]={1000000,500000,250000,125000,62498,20832,3472,183,1};
	void solve(int x){
		s[x]=a.size();
		int n=E[x].size();
        For(i,0,n-1){
            For(j,i+1,n-1){
                a.push_back(E[x][i]);
                b.push_back(E[x][j]);
            }
        }
	}
	int get(int x){
		int g=s[x],n=E[x].size();
		For(i,0,n-1){
			bool F=1;
			For(j,i+1,n-1)
			  if(h[g++]!=E[x][i])
				F=0;
			if(F)
			  return E[x][i];
		}
		return 0;
	}
	void get(int X,int Y){
		cnt=0;
		int l=0,cnt=0,g=0,B=X/Y;
		For(i,1,Y)
		  E[i].clear();
		while(!S.empty()){
			++l;
			int x=S.top();
			S.pop();
			if((l-1)/B+1!=cnt)
			  ++cnt;
			if(cnt<=Y)
			  E[cnt].push_back(x);
			else{
				++g;
				E[g].push_back(x);
			}
		}
		For(i,1,Y)
		  solve(i);
		h=ask(a,b);
		For(i,1,Y){
			int x=get(i);
			S.push(x);
		}
	}
	int work(){
		while(!S.empty())
		  S.pop();
    	_For(i,0,n-1)
    	  S.push(i);
		For(i,1,8){
			a.clear(),b.clear();
			get(p[i-1],p[i]);
		}
		return S.top();
	}
};
int richest(int N,int T,int S){
    n=N;
    if(n<=1000)
      return Sub1::work();
    else
      return Sub2::work();
}

相關文章