Codeforces Round #521 (Div. 3) (A+B+C+D+E+F1+F2)

A题 题目链接 (签到)

题意:给定q个询问,每次给出左右跳的距离,给出跳的次数k,规定先向右跳。
每次询问最终的位置。 (1<=q<=1e4)

思路:直接上就好

//#pragma comment(linker, "/STACK:102400000,102400000")//手动扩栈
#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>
#include<unordered_map>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=0x3f3f3f3f;
const double eps=1e-5;
const int maxn=2e5+7;

int main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
    int T;
    cin>>T;
    while(T--)
    {
        int a,b,k;
        LL ans=0;
        scanf("%d%d%d",&a,&b,&k);
        int times=k/2;
        ans+=1ll*times*(a-b);
        if(k&1) ans+=a;
        printf("%lld\n",ans);
    }
//    system("pause");
    return 0;
}

B题 题目链接 (贪心)

题意:给定一个0 1组成的数列,问最少把多少个不同的1改成0 可以使得不出现1 0 1 这种子段。
(1<=n<=2e5)
思路:从左到右遍历,对于遇到的两边都是1的0,我们把它右边的1改成0即可。

//#pragma comment(linker, "/STACK:102400000,102400000")//手动扩栈
#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>
#include<unordered_map>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=0x3f3f3f3f;
const double eps=1e-5;
const int maxn=2e2+7;
//题意:给定一个0 1组成的数列,问最少把多少个不同的1改成0 可以使得不出现1 0 1 这种子段
int n,a[maxn];
int main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
    scanf("%d",&n);
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",a+i);
    }
    for(int i=2;i<n;i++)
    {
        if(!a[i] && a[i-1] && a[i+1])
        {
            a[++i]=0;
            ans++;
        }
    }
    cout<<ans;
 //   system("pause");
    return 0;
}

C题 题目链接 (map+思维)

题意:给定一个数列,问是否可以去除1个元素使得剩余元素构成完美数列,完美数列指:存在一个元素是其余元素的和。求出满足条件的所有元素下标。
(1<=n<=2e5)

思路:完美数列的特点就是,数列和等于某个元素值的两倍。 用map把所有元素的两倍都存一下(如果这个值只出现一次,映射成下标,出现多次,映射成INF)。然后枚举去除每个元素,看其它元素的和是否在map中出现,且映射不是当前元素下标。

//#pragma comment(linker, "/STACK:102400000,102400000")//手动扩栈
#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>
#include<unordered_map>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=0x3f3f3f3f;
const double eps=1e-5;
const int maxn=1e6+7;

map<LL,int> mp;//存在多个就是INF 否则是index
LL sum=0;
int n,a[maxn];
vector<int> ans;
int main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",a+i);
        //把这个数的两倍存一下 如果某个数列的和就是这个数的两倍,并且这个数在数列中 
        if(!mp[a[i]*2ll]) mp[a[i]*2ll]=i;
        else mp[a[i]*2ll]=INF;
        sum+=a[i];
    }
    for(int i=1;i<=n;i++)
    {
        LL t=sum-a[i];
        int x=mp[t];
        //inf说明出现多次 ,映射为i的时候,说明其余元素的和是a[i]的两倍,不符合完美数列
        if(x==INF || (x && x!=i)) ans.push_back(i);
    }
    printf("%d\n",ans.size());
    for(auto i:ans) printf("%d ",i);
   // system("pause");
    return 0;
}

D题 题目链接 (贪心+优先队列)

题意:给定数列s,给定正整数k,问s中的元素最多能构成多少个长度为k,且元素 相同的数列t。s=[1,2,3,2,4,3,1] 能构成2个 [1,2,3]。
(1<=n,k<=2e5)

思路:
问题可以等价为:选k个数,每个数在数组中出现次数的最小值要最大。(如果k个当中有若干个数相同,那每个数出现次数为总次数/被选次数)

贪心,每次选出出现次数最大的数,如果被过了,那么次数要除以2再加入优先队列。
如果被选了x次,那么要除以x。
这题我们首先把s中的元素按出现次数放入优先队列。加一个times存每个元素被选择的次数,v表示在s中出现的次数。 根据贪心,每次我们选择v/times最大的,然后把对应元素的times+1,重新放入优先队列,循环k次即可。

//#pragma comment(linker, "/STACK:102400000,102400000")//手动扩栈
#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>
#include<unordered_map>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=0x3f3f3f3f;
const double eps=1e-5;
const int maxn=2e5+7;
int book[maxn];
int n,k,a[maxn];
struct pr
{
    int v,index;//出现次数和数列元素
    int times;//被选择的次数
    friend bool operator < (const pr &a,const pr &b)
    {
        return a.v/a.times<b.v/b.times;
    }
}p[maxn];
int cot;
vector<int> ans;
priority_queue<pr> q;
int main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",a+i);
        book[a[i]]++;
    }
    for(int i=1;i<=200000;i++)
    {
        pr t;
        if(book[i])
        {
            t.v=book[i];//出现次数
            t.index=i;//index为数列元素
            t.times=1;//初始化成第一次选择
            q.push(t);
        }
    }
    while(k--)
    {
        pr t=q.top();
        q.pop();
        ans.push_back(t.index);//index存的是对于的元素
        t.times++;//选择的次数+1
        q.push(t);
    }
    for(auto i:ans) printf("%d ",i);
//    system("pause");
    return 0;
}

E题 题目链接(二分 / dp)

题意:给出n个题目,每个题目都有一个主题,现在让你安排若干场考试,每场考试的题目数量恰好是前一场的2倍,并且

他们的主题是相同的,问最多能用掉多少题目?

