0314周赛
wtcl.惨痛的经历.
T1 NKOJ 祖玛游戏
题面:不想贴了。
思路:与方块消除类似,记f[i][j][k]为消去第i到第j个方块的最小代价。第一种转移:从j向左遍历,第一个与j颜色不同的方块的位置记作q,有一个很显然的暴力转移,即直接消去。第二种转移:对于i到q的每一个位置,如果它是一段与j颜色相同的连续段的右端,设为p,可以先讨论消去p+1,q-1这一段的代价,然后就合并到了一起继续讨论即可。转移直接记搜即可,具体见代码。
#include <bits/stdc++.h> using namespace std; int c[105], f[105][105][105], n, k; bool mark[105][105][105]; int dp(int x, int y, int z) { if (x > y) return 0; if (mark[x][y][z]) return f[x][y][z]; // if (z >= k - 1) return f[x][y][z] = 0; int p = x, q = y; while (q >= x && c[q] == c[y]) q--; f[x][y][z] = min(f[x][y][z], dp(x, q, 0) + max(k - (z + y - q), 0)); while (p < q) { if (c[p] == c[y] && c[p + 1] != c[y]) f[x][y][z] = min(f[x][y][z], dp(p + 1, q, 0) + dp(x, p, z + y - q)); p++; } mark[x][y][z] = 1; return f[x][y][z]; } int main() { memset(f, 0x3f, sizeof f); scanf("%d%d", &n, &k); for (int i = 1; i <= n; i++) scanf("%d", &c[i]); printf("%d", dp(1, n, 0)); }
T2 NKOJ 赛事直播
题面:一个网络直播平台计划直播今年ioi比赛。网络中的中转点和观看点构成了一个树形结构。这棵树的根是直播点,它将直播比赛。其中观看点是树的叶节点,他们是可能要观看比赛的用户(用户可以选择不看比赛,这样就不用缴观看费)。其他非根节点、非叶节点的中间节点就是直播数据的中间转发点,即中转点。
将比赛数据从树中一个点传到另一个点需要一定的花费。整个直播的费用就是所有传输费用的总和。
每一个用户都给出了自己愿意支付的观看费用。直播公司要决定是否给这个用户提供直播服务。例如:给一个用户(叶节点)直播比赛的花费很大,而这个用户愿意支付的费用却很少时,直播公司可以选择不给他直播。
找到一个方案使得观看ioi直播的客户尽可能多,且直播公司不能亏本(直播的费用不超过所有观看比赛的客户的缴费总和)。
思路:裸的树包,wtcl写不出来。
#include <bits/stdc++.h> using namespace std; struct node { int to, nxt, w; }e[10005]; int head[10005], tot, cnt[10005], c[10005], f[3005][3005]; inline void add_e(int u, int v, int w) {e[++tot].to = v; e[tot].nxt = head[u]; head[u] = tot; e[tot].w = w;} void dfs(int x) { if (!head[x]) cnt[x]++; for (int i = head[x]; i; i = e[i].nxt) dfs(e[i].to), cnt[x] += cnt[e[i].to]; } void dp(int x) { if (!head[x]) { f[x][1] = c[x]; return; } for (int i = head[x]; i; i = e[i].nxt) { int y = e[i].to; dp(y); for (int j = cnt[x]; ~j; j--) { for (int k = 1; k <= min(j, cnt[y]); k++) { f[x][j] = max(f[x][j], f[x][j - k] + f[y][k] - e[i].w); } } } } int main() { memset(f, -0x3f, sizeof f); int n, p; scanf("%d%d", &n, &p); for (int i = 0; i <= n; i++) f[i][0] = 0; for (int i = 1, t; i <= n - p; i++) { scanf("%d", &t); for (int j = 1, x, y; j <= t; j++) { scanf("%d%d", &x, &y); add_e(i, x, y); } } for (int i = n - p + 1; i <= n; i++) scanf("%d", &c[i]); dfs(1); dp(1); for (int i = p; ~i; i--) if (f[1][i] >= 0) return printf("%d", i), 0; }
T3 NKOJ 菜园看守
题面:每每到了韭菜成熟的时节,何老板就会看准商机,推出了一项名为“菜园守望者”的服务。该服务很简单,就是何老板安排人手去为你看守菜园,帮你驱赶鸟兽或者小偷。
最近,何老板又签下了一个看守合同,该合同需要何老板派人看守一个韭菜园,时间是从第A天起一直到第B天。在这B-A+1天里,要求每天至少有一个人在看守菜园。
何老板手下共有N个员工。每个员工都对自己的工作时间和报酬有一定的要求,比如员工甲只在T1..T2这段时间工作,并要求S1元的报酬;员工乙只在T3..T4这段时间工作,并要求S2元的报酬......
请你帮助何老板安排一个工作时间表,使得从第A天到第B天每天至少有一个员工在看守菜园,并且使得何老板支付的报酬总数尽可能少。
思路:老韭菜了。设f[i]为从A到i天都有人看守的最小代价,显然按左端点排序,然后f[End[i]] = min{f[j]+w[i]},Begin[i]-1<=j<=End[i],其中最小值用线段树维护,算出f[End[i]]后单点修改即可。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const ll inf = 0x3f3f3f3f3f3f3f3f; ll f[100005]; #define mid (Tree[q].l + Tree[q].r >> 1) #define ls (q << 1) #define rs (q << 1 | 1) struct qwq { ll l, r, w; }a[10005]; inline bool cmp(qwq x, qwq y) {return x.l < y.l;} struct node { ll l, r, minn; }; node Tree[500005]; void Build(ll q, ll x, ll y) { Tree[q].l = x, Tree[q].r = y, Tree[q].minn = inf; if (x < y) { Build(ls, x, mid); Build(rs, mid + 1, y); } } void modify(ll q, ll k, ll d) { if (Tree[q].l > k || Tree[q].r < k) return; if (Tree[q].l == Tree[q].r) { Tree[q].minn = min(Tree[q].minn, d); return; } modify(ls, k, d), modify(rs, k, d); Tree[q].minn = min(Tree[ls].minn, Tree[rs].minn); } ll query(ll q, ll x, ll y) { if (Tree[q].l > y || Tree[q].r < x) return inf; if (Tree[q].l >= x && Tree[q].r <= y) return Tree[q].minn; return min(query(ls, x, y), query(rs, x, y)); } main() { ll n, A, B; scanf("%lld%lld%lld", &n, &A, &B);A++, B++; Build(1, 1, B); memset(f, 0x3f, sizeof f); for (ll i = 1; i <= n; i++) scanf("%lld%lld%lld", &a[i].l, &a[i].r, &a[i].w), a[i].l++, a[i].r++; sort(a + 1, a + n + 1, cmp); if (a[1].l > A) return puts("-1"), 0; modify(1, a[1].r, a[1].w);f[a[1].r] = a[1].w; for (ll i = 2; i <= n; i++) { f[a[i].r] = min(f[a[i].r], query(1, a[i].l - (a[i].l > 1), a[i].r) + a[i].w); modify(1, a[i].r, f[a[i].r]); } // for (ll i = A; i <= B; i++) printf("%d %d\n", i, f[i]); printf("%lld", f[B] == inf ? -1 : f[B]); }
T4 NKOJ 石头剪刀布
题面:
何老板有三种类型的卡片,一种卡片上画的是石头,一种卡片上画的是剪刀,一种卡片上画的是布。
何老板在一个不透明的盒子里放了张石头卡片,张剪刀卡片,张布卡片。
现在何老板会反复进行如下操作:
随机一把从盒子里抓出两张卡片,若两张卡片相同,则把它们重新放回盒子。若两张卡片不同,则撕掉被打败的一张,将获胜的一张重新放回盒子。规则是,石头打败剪刀,剪刀打败布,布打败石头。
请你帮他计算:
最终盒子里只剩下石头卡片的概率有多大?
最终盒子里只剩下剪刀卡片的概率有多大?
最终盒子里只剩下布卡片的概率有多大?
思路:概率dp,设f[i][j][k]为还剩下i张石头,j张剪刀,k张布的概率,分别讨论拿出两张卡的存活关系即可。
#include <bits/stdc++.h> using namespace std; double f[105][105][105]; int main() { int x, y, z; scanf("%d%d%d", &x, &y, &z); f[x][y][z] = 1; for (int i = x; ~i; i--) { for (int j = y; ~j; j--) { for (int k = z; ~k; k--) { if (!i && !j) continue; if (!i && !k) continue; if (!k && !j) continue; int tot = i * j + i * k + j * k; if (i) f[i - 1][j][k] += f[i][j][k] * i * k * 1.0 / tot; if (j) f[i][j - 1][k] += f[i][j][k] * i * j * 1.0 / tot; if (k) f[i][j][k - 1] += f[i][j][k] * j * k * 1.0 / tot; } } } double a = 0, b = 0, c = 0; for (int i = 1; i <= x; i++) a += f[i][0][0]; for (int i = 1; i <= y; i++) b += f[0][i][0]; for (int i = 1; i <= z; i++) c += f[0][0][i]; printf("%.9lf %.9lf %.9lf", a, b, c); }
这次考的太差,明显感觉对dp的常见状态与方程的设计不够熟悉,还得努力练习啊!!!

浙公网安备 33010602011771号