§ Desc.
有 张卡片,第 张卡片正面印着一个数 ,反面印着一个数 。一开始所有数正面朝上。
有 种操作,第 种操作表示为:
- 有 的概率将卡片 翻转,否则将 翻转。
要求你求一个集合 ,使得进行了集合中所有的编号的操作之后正面朝上的所有数的和的期望最大。输出这个最大值。
§ Sol.
容易发现有概率被任意正数台机器翻转的卡片对答案的贡献是 , 其他卡片的贡献是 . 将卡片分为两个集合 和 , 其中 有 , 有 . 把一台机器的操作 分为三个集合 和 , 其中 中的边只涉及 内的卡片, 中的边只涉及 中的卡片, 中的边同时涉及两边的卡片. 中的边一定不会连, 中的边一定会连, 留给我们决策的是 中的边.
分类讨论 和 的相对大小:
- :
此时 , 采取以下的动态规划, 设 表示 中的前 个点与 的点集 有连边的最大期望答案. 有转移
- :
朴素的应用搜索即可.
代码有点脑溢血, 抄了官方题解的实现.
int main()
{
ios::sync_with_stdio(0);
cin.tie(nullptr);
int n, m; cin >> n >> m;
vi a(n), b(n), u(m), v(m);
for (int i=0;i<n;++i) cin >> a[i] >> b[i];
for (int i=0;i<m;++i) cin >> u[i] >> v[i];
for (int i=0;i<m;++i) u[i]--, v[i]--;
for (int i=0;i<m;++i) {
if (u[i] == v[i] && a[u[i]] < b[u[i]]) swap(a[u[i]], b[u[i]]);
}
int ans = 0;
vi A, B, id(n);
for (int i=0;i<n;++i) {
ans += a[i]*2;
if (a[i] >= b[i]) id[i] = A.size(), A.pb(a[i]-b[i]);
else id[i] = B.size(), B.pb(b[i]-a[i]);
}
int nA = A.size(), nB = B.size();
vector<ll> ls(nA);
const int INF = 1e9;
for (int i=0;i<m;++i) {
bool xp = a[u[i]] >= b[u[i]],
yp = a[v[i]] >= b[v[i]];
if (xp == yp) {
if (!xp) {
ans += B[id[u[i]]]+B[id[v[i]]];
B[id[u[i]]] = B[id[v[i]]] = 0;
}
} else {
if (!xp) swap(u[i], v[i]);
ls[id[u[i]]] |= 1ll<<id[v[i]];
}
}
if (nA <= nB) {
int opt = 0;
for (int i=0;i<1<<nA;++i) {
int cur = 0; ll st = 0;
for (int j=0;j<nA;++j) if (i&(1<<j)) cur -= A[j], st |= ls[j];
for (int j=0;j<nB;++j) if (st&(1ll<<j)) cur += B[j];
chkmax(opt, cur);
}
ans += opt;
} else {
vector dp(nA+1, vi(1<<nB, -INF));
dp[0][0] = 0;
for (int i=0;i<nA;++i) {
for (int j=0;j<1<<nB;++j) {
chkmax(dp[i+1][j], dp[i][j]);
chkmax(dp[i+1][j|ls[i]], dp[i][j]-A[i]);
}
}
int mx = 0;
for (int j=0;j<1<<nB;++j) {
int cur = dp[nA][j];
for (int i=0;i<nB;++i) if (j&(1<<i)) cur += B[i];
chkmax(mx, cur);
}
ans += mx;
}
cout << fixed << ans / 2. << "\n";
}
/ 当最后一缕余温消亡 /
/ 空躯壳何处能够安放 /
/ 睁眼醒于温暖的慌 /
/ 一笑而过继续坚强 /