思路:先把数列元素离散化,按个数递增排序。接下来有两种做法。
①:二分。 从1开始枚举初始场的题目,最大为mmax,即所有种类中最多的题目数,设为i。 然后二分找2i 4i…,如果没找到,就break。 更新ans。
时间复杂度为nlogn

//#pragma comment(linker, "/STACK:102400000,102400000")//????
#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>
#include<unordered_map>
using namespace std;
#define LL long long
#define ULL unsigned long long
const LL INF=1e18+7;
const double eps=1e-5;
const int maxn=2e5+7;
//对于每个比赛 题目主题相同且为比赛主题
//不同比赛的主题不同
map<int,int> mp;
int n;
vector<int> a;
int ans=0;
int main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int t;
        scanf("%d",&t);
        mp[t]++;
    }
    for(auto i:mp) a.push_back(i.second);
    sort(a.begin(),a.end());
    int len=(int)a.size(),mmax=a[len-1];
    for(int i=1;i<=mmax;i++)
    {
        int st=i,tmax=0;
        int in=0;//记录二分找的位置
        for(;st<=mmax;st*=2)
        {
            int pos=lower_bound(a.begin()+in,a.end(),st)-a.begin();
            if(in==len) break;
            tmax+=st;
            in=pos+1;//更新下次二分的初始位置
        }
        ans=max(ans,tmax);
    }
    cout<<ans;
//    system("pause");
    return 0;
}

②:dp。 记排序后题目种类数为0~len-1 ,dp[i][j]表示只考虑[i,len-1],第i种题目作为初始场并选j题的状态。

可以滚动数组优化第一维
dp[j]=dp[j<<1]+j

 sort(a.begin(),a.end());
    int len=a.size();
    for(int i=len-1;i>=0;i--)
    {
        for(int j=1;j<=a[i];j++)
        {
            dp[j]=j+dp[j<<1];
            ans=max(ans,dp[j]);
        }
    }
    cout<<ans;
//#pragma comment(linker, "/STACK:102400000,102400000")//????
#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>
#include<unordered_map>
using namespace std;
#define LL long long
#define ULL unsigned long long
const LL INF=1e18+7;
const double eps=1e-5;
const int maxn=2e5+7;
//对于每个比赛 题目主题相同且为比赛主题
//不同比赛的主题不同
map<int,int> mp;
int n;
vector<int> a;
int ans=0;
int dp[maxn<<1];//从后往前更新 dp[i][j]=dp[i+1][j*2]+j
int main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int t;
        scanf("%d",&t);
        mp[t]++;
    }
    for(auto i:mp) a.push_back(i.second);
    sort(a.begin(),a.end());
    int len=a.size();
    for(int i=len-1;i>=0;i--)
    {
        for(int j=1;j<=a[i];j++)
        {
            dp[j]=j+dp[j<<1];
            ans=max(ans,dp[j]);
        }
    }
    cout<<ans;
   // system("pause");
    return 0;
}

F题
题目链接 (单调队列优化dp)

(F1 F2都是一样的题,不过F1的数据更小可以暴力,F2需要用单调队列)

题意:给定长度为n的数列,取出x个数,要求每个长度为k的连续子段都要有取到,问x个数的和最大为多少。

思路:这题和烽火传递思路基本一样。只不过有x限制,需要加一维。

dp[i][j]表示只考虑前i个数,第i个数取,并且包括i在内已经取了j个数的情况。
转移方程: dp[i][j]=max(a[i]+dp[m][j-1],dp[i][j]) ( i-k<=m<=i-1 )
m的范围很好理解,比如i=7,k=3,那么在我们考虑i=7之前,4 5 6必须取一个,所以就是i-k<=m<=i-1

for(int i=2;i<=n;i++)
    {
        for(int j=1;j<=min(i,x);j++)
        {
            for(int m=max(0,i-k);m<=i-1;m++)
            {
                dp[i][j]=max(dp[i][j],dp[m][j-1]+a[i]);
            }
        }
    }

这样的话时间复杂度最高是O(n²)

在求[i-k,i-1]上dp的最大值我们可以用上单调队列进行维护。

//#pragma comment(linker, "/STACK:102400000,102400000")//手动扩栈
#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>
#include<deque>
#include<unordered_map>
using namespace std;
#define LL long long
#define ULL unsigned long long
const LL INF=1e18;
const double eps=1e-5;
const int maxn=5e3+7;
//题意:给定长度为n的数列,取出x个数,要求每个长度为k的连续子段都要有取到,问x个数的和最大为多少
LL dp[maxn][maxn];
//dp[i][j]=max(dp[l][j-1],dp[i][j]) i-k<=l<=i-1 
int n,k,x,a[maxn];
deque<int> q;
int main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
    scanf("%d%d%d",&n,&k,&x);
    for(int i=1;i<=n;i++) scanf("%d",a+i);
    fill(dp[0],dp[0]+maxn*maxn,-INF);
    dp[0][0]=0;//0的边界不能丢
    for(int j=1;j<=x;j++)
    {
        q.clear();
        q.push_back(0);//队列初始不能为空
        for(int i=1;i<=n;i++)
        {
            dp[i][j]=max(dp[i][j],dp[q.front()][j-1]+a[i]);
            //i入队时,先把小于它的丢出去
            while(q.size() && dp[q.back()][j-1]<dp[i][j-1]) q.pop_back();
            q.push_back(i);
            //出队,把太久远的也丢出去
            while(q.size() && q.front()<i+1-k) q.pop_front();
        }
    }
    LL ans=-1;
    for(int i=n;i>n-k;i--) ans=max(ans,dp[i][x]);
    cout<<ans;
//    system("pause");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值