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);
}

浙公网安备 33010602011771号