2021CCPC哈尔滨【个人题解BDEGIJ】

B - Magical Subsequence(暴力、贪心)

思路

因为 A [ i ] + A [ j ] = s u m A[i] + A[j] = sum A[i]+A[j]=sum不会超过200,因此直接枚举 s u m sum sum,找到每个 s u m sum sum对应的 b b b序列长度,再取最长的 b b b序列长度。
对于每个 s u m sum sum,用 p o s [ k ] pos[k] pos[k]表示数字 k k k在数组 A A A中最后出现的位置。遍历 A A A数组,设当前遍历到第 i i i个,若 s u m − A [ i ] sum - A[i] sumA[i]恰好在前面出现过,且 p o s [ s u m − A [ i ] ] pos[sum - A[i]] pos[sumA[i]]比已找好的 b b b序列最后一个元素还要大,即 p o s [ s u m − A [ i ] ] pos[sum - A[i]] pos[sumA[i]]还未被计入 b b b数组中,则可将 i i i也计入 b b b序列中。
其实这就是一个区间覆盖的经典贪心问题,等价于有很多的区间(配对上的位置是区间端点),区间之间会相互覆盖或相互包含,求最多的不相交区间数量,先将区间按照右端点排序,从左到右遍历区间,如果交到之前的区间就不选,否则就选。

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], pos[N], n;
int main()
{
    scanf("%d", &n);
    int mi = 1e9, mx = 0;
    for(int i = 1; i <= n; i ++) scanf("%d", &a[i]), mi = min(mi, a[i]), mx = max(mx, a[i]);
    mi *= 2, mx *= 2;
    int ans = 0;
    for(int i = mx; i >= mi; i --)
    {
        memset(pos, 0, sizeof pos);
        int last = 0, cnt = 0;
        for(int j = 1; j <= n; j ++)
        {
            if(i <= a[j]) continue;
            if(pos[i - a[j]] > last)
            {
                last = j;
                cnt += 2;
            }
            else pos[a[j]] = j;
        }
        ans = max(ans, cnt);
    }
    printf("%d\n", ans);
}

D - Math master(二进制枚举、细节)

思路

不难想到可以二进制枚举分子删掉了哪些数位,令删掉这些数位之后得到的新分子为 n p np np,则新分母 n q = n p ∗ q / p nq = np * q / p nq=npq/p。所以只需要看旧分母在删掉新分子相对旧分子删掉的那些数字可否等于 n q nq nq即可。

记录新分子相比于旧分子删掉的每个数字 i i i的个数 p s u b [ i ] psub[i] psub[i], 再记录 n q nq nq相比于旧分母删掉的每个数字 i i i的个数 q s u b [ i ] qsub[i] qsub[i],首先对于 1 1 1~ 9 9 9每个数字 i i i,均要有 p s u b [ i ] = q s u b [ i ] psub[i] = qsub[i] psub[i]=qsub[i],对于数字0,注意有前导0的问题,就是如果 q s u b [ i ] > p s u b [ i ] qsub[i] > psub[i] qsub[i]>psub[i],说明 n q nq nq现在含有的0数量太少了,和原来差距太大,因此含有前导0,因此先将 n q nq nq前面补足 q s u b [ i ] − p s u b [ i ] qsub[i] - psub[i] qsub[i]psub[i]个0。

最后将旧分母和 n q nq nq从前到后一一比较,如果某 i i i位不同,则旧分母应该将第 i i i位的数 d i g i t [ i ] digit[i] digit[i]删掉之后再继续比较,如果 q s u b [ d i g i t [ i ] ] qsub[digit[i]] qsub[digit[i]]已经是0了,则说明旧分母无法变成 n q nq nq,当前的新分子并不合法。

代码

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

LL p, q;
int n, m;
int P[20];
int Q[20];
int sub[10];

LL gcd(LL a, LL b)
{
    return !b ? a : gcd(b, a % b);
}

