Loading

P10786 [NOI2024] 百万富翁 解题报告

这个交互,很简单。

Subtask 1 可以直接构造完全图。

Subtask 2 这个 \(1099944\) 启示我们 DP,于是设计状态 \(f[i][j]\) 代表用 \(i\) 组查询区分大小为 \(j\) 的集合,最少要多少次查询。

然后考虑我们使用一组查询将决策集合从 \(x\) 缩小到 \(y\) 需要多少次查询。

Subtask 1 提醒了我们,如果想要把 集合从 \(s\) 缩小到 \(1\),那么必须要构造一个完全图。

那我们就可以尝试把 \(x\) 个点分成 \(y\) 组,每一组直接构造一个完全图就好了。由数学知识,必须要尽可能均匀地分组。

然后这个代价就可以 \(\mathcal{O}(1)\) 算了。

之后就是 DP 找一个长度为 \(9\),两端是 \(1\)\(1000000\) 的序列。其实就是非常普通的划分形式,但是我不会单调队列,这个东西也不能斜率优化。

我猜是决策单调性,但我没学过,咋办呢?

看了眼题解,是时候尝试一些乱搞做法了。

附上我的爬山代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;
const double t0 = 400000, alp = 0.9995, t1 = 1e-8;
mt19937 Misaka(time(0));
double ran(){
	uniform_real_distribution<double> dis(-1.0, 1.0);
	return dis(Misaka);
}
double P(){
	uniform_real_distribution<double> dis(0, 1.0);
	return dis(Misaka);
}
bool check(vector<int> res){
	for(int i = 0; i + 1 < res.size(); i ++){
		if(res[i] >= res[i + 1]) return false;
		if(res[i] <= 0 || res[i + 1] <= 0) return false;
	}
	return true;
}
vector<int> gen(vector<int> st, double tem){
	vector<int> tmp = st;
	int mxa = 100;
	while(mxa --){
		st = tmp;
		for(int i = 1; i + 1 < st.size(); i ++){
			int x = tem / 50 * (1 << (i - 1));
			if(x > 20000) x = 20000;
			if(x < 0) x = 0;
			uniform_real_distribution<double> dis(-x, x);
			st[i] += dis(Misaka);
		}
		if(check(st)) return st;
	}
	return tmp;
}
int ss(int x) {
    return x * (x - 1) / 2;
}
int cal(int a, int b) {
    int remainder = a % b;
    int quotient = a / b;
    return (b - remainder) * ss(quotient) + remainder * ss(quotient + 1);
}
int f(vector<int> cur){
	int res = 0;
	for(int i = 1; i < cur.size(); i ++){
		res += cal(cur[i], cur[i - 1]);
	}
	return res;
}
vector<int> SA(){
	double t = t0;
	vector<int> cur = {1000000, 500000, 250000, 125000, 62500, 31250, 15625, 7812, 1};
	reverse(cur.begin(), cur.end());
	vector<int> bst = cur;
	while(t > t1){
		int inner = 2000;
		while(inner --){
			vector<int> nw = gen(cur, t);
			int del = f(nw) - f(cur);
			if(del < 0) cur = nw;
//			else if(P() <= exp(-del / t)) cur = nw;
			if(f(cur) < f(bst)) bst = cur;
		}
		t *= alp;
	}
	cout << f(bst) << "\n";
	for(int x: bst) cout << x << ", ";
}
signed main(){
//	ios::sync_with_stdio(0), cin.tie(0);
	SA();
	return 0;
}

跑出来的结果:

1099944
1,183,3472,20832,62500,125000,250000,500000,1000000

按照这个序列询问就行。

#include <bits/stdc++.h>
using namespace std;
vector<int> w;
vector<int> ask(vector<int> a, vector<int> b);
int solve1(int n){
  vector<int> blue, red;
  for(int i = 0; i < n; i ++){
    for(int j = i + 1; j < n; j ++){
      blue.push_back(i);
      red.push_back(j);
    }
  }
  vector<int> res = ask(blue, red);
  vector<int> deg(n, 0);
  for(int st: res) deg[st] ++;
  for(int i = 0; i < n; i ++)
    if(deg[i] == n - 1) return i;
}
vector<int> gets(int x, int y){
	int bs = x / y, rem = x % y;
	vector<int> res;
	for(int i = 1; i <= y - rem; i ++) res.push_back(bs);
	for(int i = 1; i <= rem; i ++) res.push_back(bs + 1);
	return res;
}
void appd(vector<int> &s, vector<int> &a, vector<int> &b){
	for(int i = 0; i < s.size(); i ++){
		for(int j = i + 1; j < s.size(); j ++){
			a.push_back(s[i]);
			b.push_back(s[j]);
		}
	}
	s.clear();
}
int solve2(int n){
	vector<int> qur = {1, 183, 3472, 20832, 62500, 125000, 250000, 500000, 1000000};
	reverse(qur.begin(), qur.end());
	vector<int> vis(n, 1);
	for(int i = 1; i <= 8; i ++){
		vector<int> div = gets(qur[i - 1], qur[i]);
		vector<int> blue, red, cnt;
		for(int i = 0; i < n; i ++){
			if(vis[i]) cnt.push_back(i);
			if(cnt.size() == div.back()){
				div.pop_back();
				appd(cnt, blue, red);
			}
		}
		vector<int> res = ask(blue, red);
		for(int i = 0; i < res.size(); i ++){
			if(res[i] == blue[i]) swap(blue[i], red[i]);
			vis[blue[i]] = 0;
		}
	}
	for(int i = 0; i < n; i ++){
		if(vis[i]) return i;
	}
}
int richest(int N,int T,int S){
  if(N == 1000) return solve1(N);
  return solve2(N);
}
posted @ 2026-05-24 13:01  Trent900  阅读(31)  评论(0)    收藏  举报