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的常见状态与方程的设计不够熟悉,还得努力练习啊!!!

 

posted @ 2021-03-16 19:50  Chasing-Dreams  阅读(50)  评论(0)    收藏  举报