bool check(LL nq)
{
    for (int i = 0; i < m; i ++ )
    {
        if (nq && Q[i] == nq % 10)
        {
            nq /= 10;
            continue;
        }
        else if (nq == 0 && Q[i] == 0 && !sub[0]) continue;
        if (sub[Q[i]])
            sub[Q[i]] --;
        else return false;
    }
    for (int i = 0; i < 10; i ++ )
        if (sub[i]) return false;
    return true;
}

int main()
{
    int _;
    scanf("%d", &_);
    while(_ --)
    {
        scanf("%lld%lld", &p, &q);
        LL x = p;
        n = 0;
        while (x)
        {
            P[n ++] = x % 10;
            x /= 10;
        }
        x = q;
        m = 0;
        while (x)
        {
            Q[m ++] = x % 10;
            x /= 10;
        }
        
        LL g = gcd(p, q);
        LL ap = p, aq = q;
        for (int i = 1; i < (1 << n); i ++)
        {
            for (int j = 0; j < 10; j ++ )
                sub[j] = 0;
            
            LL newp = 0;
            for (int j = n - 1; j >= 0; j -- )
                if (i >> j & 1) newp = newp * 10 + P[j];
                else sub[P[j]] ++;
            
            if (newp == 0) continue;
            if (newp % (p / g)) continue;
            
            LL newq = (newp / (p / g)) * (q / g);
            if (!check(newq)) continue;

            ap = min(ap, newp);
            aq = min(aq, newq);
        }

        printf("%lld %lld\n", ap, aq);

    }
    return 0;
}

E - Power and Modulo(思维)

思路

模拟一下会发现,a[0]如果为0,则M=1,如果此时a[1]~a[n-1]不为0,则无解,-1;
a[0]如果>1,那么直接无解,-1;
否则a[0]=1时,M>=2,此时考虑a[1]的值,
若a[1]=0,则说明M=2,如果a[2]~a[n-1]不为0,则无解,-1;
否则a[1]=2时,M>=3,此时考虑a[2]的值。
通过模拟可以发现,当a[i] != a[i-1] * 2时(i>=1),M的值就可以确定。
如果此时M<=a[i-1]则无解
否则暴力往后判断是否成立即可
注意如果对于所有i都满足a[i] == a[i-1] * 2的话,也是无解

代码

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

typedef long long LL;

const int N = 100005;

int n;
int a[N];

int main()
{
    int _;
    scanf("%d", &_);
    while(_ --)
    {
        scanf("%d", &n);
        for (int i = 0; i < n; i ++ ) 
            scanf("%d", &a[i]);
        
        bool flag = true;

        int M = 0;
        if (a[0] == 0)
        {
            M = 1;
            for (int i = 1; i < n; i ++ )
                if (a[i])
                {
                    flag = false;
                    break;
                }
        }
        else if (a[0] > 1)
            flag = false;
        else 
        {
            int i;
            for (i = 1; i < n; i ++ )
                if (a[i] != a[i - 1] * 2) 
                {
                    M = 2 * a[i - 1] - a[i];
                    break;
                }
            if (M <= a[i - 1]) flag = false;
            else 
            {
                for (int i = 1; i < n; i ++ )
                if (a[i] != a[i - 1] * 2 % M)
                {
                    flag = false;
                    break;
                }
            }
        }
        

        if (!flag)
            puts("-1");
        else printf("%d\n", M);
    }
    return 0;
}

G - Damaged Bicycle(期望、状压dp)

