Codeforces Round #490 (Div. 3) (A+B+C+D+E+F)

蒟蒻补完整的第一场cf QAQ

A题 题目链接(模拟+签到)
题意:给你一个长度为N个序列,你可以每次从最左边或者最右边删除一个小于等于k的数字,问你最多能删多少个数字。 数据N,K<=100

直接上代码,不用解释。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
using namespace std;
#define LL long long
#define ULL unsigned long long
const LL INF=1e15;
const double eps=1e-5;
const int maxn=6e3+10;

int main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
    int n,k;
    int ans=0;
    int a[105];
    scanf("%d%d",&n,&k);
    int p1=1,p2=n;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",a+i);
    }
    while(p1<=p2)
    {
        if(a[p1]<=k)
        {
            p1++;
            ans++;
        }
        else
        {
            if(a[p2]<=k)
            {
                p2--;
                ans++;
            }
            else break;
        }      
    }
    printf("%d",ans);
 //   system("pause");
    return 0;
}

B题 题目链接 (模拟+签到)

题目大意:给定一个字符串,长度为D,我们取D的每个因数(包括自身),从大到小开始,反转字符串的1~d位置,比如D=10, s=“codeforces” ,d分别取 10 5 2 1
“codeforces” → “secrofedoc” → “orcesfedoc” → “rocesfedoc” → “rocesfedoc”
现在给你一个反转后的字符串,求原串。 D<=100

思路:看数据也是不难,直接用栈存D的因数,然后模拟reverse即可

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=3000;
const double eps=1e-5;
const int maxn=1e6+50;
char s[105];
int n;
stack<int> d;
int main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
    scanf("%d %s",&n,s+1);
    for(int i=n;i>=2;i--)
    {
        if(n%i==0) d.push(i);
    }
    while(!d.empty())
    {
        int x=d.top();
        d.pop();
        reverse(s+1,s+1+x);
    }
    printf("%s",s+1);
//    system("pause");
    return 0;
}

C题 题目链接 (模拟+签到)

题目大意:给定一个小写字母组成的字符串,一个数k,要求按字典序优先级删去k个字符,并且优先删去左边的(a删完了删b,b删完了删c…)。 求删完后的字符串。
思路:拿数组存一下各字母的个数,然后按abcd…顺序遍历,删除即可

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=3000;
const double eps=1e-5;
const int maxn=4e5+50;
char s[maxn];
char ans[maxn];
int cnt;
int n,k;
int book[128];
int main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
    scanf("%d%d%s",&n,&k,s+1);
    for(int i=1;i<=n;i++)
    {
        book[s[i]]++;
    }
    for(int i='a';i<='z';i++)
    {
        if(book[i]>=k)//k耗尽了
        {
            book[i]-=k;
            k=0;
            break;
        }
        else
        {
            k-=book[i];
            book[i]=0;
        }
    }
    for(int i=n;i>=1;i--)//因为优先删左边的,所以从右边开始遍历原串
    //如果这个字符还有剩余,就加给ans串,同时数目--
    {
        if(book[s[i]])
        {
            book[s[i]]--;
            ans[cnt++]=s[i];
        }
    }
    reverse(ans,ans+cnt);//ans串是反的,要反转一下
    printf("%s",ans);
//    system("pause");
    return 0;
}

这场开始40分钟写完了ABC,结束时也只写完了ABC= =、 自闭了1个多小时搞D E题,我太菜了QAQ 。看了题解发现后面三题其实也不难…

D题 题目链接(set+贪心+二分)

这题题意弄清楚了其实不难想qaq
给定n*m个数,每个数可以操作无数次,每次操作可以+1,要求最少操作次数,
满足操作完成后数组元素可以平均分成m组,第i组的所有数%m都为i-1

思路:用set维护没有凑够元素个数的余数

对于每个数a[i] ,设a[i]的余数为t,如果t大于set中任意一个数,就找出set中最小的那个元素x(贪心,因为a[i]只能加1,不能减),

否则从set二分找出第一个大于等于t的余数x,让a[i]%m的余数变成x,操作次数就是(x-t+m)%m。 如果某个余数凑够了n/m个,就把它从set中删了

( 为什么x<t的时候操作次数也是这个?可以设一些数据,草稿演算一下。)

