CodeForces 813A|813B|813C|813D - 搜索|DP

813A - The Contest

Pasha参加了一个有n道题的线上比赛。Pasha解决第i个问题需要 ai 秒的时间(毕竟是神犇,题题都能AC!)。任意一个时刻祂只能同时解决一个问题(结果只是个单线程),而且拥有超能力,能瞬间提交程序评测通过。不幸的是,这个比赛很多人参加,OJ老是瘫痪,Pasha发现OJ只在m秒内不是404(误,应该是50x),只会在j个时间片段 [lj,rj] 秒内正常运作。Pasha只能在不瘫痪的时候提交祂的程序。Pasha想知道祂最多能A几道题,我们需要告诉他最早在第几秒时他能提交所有的程序,或者他不能提交所有的程序。

输入

第一行只有1个正数 n(1n1000)
第二行n个正数 ai(1ai105) ,解决第i道题的时间
第三行一个正数 m(0m1000) ,OJ能运作的时间片段的个数。
接下来m行每行描述一个时间片段: lj,rj(1lj<rj105) ,表示OJ开始正常工作和结束正常工作的时间$
保证这些时间片段没有交集,且按时间顺序给出。

输出

如果Pasha能AK,输出祂最早能AK的时间点。否则输出-1。

样例

样例输入1

2
3 4
2
1 4
7 9

样例输出1

7

样例输入2

1
5
1
1 4

样例输出2

-1

样例输入3

1
5
1
1 5

样例输出3

5

提示

第一个样例中,Pasha先AC第二道题,并立刻提交程序;然后在AC第一道题并在第7s时提交程序。
第二个样例中,Pasha不能在OJ正常工作的时候完成所有的题目。
第三个样例中,Pasha在第一个时间片段最后1s提交了所有的程序。

题解

题解什么的不存在,脸肿.jpg

代码

#include <cstdio>
#define FOR(i,j,k) for(i=j;i<=k;++i)
int main() {
    int n, a, b, tot = 0, i;
    scanf("%d", &n);
    FOR(i,1,n) scanf("%d", &a), tot += a;
    scanf("%d", &n);
    FOR(i,1,n) {
        scanf("%d%d", &a, &b);
        if (a <= tot && tot <= b) {
            printf("%d", tot);
            goto end;
        } else if (a > tot) {
            printf("%d", a);
            goto end;
        }
    }
    puts("-1");
end:
    return 0;
}

813B - The Golden Age

一个不幸运的年份 n 可以表示为n=xa+yb(a,b>0)
比如,如果 x=2,y=3 ,第 4(=20+31) 和第 17(=23+32=24+30) 年就不是幸运的,而第18年就是幸运的。
而连续几年都幸运的年代就被称为黄金时代。
你的任务是写一个程序,找出最长的黄金年代,并且满足不早于第 l 年开始,不晚于第r年结束。如果不存在黄金年代(即 [l,r] 内都是不幸运的年份),输出0。

输入

唯一的一行包含4个正数 x,y,l,r(2x,y1018,1lr1018)

输出

输出 [l,r] 内最长的黄金年代的长度。

样例

样例输入1

2 3 1 10

样例输出1

1

样例输入2

3 5 10 22

样例输出2

8

样例输入3

2 3 3 5

样例输出3

0

提示

第一个样例中,2,3,4,5,7,9,10是不幸运的,所以[1,1],[6,6],[8,8]是黄金年代。
第二个样例中的最长的黄金年代是[15,22]。

题解

容易知道 a,b64 ,所以 xa yb 的个数就 6464=4096 ,接下来就好做了,把所有不幸运的年份都算出来就好了。

代码

#include <iostream>
#include <algorithm>
#define FOR(i,j,k) for(i=j;i<=k;++i)
using namespace std;
typedef long long ll;
const int N = 8192;
ll p[N], q[N], year[N];
int main() {
    ll x, y, l, r, i, j, P, Q, Y = 0, ans = 0;
    p[0] = q[0] = 1;
    cin>>x>>y>>l>>r;
    for (P = 0; p[P] <= r / x; ++P) p[P + 1] = p[P] * x; // 防止爆long long
    for (Q = 0; q[Q] <= r / y; ++Q) q[Q + 1] = q[Q] * y;
    FOR(i,0,P) FOR(j,0,Q) if (p[i] + q[j] <= r && p[i] + q[j] >= l) year[++Y] = p[i] + q[j];
    year[++Y] = l - 1; year[++Y] = r + 1;
    sort(year + 1, year + 1 + Y);
    FOR(i,2,Y) ans = max(ans, year[i] - year[i - 1] - 1);
    cout<<ans;
    return 0;
}

813C - The Tag Game

Alice和Bob在一棵由n个节点组成的树(1为根节点)上玩一个游戏。Alice的棋子一开始在节点1,Bob的棋子一开始在节点 x(x1) 。A和B轮流走,Bob先走。走一步的定义是将棋子保持不动或将棋子移到其相邻的点上(即两点间有边)。当Alice的棋子和Bob的棋子在同一个点上(A吃了B),游戏就结束了。Alice和Bob都很聪明,会做最好的选择(Alice希望最小化总步数而Bob希望最大化总步数)。请你写个程序算出游戏总步数。

输入格式

第一行2个正数n和x ( 2n2105 , 2xn ).
接下来的 n1 行,每行2个正数 a b( 1a,bn ),表示树上的边。保证树合法。

输出格式

输出游戏总步数(Alice走的步数和Bob走的步数的和)

样例

