2024杭电多校第10场
10
1001 LIS (hdu7541)
网络流总诱惑我去学一些根本做不出来的东西
由Dilworth定理,偏序集能划分成的最小全序集个数等于最大反链的长度。将 \((i, a_i)\) 视为一个二元组,定义 \((i,a_i)\leq(j,a_j)\) 当且仅当 \(i\leq j, a_i\geq a_j\),则 \(i\leq j\) 且 \(a_i<a_j\) 时,两元素不可比,此时形成的反链为单调上升的子序列。题目要求最长上升子序列长度 \(\leq k\),即等价于序列中不交叉的不降子序列不多于 \(k\) 个。为了使删数代价最小,需要用 \(k\) 个不降子序列尽可能覆盖代价更大的元素。
考虑费用流建模,将 \(i\) 拆成 \(2i - 1, 2i\) 两个节点,连接源点 \(st\) 与 \(2i-1\),汇点 \(ed\) 与 \(2i\),由于每个点只能选择一次,流量为 \(1\),费用为 \(0\);\(2i-1\) 与 \(2i\) 连边,流量为 \(1\),费用为 \(-b_i\),将最大覆盖价值转换为最小费用求解。对于 \(i < j\) 且 \(a_i>a_j\),连接 \(2i\) 与 \(2j-1\),流量为 \(1\),费用为 \(0\),若此处有流量经过、则代表它们在同一个子序列内。题目要求计算 \(1\leq k\leq n\) 的所有答案,因为所有边的流量限制为 \(1\),每次增广的流量也一定为 \(1\),在最短路计算、边权修改之后直接输出本次答案即可,不需要重新计算每个最大流下的最小费用。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
typedef pair <int, int> P;
const int N = 510;
const ll INF = 1e12;
int n, a[N], b[N];
int st, ed;
struct edge{
int t, w, c, r;
};
vector <edge> v[N * 2];
void Clear() {
for(int i = 0; i <= ed; i++) {
v[i].clear();
}
}
void add(int f, int t, int w, int c) {
v[f].push_back({t, w, c, (int)v[t].size()});
v[t].push_back({f, 0, -c, (int)v[f].size() - 1});
}
ll d[N * 2], h[N * 2];
int pre[N * 2], id[N * 2];
// Primal-Dual 原始对偶算法
void dijkstra() {
fill(d, d + ed + 1, INF);
d[st] = 0;
priority_queue <P, vector<P>, greater<P> > q;
q.push(P(d[st], st));
while(!q.empty()) {
int x = q.top().first, y = q.top().second;
q.pop();
if(d[y] < x) continue;
for(int i = 0; i < v[y].size(); i++) {
int t = v[y][i].t, w = v[y][i].w, c = v[y][i].c;
if(w > 0 && d[t] > d[y] + c + h[y] - h[t]) {
d[t] = d[y] + c + h[y] - h[t];
pre[t] = y;
id[t] = i;
q.push(P(d[t], t));
}
}
}
}
int ans;
void mcf() {
dijkstra();
for(int i = st; i <= ed; i++) {
h[i] += d[i];
}
for(int i = ed; i != st; i = pre[i]) {
edge &e = v[pre[i]][id[i]];
ans += e.c;
e.w--;
v[i][e.r].w++;
}
// 一次增广后直接返回(实测每次重新算就会TLE)
}
void solve() {
Clear();
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
int sum = 0;
for(int i = 1; i <= n; i++) {
scanf("%d", &b[i]);
sum += b[i];
}
fill(h, h + ed + 1, 0);
st = 0, ed = n * 2 + 1;
for(int i = 1; i <= n; i++) {
// 预处理初始势能
h[i * 2 - 1] = h[i * 2 - 2];
h[i * 2] = h[i * 2 - 1] - b[i];
add(st, i * 2 - 1, 1, 0);
add(i * 2 - 1, i * 2, 1, -b[i]);
add(i * 2, ed, 1, 0);
}
h[ed] = h[n * 2];
for(int i = 1; i <= n; i++) {
for(int j = i + 1; j <= n; j++) {
if(a[j] < a[i]) {
add(i * 2, j * 2 - 1, 1, 0);
}
}
}
ans = 0;
for(int f = 1; f <= n; f++) {
mcf();
// ans为最大覆盖价值的相反数
printf("%d ", sum + ans);
}
printf("\n");
}
int main() {
int t;
scanf("%d", &t);
while(t--) {
solve();
}
system("pause");
return 0;
}
1002 scenery (hdu7542)
由于 \(l\) 序列不增、 \(r\) 序列不降,每处景色的拍摄安排在可选时间的开始/结束位置显然是最优的。设 \(dp[i][j]\) 表示(从后往前)考虑到第 \(i\) 处景色、可选时间从 \(j\) 开始的最晚结束位置,则转移方程:
\(dp[i][max(l_i, j) + t_i] = max(dp[i][max(l_i, j) + t_i], dp[i - 1][j])\) (开始位置)
\(dp[i][j] = max(dp[i][j], min(dp[i - 1][j], r_i + 1) - t_i)\) (结束位置)
预先将所有状态赋为不合法情况 \(-1\),最后检查 \(dp[1][j]\) 中是否有合法情况即可。
memset(dp, -1, sizeof(dp));
int it = 1;
dp[1][0] = m + 1;
for(int i = n; i; i--) {
it ^= 1;
for(int j = 0; j <= m; j++) {
dp[it][j] = -1;
}
for(int j = 0; j <= m; j++) {
int l = max(a[i].l - 1, j) + a[i].t;
if(l > a[i].r || l >= dp[it ^ 1][j]) continue;
dp[it][l] = max(dp[it][l], dp[it ^ 1][j]);
}
for(int j = 0; j <= m; j++) {
int r = min(dp[it ^ 1][j], a[i].r + 1) - a[i].t;
if(r < a[i].l || r <= j) continue;
dp[it][j] = max(dp[it][j], r);
}
}
bool flag = 0;
for(int j = 0; j <= m; j++) {
if(dp[it][j] >= 0) {
flag = 1;
break;
}
}
if(flag) printf("YES\n");
else printf("NO\n");
居然能在赛时写对dp,挺难得的)
1008 SunBian (hdu7548)
对于 \(n\) 为偶数的情况,除非Alice能够一次将所有笋变成横向,即 \(k=n\),否则无论她如何操作,Bob都可以在对称位置模仿操作、使自己必胜。\(n\) 为奇数时,若 \(1<k<n\),Bob可将整个环隔开成为两个对称的部分、同理模仿操作;\(k=1\) 或 \(n\) 时则Bob必败。
1010 A+B Problem (hdu7550)
一个小规律:\(ans_i\) 的二进制最低位与 \(ans_{i - 1}\) 无关,对于确定的 \(a_i,b_i\),\(ans_i\space \&\space 1\)为定值。而其他每位上的取值只与 \(a_i,b_i\) 以及 \(ans_{i - 1}\) 的较低位有关,因此确定最低位后、可通过递推求出完整的 \(ans_i\),即最终答案唯一。
for(int i = 1; i <= q; i++) {
scanf("%lld%lld", &a[i], &b[i]);
c[i] = (a[i] ^ b[i]) & 1;
}
c[0] = c[q];
for(int z = 1; z < 32; z++) {
for(int i = 1; i <= q; i++) {
ll y = (a[i] ^ c[i - 1]) + (b[i] ^ c[i - 1]);
c[i] += (y & (1ll << z));
}
c[0] = c[q];
}
for(int i = 1; i <= q; i++) {
printf("%lld\n", c[i]);
}
另有:写1011的时候假设这位同学每次比赛都不幸爆零、排他前面的人都AK,后来发现题解真这么写的,好一道吉利的题目()