比如m=5,x=0,t=3
0 1 2 3 4 0 1 2 3 4 0 ……
t到x的最短距离就x+m-t 了,再对m取模防止出现大于等于m的数

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=3000;
const double eps=1e-5;
const int maxn=2e5+50;
int n,m;
int num[maxn];//余数是i的个数
int k;
LL ans;
set<int> s;
int a[maxn];
//给定n*m个数,每个数可以操作无数次,每次操作可以+1,要求操作最少次数,
//满足可以平均分成m组 第i组的所有数%m都为i-1
//思路:用set维护没有凑够元素个数的余数
//对于每个数a[i] 设a[i]的余数为t,如果t大于set中任意一个数,就找出set中最小的那个(贪心),
//否则从set二分找出第一个大于等于t的余数,让这个余数的元素个数+1 ,够n/m就从set中删了
int main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
    scanf("%d%d",&n,&m);
    k=n/m;
    for(int i=0;i<m;i++) s.insert(i);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",a+i);
    }
    for(int i=1;i<=n;i++)
    {
        int t=a[i]%m;
        int x;
        if(t>(*s.rbegin())) x=*s.begin();//比所有的都大,取最小的余数
        else x=*s.lower_bound(t);//否则取第一个大于等于它的余数
        if(++num[x]==k) s.erase(x);//凑够了n/m个
        ans+=(x-t+m)%m;
        a[i]+=(x-t+m)%m;
    }
    cout<<ans<<'\n';
    for(int i=1;i<=n;i++) cout<<a[i]<<' ';
   // system("pause");
    return 0;
}

E题 题目链接(强连通分量+缩点)

题意:给定一个有向图,起始点s,问最少还要修建多少条有向边,才能使得s可以到达其它所有的顶点

这题一开始以为可以dfs跑一下,记录没访问过的点,然后从没访问过的点开始dfs,求出最少从几个没访问过的点开始dfs可以搜完所有没访问过的点,但是没搞出来QAQ

查题解发现自己就是个黛比,这么裸的强连通分量缩点都没看出来

思路:先用tarjan跑一遍,缩点,记录每个分量点的入度,除s所在的分量点之外,入度为0的分量点数就是答案。 (因为对于每个入度不为0的SCC,取进入这个SCC的SCC就好了233

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=3000;
const double eps=1e-5;
const int maxn=5e3+50;
struct
{
    int to,next;
}edge[maxn];
int head[maxn],cnt=1;//链式前向星建图
int n,e,s;//s是出发点
stack<int> st;
int belong[maxn];//点i属于哪个强连通分量
vector<int> scc_g[maxn];//存每个SCC包括了哪些顶点
int indu[maxn];//每个强连通分量的出度,用于缩点,成为有向无环图
int step=0;//时间戳
int cont=0;//强连通分量的个数 从1开始
bool instack[maxn];//标记是否在栈中
int DFN[maxn];//顶点i的进栈时间戳、次序号(就是第几个被搜到的),初始化成0表示还未进栈,定了就不会变了
int low[maxn];
void add(int from,int to)
{
    edge[cnt].to=to;
    edge[cnt].next=head[from];
    head[from]=cnt++;
}
void tarjan(int x)
{
    DFN[x]=low[x]=++step;
    st.push(x);
    instack[x]=true;
    for(int i=head[x];i;i=edge[i].next)
    {
        int to=edge[i].to;
        if(!DFN[to])
        {
            tarjan(to);
            low[x]=min(low[x],low[to]);
        }
        else if(instack[to])//早已经入过栈且已经出栈了
        {
            low[x]=min(low[x],DFN[to]);
        }
    }
    if(DFN[x]==low[x])//以x为根的搜索树是强连通分量
    {
        cont++;
        while(1)//疯狂出栈 直到把x也出了
        {
            int temp=st.top();
            st.pop();
            instack[temp]=false;
            belong[temp]=cont;
            scc_g[cont].push_back(temp);
            if(temp==x) break;
        }
    }
}
void getpoint()
{
    for(int i=1;i<=n;i++)//链式前向星遍历方法 ,枚举出发点,出发点的每条边
    {
        for(int j=head[i];j;j=edge[j].next)
        {
            int to=edge[j].to;
            if(belong[i]!=belong[to]) indu[belong[to]]++;
        }
    }
}
int main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
    scanf("%d%d%d",&n,&e,&s);
    for(int i=1;i<=e;i++)
    {
        int v,u;
        scanf("%d%d",&v,&u);
        add(v,u);
    }
    for(int i=1;i<=n;i++) 
    {
        if(!DFN[i]) tarjan(i);
    }
    getpoint();//缩点
    int ans=0;
    indu[belong[s]]++;//保证s所在 入度不为0
    for(int i=1;i<=cont;i++) 
    {
        if(!indu[i]) ans++;
    }
    printf("%d",ans);
//    system("pause");
    return 0;
}

F题 题目链接 (思维+01背包)