样例输入1

4 3
1 2
2 3
2 4

样例输出1

4

样例输入2

5 2
1 2
2 3
3 4
2 5

样例输出2

6

题解

一些显然的性质:
1. 无论Bob怎么走,Alice总是沿AB路径走而且不会某一步选择不动
2. Bob不会走回头路(这样做和在某个点停住不动是一个情况)
3. 可以认为Bob只在叶子停住不动(选择不动可以在中途也可以在最后,我们调整到最后)
4. AB间距不会增大(在2的前提下)
5. 一定是Alice最后走,Bob不会最后走(主动投入Alice温暖又邪恶的怀抱???)
因此我们可以想到这样的思路:首先1到x的路径离Alice 1近的路径上一半的点Bob一定到不了,剩下的点Bob不作死都可以到,那么Bob能到的这些点离1最远的就是答案的一半。
话说也就是那些Alice比Bob晚到的点中找,一个道理。

代码

#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 200005, M = 2 * N;
#define FOR(i,j,k) for(i=j;i<=k;++i)

int n, x, h[N], p[M], v[M], vis[N], edge = 0;
int rec1[N], recx[N], path[N], len;

void add(int x, int y) {
    p[++edge] = h[x]; v[edge] = y; h[x] = edge;
    p[++edge] = h[y]; v[edge] = x; h[y] = edge;
}

bool dfs(int x, int fa, int dep) { // 先找AB路靠近Alice的那一半的点
    path[dep] = x; len = dep;
    if (x == 1) return true;
    for (int i = h[x]; i; i = p[i])
        if (v[i] != fa && dfs(v[i], x, dep + 1))
            return true;
    return false;
}

void dfs2(int x, int fa, int dep, int *rec) {
    rec[x] = dep;
    for (int i = h[x]; i; i = p[i])
        if (v[i] != fa && !vis[v[i]])
            dfs2(v[i], x, dep + 1, rec);
}

int main() {
    int i, ans = 0, a, b;
    scanf("%d%d", &n, &x);
    FOR(i,1,n) rec1[i] = recx[i] = -1;
    FOR(i,2,n) scanf("%d%d", &a, &b), add(a, b);
    dfs(x, 0, 1); dfs2(1, 0, 1, rec1); // 算出Alice到各点的距离
    FOR(i, len / 2 + 1, len) vis[path[i]] = 1; // 靠近Alice的一半的点Bob不可达
    if (!vis[x]) dfs2(x, 0, 1, recx); // 剩下的可达
    FOR(i,1,n) if (recx[i] != -1) ans = max(ans, rec1[i]); // Bob可达的点离Alice最远的就是答案
    printf("%d", (ans - 1) * 2); // Bob 和 Alice 的距离的两倍

    return 0;
}

813D - Two Melodies

有一个大序列,挑出2个不相交不为空的子序列,满足相邻两个元素差为1或模7相等。
写一个程序计算最大的满足条件的2个不相交子序列的长度和。

输入

第一行只有一个整数 n(2n5000) ,大序列元素个数.
第二行有n个整数 a1,a2,...,an(1ai105) 大序列元素。

输出

输出符合条件的两个非空不相交子序列的长度和的最大值。

样例

样例输入1

4
1 2 4 5

样例输出1

4

样例输入2

6
62 22 60 61 48 49

样例输出2

5

提示

样例一的子序列为 [1,2],[4,5] 两个。
样例二的子序列为 [62,48,49],[60,61] 。如果先选 [62,61] ,第二个子序列的最大长度为2,不是最优。

题解

容易想到用 dp[i,j] 表示第一个子序列在第i个元素结束,第二个子序列在第j个元素结束。
而且也容易想到,如果a[k]和a[i]满足条件,那么dp[i,j]将用dp[k,j]更新答案,而且可以避免两个集合

引用\[1\]中提到了一种树形动态规划的方法来解决CodeForces - 982C问题。在这个问题中,subtree指的是子连通块,而不是子树。为了使cnt_white - cnt_black尽可能大,可以使用两次树形动态规划来求解。第一次是自底向上的过程,维护一个dp数组,表示以每个节点为根的子树中的最大连通块。第二次是自顶向下的过程,处理自底向上过程中无法包含的树链所代表的子树。在第二次遍历中,需要维护一个sum变量,用于存储树链所代表的子树的贡献。根据ans\[u\]的正负,决定是否能对相邻的子节点做出贡献。如果ans\[u\]为正,则减去dp\[v\]就是树链所代表的子树的权值。最终,ans\[u\]代表包含节点u在内的子连通块的最大权值。\[1\] 问题: CodeForces - 982C 树形DP是什么问题?如何解决? 回答: CodeForces - 982C是一个树形动态规划问题。在这个问题中,需要求解子连通块的最大权值和,使得cnt_white - cnt_black尽可能大。解决这个问题的方法是使用两次树形动态规划。第一次是自底向上的过程,维护一个dp数组,表示以每个节点为根的子树中的最大连通块。第二次是自顶向下的过程,处理自底向上过程中无法包含的树链所代表的子树。在第二次遍历中,需要维护一个sum变量,用于存储树链所代表的子树的贡献。根据ans\[u\]的正负,决定是否能对相邻的子节点做出贡献。最终,ans\[u\]代表包含节点u在内的子连通块的最大权值。\[1\] #### 引用[.reference_title] - *1* *2* [CodeForces - 1324F Maximum White Subtree(树形dp)](https://blog.csdn.net/qq_45458915/article/details/104831678)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值