Codeforces Round #436 (Div. 2) 做题总结

比赛链接


A. Fair Game
题目链接

题意:给你 n n n个数,问能否将 n n n个数分成两组,每一组里的数完全相同,且两个组包含的数的数目相同。

思路:模拟即可。

代码

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

const int A = 200 + 10;
int vis[A],val[A];

int main(){
    int tot = 0;
    int n;scanf("%d",&n);
    for(int i=1 ;i<=n ;i++){
        int x;scanf("%d",&x);
        if(!vis[x]){
            val[++tot] = x;
        }
        vis[x]++;
    }
    if(tot == 2 && vis[val[1]] == vis[val[2]]){
        puts("YES");
        printf("%d %d\n",val[1],val[2]);
    }
    else puts("NO");


    return 0;
}

B. Polycarp and Letters
题目链接

题意
给你一个字符串,求某些特殊的位置 A A A满足:
1. A A A位置的字符为小写字母且任意两个不同的 A A A位置的小写字母不同。
2.将所有 A A A位置从左到右排序后,任意相邻两个 A A A位置之间不含大写字母。
A A A位置最多能有多少?

思路
由条件 2 2 2,可以以大写字母为界限将字符串分成若干个子串,对于每个子串单独计算答案。
考虑遍历每个子串,看其中有多少种不同的小写字母出现。
所有子串的答案取一个 m a x max max即可。

代码

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

const int A = 200 + 10;
char s[A];
bool vis[A];

int calc(int S,int T){
    int res = 0;
    memset(vis,0,sizeof(vis));
    for(int i=S ;i<=T ;i++){
        if(!vis[s[i]-'a']) res++;
        vis[s[i]-'a'] = 1;
    }
    return res;
}

int main(){
    int n;
    scanf("%d",&n);
    scanf("%s",s);
    int ans = 0;
    for(int i=0 ;i<n ;){
        if(s[i]>='A' && s[i]<='Z'){
            i++;
            continue;
        }
        int j;
        for(j=i+1 ;j<n ;j++){
            if(s[j]>='A' && s[j]<='Z'){
                break;
            }
        }
        ans = max(ans,calc(i,j-1));
        i = j+1;
    }
    printf("%d\n",ans);
    return 0;
}

C. Bus
题目链接

题意:
一辆车行驶在一条直线上,起点坐标为 0 0 0,终点坐标为 a a a
汽车从起点到终点,或者从终点到起点称为一次旅途。

汽车每行驶一个单位会消耗一升汽油,一开始汽车有 b b b升汽油。
在坐标 f f f处有一个加油站,汽车每次经过加油站可以选择路过或者把油加满。

问汽车从起点出发,完成 k k k次旅途,最少需要加几次油。

思路:
开始分类讨论了很久,虽然存在很多重复状态,但思路并没有真正理清楚。

后来考虑到 k k k很小,便直接模拟,每当汽车经过加油站时,判断一下当前的油量能否支撑它下一次回到加油站。
贪心即可。

注意特判开始和最后的状态。

代码:

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

int main(){
    int a,b,f,k;
    scanf("%d%d%d%d",&a,&b,&f,&k);
    if(b<f){
        puts("-1");
        return 0;
    }
    int now = b - f;
    int ans = 0;
    for(int i=1 ;i<k ;i++){
        int need = i&1?2*(a-f):2*f;
        if(need > b){
            puts("-1");
            return 0;
        }
        if(need>now){
            ans++;
            now = b - need;
        }
        else{
            now -= need;
        }
    }
    int need = k&1?(a-f):f;
    if(need>b){
        puts("-1");return 0;
    }
    if(need>now) ans++;
    printf("%d\n",ans);
    return 0;
}

D. Make a Permutation!
题目链接

题意
n n n个数,每个数的值 ( 1 &lt; = v a l &lt; = n ) (1&lt;=val&lt;=n) (1<=val<=n),现在能改变任意一个元素为任意值,问最少需要改变多少个数,使改变后这 n n n个数为 1 1 1~ n n n的一个排列。
若有多种改变方式,则输出改变之后字典序最小的排列。

思路
很明显,如果某个数出现了 c n t cnt cnt次(cnt>1),则需要将剩下的 c n t − 1 cnt-1 cnt1个数改变成没有出现的数。
另外,若有多个没有出现的数,则需要改变的位置越靠前就越应该贪心地填入小的数。

考虑使用优先队列来模拟上面的过程。

另外注意判断一下,若某个数出现了 c n t cnt cnt次,则需要改变的那 c n t − 1 cnt-1 cnt1个位置是任意的,那一个位置不改变需要和队列顶元素进行判断。

代码

#include<cmath>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

const int A = 2e5 + 10;
int vis[A],a[A];
bool Bi[A];
priority_queue<int> que;

int main(){
    int n;scanf("%d",&n);
    for(int i=1 ;i<=n ;i++){
        scanf("%d",&a[i]);
        vis[a[i]]++;
    }
    int tot = 0;
    for(int i=1 ;i<=n ;i++){
        if(!vis[i]) que.push(-i);
    }

    for(int i=1 ;i<=n ;i++){
        if(vis[a[i]]>1){
            int u = -que.top();
            if(!Bi[a[i]] && a[i]<u){
                Bi[a[i]] = 1;
                continue;
            }
            vis[a[i]]--;
            que.pop();
            a[i] = u;
            tot++;
        }
    }
    printf("%d\n",tot);
    for(int i=1 ;i<=n ;i++){
        printf("%d%c",a[i],i==n?'\n':' ');
    }

    return 0;
}