题意:给定n*k个卡牌,每张牌标有数字,n个人每个人都有自己的幸运数字,每人发k张牌,
对于每个人,得到x张幸运数字卡可以获得joy[x]的快乐值 ,且joy数组单调递增,问最大分配价值

(拿到题莫名有种01背包的感觉,但是就是不晓得咋下手orz,果然太菜了。 )

思路:由贪心可知,只有把卡牌分给幸运数字对应的人,才能创造最优价值。
我们记dp[i][j]为 对于某种幸运数字L,i个人分配j个卡牌的最优解(i个人的幸运数字都是L,卡牌数字也都是L,也就是把每个幸运数字分开解决)。
我们不重复地枚举每个幸运数字L,计算dp[i][j] 最终加起来就是答案
这样的话就是个比较好写的01背包

dp[i][j]=max(dp[i][j],dp[i-1][j-k]+joy[k]) (第i个人取k个数字,枚举k即可

先上开二维数组的代码,时间1600ms
当然可以01背包滚动数组优化,省略一维,这样时间是40ms,不懂为啥差别那么大…以后就开一维了

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=3000;
const double eps=1e-5;
const int maxn=5e2+50;
int n,k;
int number[maxn];//各自幸运数字
int joy[maxn];//得到i个  获得的快乐值
int num[100005];//各种数字 各有几张
int stu[100005];//各种数字 都有几个人
//给定n*k个卡牌,标数,n个人每个人都有自己的幸运数字,每人发k张牌,
//得x张幸运数字卡可以获得joy[x]的快乐值 问最大分配价值
int dp[maxn][5005];//第二维是500*10
//由贪心可知应该把所有的卡分给幸运数字与卡片数字相应的人
//dp[i][j] 表示 i个幸运数字相同的人 分j个幸运数字卡 最优解
//dp[i][j]= max(dp[i-1][j-k]+joy[k]) 枚举k
LL ans;
int main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n*k;i++)
    {
        int t;
        scanf("%d",&t);
        num[t]++;
    }
    for(int i=1;i<=n;i++)
    {
        scanf("%d",number+i);
        stu[number[i]]++;
    }
    for(int i=1;i<=k;i++) scanf("%d",joy+i);
    for(int L=0;L<=100000;L++)
    {
        if(num[L] && stu[L])//这个幸运数字的卡 和 人 都有
        {
            memset(dp,0,sizeof(dp));
            for(int i=1;i<=stu[L];i++)
            {
                for(int j=1;j<=num[L];j++)
                {
                    for(int m=0;m<=min(j,k);m++)
                    {
                        dp[i][j]=max(dp[i][j],dp[i-1][j-m]+joy[m]);
                    }
                }
            }
            ans+=dp[stu[L]][num[L]];
        }
    }
    printf("%lld\n",ans);
//    system("pause");
    return 0;
}
时间1600ms
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=3000;
const double eps=1e-5;
const int maxn=5e2+50;
int n,k;
int joy[maxn];//得到i个  获得的快乐值
int num[100005];//各种数字 各有几张
int stu[100005];//各种数字 都有几个人
//给定n*k个卡牌,标数,n个人每个人都有自己的幸运数字,每人发k张牌,
//得x张幸运数字卡可以获得joy[x]的快乐值 问最大分配价值
int dp[5001];
//由贪心可知应该把所有的卡分给幸运数字与卡片数字相应的人
//dp[i][j] 表示 i个幸运数字相同的人 分j个幸运数字卡 最优解
//dp[i][j]= max(dp[i-1][j-k]+joy[k]) 枚举k
LL ans;
int main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n*k;i++)
    {
        int t;
        scanf("%d",&t);
        num[t]++;
    }
    for(int i=1;i<=n;i++)
    {
        int t;
        scanf("%d",&t);
        stu[t]++;
    }
    for(int i=1;i<=k;i++) scanf("%d",joy+i);
    for(int L=0;L<=100000;L++)
    {
        if(num[L] && stu[L])//这个数字的卡 和 人 都有
        {
            memset(dp,0,sizeof(dp));
            for(int i=1;i<=stu[L];i++)
            {
                for(int j=num[L];j;j--)//记得逆序
                {
                    for(int m=0;m<=min(j,k);m++)
                    {
                        dp[j]=max(dp[j],dp[j-m]+joy[m]);
                    }
                }
            }
            ans+=dp[num[L]];
        }
    }
    printf("%lld\n",ans);
//    system("pause");
    return 0;
}

至此,这场cf的题算是全部补完了,在感慨自己太菜之余还是学到了一些东西的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值