思路

  1. 为了统一问题,如果起点或终点不是单车点,就把它们也加入单车点中,并设其单车坏掉的概率为100 %,并记录其在起点和终点在单车点中的下标 e 1 , e 2 e1, e2 e1,e2
  2. 进行简单预处理,将每个单车点作为单源点,用 d i j k s t r a dijkstra dijkstra预处理一下单车点之间的最短路, d i s t [ i ] [ j ] dist[i][j] dist[i][j]表示第 i i i个单车点到第 j j j个单车点的最短路。
  3. 单车点最多20个(加上起点和终点),因此一眼状压!开始乱设状态!二进制数 s t a t e state state i i i位为1,表示单车点 i i i被走过,第 i i i位为0,表示单车点 i i i未被走过,用 s t a t e state state作为 d p dp dp值下标, d p [ s t a t e ] dp[state] dp[state]表示到达状态 s t a t e state state的最短期望时间。
  4. 开始状态转移!思考找车过程,从点 i i i走到点 j j j会发生什么情况,会花费掉 d i s t [ i ] [ j ] / t dist[i][j] / t dist[i][j]/t的时间…过程的第一步就凉了,因为不知道状态的最后一个单车点是什么,因此扩展 d p dp dp表示维数, d p [ s t a t e ] [ i ] dp[state][i] dp[state][i]表示最后停在 i i i单车点,已经确定坏掉的单车是 s t a t e state state状态,到达 n n n点还需要的最短期望时间。
  5. 正式状态转移!如何更新 d p [ s t a t e ] [ i ] dp[state][i] dp[state][i]?有两种可能性:
    1. 发现 i i i点车是坏的,概率为 p [ i ] p[i] p[i],此时有两个选择
      1. 继续找下一个单车点,枚举下一个单车点 j j j,需要时间为 d i s t [ i ] [ j ] / t + d p [ j ] [ s t a t e ∣ ( 1 < < j ) ] dist[i][j] / t + dp[j][state | (1 << j)] dist[i][j]/t+dp[j][state(1<<j)]
      2. 直接从 i i i点走到终点,需要时间为 d i s t [ i ] [ i d [ n ] ] / t dist[i][id[n]] / t dist[i][id[n]]/t
      在1,2两种情况中选一个最小时间 m i mi mi
    2. j j j点车是好的,概率是 1 − p [ i ] 1 - p[i] 1p[i],直接骑到终点,时间加上 d i s t [ i ] [ e 2 ] / r dist[i][e2] / r dist[i][e2]/r
      因此 d p [ s t a t e ] [ i ] = p [ i ] ∗ m i + ( 1 − p [ i ] ) ∗ d i s t [ i ] [ e 2 ] / r ) dp[state][i] = p[i] * mi + (1 - p[i]) * dist[i][e2] / r) dp[state][i]=p[i]mi+(1p[i])dist[i][e2]/r)

代码

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 1e5 + 10;
int n, m, s, head[N], cnt, dis[20][N], inf = 0x3f3f3f3f;
bool vis[N];
double dp[(1 << 20)][20];
struct Node
{
    int pos, dis;
    bool operator<(const Node &x) const {return x.dis < dis;}
};
struct E
{
    int to, nxt, w;
}e[N << 1];