E. Fire
题目链接

题意
一场火灾中,有 n n n件物品,取出它需要花费 t i t_i ti的时间,然后能够得到 v i v_i vi的价值,但每件物品一旦时间超过了 d i d_i di则彻底损坏。
问能取得的最大价值是多少,并输出取的策略。

思路
考虑对时间进行DP。
如果存在最大价值的方案,由贪心的思想,其中彻底损坏时间最小的一定可以最先取。

将物品按损坏时间排序后,就变成了经典的01背包问题。
记录一下DP路径然后输出即可。

代码

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;

const int A = 2e3 + 10;
int dp[A],pre[A];
class P{
public:
    int t,d,p,id;
    bool operator<(const P& rhs) const{
        return d < rhs.d;
    }
}a[110];
int tot,Ans[110];
bool vis[A][A];

int main(){
    //freopen("output","w",stdout);
    int n;scanf("%d",&n);
    for(int i=1 ;i<=n ;i++){
        scanf("%d%d%d",&a[i].t,&a[i].d,&a[i].p);
        a[i].id = i;
    }
    sort(a+1,a+n+1);
    for(int i=1 ;i<=n ;i++){
        for(int j=a[i].d ;j-a[i].t>=1 ;j--){
            if(dp[j-a[i].t] + a[i].p > dp[j]){
                dp[j] = dp[j-a[i].t] + a[i].p;
                vis[i][j] = 1;
            }
        }
    }
    int Mx = 0,now = 0;
    for(int i=0 ;i<A ;i++){
        if(dp[i]>=Mx){
            Mx = dp[i];
            now = i;
        }
    }

    tot = 0;
    for(int i=n ;i>=1 ;i--){
        if(vis[i][now]){
            Ans[++tot] = a[i].id;
            now -= a[i].t;
        }
    }
    printf("%d\n",Mx);
    printf("%d\n",tot);
    for(int i=tot ;i>=1 ;i--){
        printf("%d%c",Ans[i],i==1?'\n':' ');
    }
    return 0;
}

F. Cities Excursions
题目链接

题意
n n n个点, m m m条边的有向图,给出 q q q个询问 s , t , k s,t,k s,t,k
问从 s s s t t t的所有路径中字典序最小的那条路径上第 k k k个点是多少。

思路
(参考官网题解)

q q q很大而 n n n很小,故很多询问一定有重复的 s s s或者 t t t,考虑离线打包一起处理。

遍历每一个 t t t,可以反向建图后 d f s dfs dfs,得出所有合法的,即从 s s s t t t存在可达路径的 s s s

因为每个 s s s都是按照最优策略,即可选路径中字典序最小的路径走,故所有合法的 s s s t t t构成了一棵以 t t t为根的生成树,然后从 s s s t t t的第 k k k个节点,就成了求 s s s节点在这棵树上的第 k k k层祖先。

考虑倍增优化该过程。

代码

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;

const int A = 3e3 + 10;
const int B = 4e5 + 10;
class Gra{
public:
    int v,next;
}G[A];
int head[A],tot;
class Qu{
public:
    int v,next,k,id;
}Q[B];
int Q_head[B],Q_tot,dp[A][15],Ans[B];
int n,m,q;

void init(){
    memset(head,-1,sizeof(head));
    memset(Q_head,-1,sizeof(Q_head));
}

void add_G(int u,int v){
    G[tot].v = v;
    G[tot].next = head[u];
    head[u] = tot++;
}

void add_Q(int u,int v,int k,int id){
    Q[Q_tot].v = v;
    Q[Q_tot].id = id;
    Q[Q_tot].k = k;
    Q[Q_tot].next = Q_head[u];
    Q_head[u] = Q_tot++;
}

void dfs(int u,int tp){
    for(int i=head[u] ;i!=-1 ;i=G[i].next){
        int v = G[i].v;
        if(v!=tp && (!dp[v][0] || dp[v][0]>u)){
            dp[v][0] = u;
            dfs(v,tp);
        }
    }
}

int main(){
    init();
    scanf("%d%d%d",&n,&m,&q);
    for(int i=1 ;i<=m ;i++){
        int u,v;scanf("%d%d",&u,&v);
        add_G(v,u);
    }
    for(int i=1 ;i<=q ;i++){
        int u,v,k;
        scanf("%d%d%d",&u,&v,&k);
        add_Q(v,u,k,i);
    }
    int Mx = (int)(log(1.0*n)/log(2.0)) + 2;
    for(int i=1 ;i<=n ;i++){
        memset(dp,0,sizeof(dp));
        dfs(i,i);
        for(int k=1 ;k<=Mx ;k++){
            for(int j=1 ;j<=n ;j++){
                dp[j][k] = dp[dp[j][k-1]][k-1];
            }
        }

        for(int j=Q_head[i] ;j!=-1 ;j=Q[j].next){
            int u = Q[j].v,k = Q[j].k-1,id = Q[j].id;
            if(!dp[u][0] || dp[u][Mx]){
                Ans[id] = -1;continue;
            }
            int dep = 0;
            while(k){
                if(k&1) u = dp[u][dep];
                k >>= 1;
                dep++;
            }
            Ans[id] = u?u:-1;
        }
    }
    for(int i=1 ;i<=q ;i++){
        printf("%d\n",Ans[i]);
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值