void add(int u, int v, int w)
{
    e[++ cnt] = {v, head[u], w};
    head[u] = cnt;
}
int p[20], idx[20], k, cur;
void dijkstra()
{
    priority_queue<Node> q;
    memset(dis[cur], 0x3f, sizeof dis[cur]);
    memset(vis, 0, sizeof vis);
    q.push({s, 0}); dis[cur][s] = 0;
    while(!q.empty())
    {
        int u = q.top().pos; q.pop();
        if(vis[u]) continue;
        vis[u] = true;
        for(int i = head[u]; i; i = e[i].nxt)
        {
            int v = e[i].to;
            if(vis[v]) continue;
            if(dis[cur][v] >  dis[cur][u] + e[i].w)
            {
                dis[cur][v] = dis[cur][u] + e[i].w;
                q.push({v, dis[cur][v]});
            }
        }
    }  
}
int main()
{
    #ifdef ZY
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
    #endif
    int t, r; scanf("%d%d", &t, &r);
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= m; i ++) 
    {
        int u, v, w; scanf("%d%d%d", &u, &v, &w);
        add(u, v, w), add(v, u, w);
    }
    scanf("%d", &k);
    int e1 = false, e2 = false;
    for(int i = 0; i < k; i ++)
    {
        scanf("%d%d", &idx[i], &p[i]);
        s = idx[i], cur = i;
        if(idx[i] == 1) e1 = i;
        if(idx[i] == n) e2 = i;
        dijkstra();
    }
    if(n == 1)
    {
        puts("0");
        return 0;
    }
    if(!e1) {idx[k] = 1, p[k] = 100, e1 = cur = k; s = 1; dijkstra(); k ++;}
    if(!e2) {idx[k] = n, p[k] = 100, e2 = cur = k; s = n; dijkstra(); k ++;}
    if(dis[e1][n] == inf)
    {
        puts("-1");
        return 0;
    }
    for(int i = (1 << k) - 1; i >= 0; i --) 
    {
        for(int j = 0; j < k; j ++) 
            if(!((i >> e2) & 1)) dp[i][j] = 1e18;
    }
    for(int i = (1 << k) - 1; i >= 1; i --)
    {
        bitset<3> b(i);
        if((i >> e2) & 1) continue;
        for(int j = 0; j < k; j ++) //枚举状态
        {
            if((!((i >> j) & 1)) || dis[j][n] == inf) continue;
            //当前状态下是有j单车点
            //枚举下一个状态
            double mi = 1.0 * dis[j][n] / t;
            dp[i][j] = 0.01 * (100 - p[j]) * dis[j][n] / r; 
            for(int s = 0; s < k; s ++)
            {
                if(((i >> s) & 1) || dis[s][n] == inf || dis[s][idx[j]] == inf) continue;
                mi = min(mi, dp[i | (1 << s)][s] + 1.0 * dis[j][idx[s]] / t);
            }
            dp[i][j] += mi * p[j] * 0.01;
            // cout << b << ' ' << j << ' ' << dp[i][j] << endl;
        }
    }   
    printf("%.10f", dp[(1 << e1)][e1]);
    return 0;
}

I - Power and Zero(暴力、贪心)

思路

首先问题可以转换为有31个数(对应31个二进制位),每个位可以从高位借位(二进制的借位方法),每次操作可以将位置从0开始的一段非0串(要求每个位都不为0)的每个位置减掉1。求最少操作次数。
考虑贪心,每次都减掉最长的一段,不够就向高位借位即可。

代码

#include <iostream>

using namespace std;

const int N = 100005;

int n;
int a[32];

int main()
{
    int _;
    scanf("%d", &_);
    while (_ --)
    {
        int n;
        scanf("%d", &n);
        for (int i = 0; i <= 30; i ++ )
            a[i] = 0;
        
        for (int i = 1; i <= n; i ++ )
        {
            int x;
            scanf("%d", &x);
            for (int j = 0; j <= 30; j ++ )
                if (x >> j & 1)
                    a[j] ++;
        }

        int sum = 0;
        for (int i = 0; i <= 30; i ++ )
            sum += a[i];
        int ans = 0;

        while(sum)
        {
            ans ++;
            for (int i = 0; i <= 30; i ++ )
            {
                if (a[i]) a[i] --, sum --;
                else 
                {
                    int j;
                    for (j = i + 1; j <= 30; j ++ )
                        if (a[j]) break;
                    if (j > 30)
                        break;
                    a[j] --, sum --;
                    for (int k = i; k < j; k ++ )
                        a[k] ++, sum ++;
                }
            }
        }

        printf("%d\n", ans);
    }
    return 0;
}

J - Local Minimum(签到)

思路

算出每一行每一列的最小值然后一个一个判断即可。

代码

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 1005;

int a[N][N];
int mir[N], mic[N];

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            scanf("%d", &a[i][j]);

    for (int i = 1; i <= n; i ++ )
        mir[i] = 1e9;
    for (int i = 1; i <= m; i ++ )
        mic[i] = 1e9;
    
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            mir[i] = min(mir[i], a[i][j]);
        
    for (int j = 1; j <= m; j ++ )
        for (int i = 1; i <= n; i ++ )
            mic[j] = min(mic[j], a[i][j]);

    int ans = 0;
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            if (min(mir[i], mic[j]) == a[i][j])
                ans ++;
    printf("%d\n", ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值