SDUT 2016级 CF 每周题集 Round 2

来源:互联网 发布:php api接口参数加密 编辑:程序博客网 时间:2024/06/05 04:16

题集链接

额外题目链接:
最短路存储
离散化ST

A:
26个字母的循环,找最小拨钟次数,找出三种拨钟方法的最小值(取正值)

#include <iostream>#include<bits/stdc++.h>using namespace std;char a[4845];int main(){    scanf("%s",a+1);    int sum = 0;    a[0] = 'a';       int len = strlen(a);    for(int i=1;i<len;i++)    {        sum+=min(abs(a[i]-a[i-1]),min(abs(a[i]+26-a[i-1]),abs(a[i-1]+26-a[i])));    }    cout<<sum<<endl;    return 0;}

B:[CodeForces-731B] [Problem B]
节目制作的竞争季已经开始,是时候为ICPC进行训练了。Sereja教练他的团队已经有一年的时间了,他知道为了准备参加训练课,仅仅准备问题和编辑是不够的。随着训练持续几个小时,团队就会感到饥饿。因此,Sereja订购了一些披萨,这样他们就可以在比赛结束后吃到。

团队计划连续训练n次。在培训过程中,Sereja为每一个今天在场的团队订了一个披萨。他已经知道在第i天将会有人工智能团队。

Sereja最喜欢的披萨店有两种折扣。第一个折扣是在一天买两个披萨的时候,而第二个是一个优惠券,允许在连续两天内买一个披萨(总共两个披萨)。

由于Sereja在这个地方真的有很多披萨,他是黄金客户,可以在任何时候使用任意类型的折扣和优惠券。

Sereja想要在第i天订一个完全的ai pizzas,而只使用折扣和优惠券。注意,他永远不会买比他今天需要的更多的披萨。帮助他确定,如果他只允许使用优惠券和折扣,他是否可以每天购买适当数量的比萨饼。注意,在一天结束后,它也被禁止拥有任何活跃的优惠券。

输入

输入的第一行包含一个整数n≤n≤200(200)-训练的数量。

第二行包含n个整数,a1,a2,…,一个ai(0≤≤10 000)——团队的数量将出现在每一天。

输出

如果有一种方法可以让披萨只使用优惠券和折扣,而且在任何时候都不买任何额外的披萨,那么在唯一的输出线上打印“YES”(没有引号)。否则,打印“不”(没有引号)。

在第一个示例中,sereja可以使用优惠券在第一和第二天买一个比萨饼,比萨饼优惠券买第二天和第三天,和一个折扣,在第四天买披萨。这是订购这种样品的比萨饼的唯一方法。

第二样,sereja不能使用优惠券和折扣都没有订购额外的比萨饼。请注意,有可能在几天内没有参加训练班的球队。

Input
4
1 2 1 2
Output
YES
Input
3
1 0 1
Output
NO

#include <iostream>#include<bits/stdc++.h>using namespace std;int  a[484455];int main(){    int n;    cin>>n;    for(int i=1;i<=n;i++)        scanf("%d",&a[i]);     int res =0,f = 1;     for(int i=1;i<=n;i++)     {         if(res==1&&a[i]==0)         {f = 0;break;}         res = (res+a[i])%2;     }     if(f&&res==0)printf("YES\n");     else printf("NO\n");    return 0;}

C:
参考1
参考2

#include <iostream>#include<bits/stdc++.h>using namespace std;int n,m,k;int tot,ans;int f[300000],num[300000],col[300000];vector<int>v[300000];int getf(int x){    if(f[x]==x)    return x;    else    {        f[x] = getf(f[x]);        return f[x];    }}void merge(int a,int b){    int aa = getf(a);    int bb = getf(b);    if(aa!=bb)    {        f[bb] = aa;    }}int main(){     cin>>n>>m>>k;     int a,b;     for(int i=1;i<=n;i++)     {         scanf("%d",&col[i]);     }     for(int i=1;i<=n;i++)       f[i] = i;     for(int i=1;i<=m;i++)     {         scanf("%d%d",&a,&b);         merge(a,b);     }     for(int i=1;i<=n;i++)     {         if(f[i]==i)num[i] = ++tot;     }     for(int i=1;i<=n;i++)     {         v[num[getf(i)]].push_back(col[i]);     }     for(int i=1;i<=tot;i++)     {         int sz=v[i].size();         int mx = 0;         map<int,int>mp;         for(int j=0;j<sz;j++)         {             mp[v[i][j]]++;             mx = max(mx,mp[v[i][j]]);         }         ans+=(sz-mx);     }     cout<<ans<<endl;    return 0;}

E:
CodeForces-731E Funny Game(DP+Games) 博弈dp
参考博客

题意:
每个人每次从2..n选一个点,拿掉其前面所有的点然后进行加和,然后把加和放在剩余点的前面作为第一个点,加和并作为该玩家的分数,直至只剩下一个点游戏结束。最终玩家所有的分数加和,问在双方都选择最优策略的情况下(都尽量使自己减去对方的值最大),先手拿的减去后手拿的得分的最大值。
思路:
题意就是求先手值-后手值尽可能大,简单分析一下会发现先手和后手就是在这个序列的前缀和上进行操作的。而且先手后手都会选择其最优的策略,由于前缀和sum[n]是最终必选的,所以我们可以假设先手或后手取sum[n],然后从n-1倒着推,如果是先手选择当前点,那么就要承受后手在后面的最优,如果是后手选择当前点,那么就要承受先手在后面的最优。
dp[i][0]表示先手从第i点到第n点选择一个点使先手值-后手值最大。
dp[i][1]表示后手从第i点到第n点选择一个点使先手值-后手值最小。
dp[i][0] = max(dp[i+1][1]+sum[i], dp[i+1][0]);
dp[i][1] = min(dp[i+1][0]-sum[i], dp[i+1][1]);
(dp[n][0] = sum[n], dp[n][1] = -sum[n])

这道题真的是花费了我好长时间去想,首先这道题的游戏规则设计的十分的诡异,每次从2到n中选择一个数,把从1到该数的和作为玩家的得分,然后再把该和作为数列的第一项,另一个玩家从该得分继续向后进行选择,但仔细一想,这说的不就是每次的得分都是前缀和吗?然后看题解中的描述,两个人的策略正好相反,所以第二人的策略也可以理解成使先手值-后手值最小,由于该游戏又是博弈的,即双方每次选择都是基于自己最优的,所以要同时维护两个人的得分,而且状态转移时,例如就先手而言,不选择该点或者选择该点,但是选择该点时的转移并非从该人的前一个状态转移而来,而是从另一个人的状态转移而来(由于博弈的原因),因此才有了上面的转移方程,后手的状态加上选择该点的得分,才是当前选择该点所得到的答案。下面说一下初值,dp[n][0] 代表先手选择了所有数所以答案是sum[n],dp[n][1] 代表后手选择了所有数,所以答案是-sum[n].这里需要注意dp数组始终代表的是当前该点的答案,即对于dp[i][0] 而言表示的是先手-后手差值的最大值。

#include <bits/stdc++.h>using namespace std;int sum[200005]={0};int dp[200005][3];const int inf = 0x3f3f3f3f;int main(){    int x,n;    cin>>n;    sum[0]=0;    for(int i=1;i<=n;i++)    {        cin>>x;        sum[i] = sum[i-1]+x;    }    dp[n][0] = sum[n];    dp[n][1] = -sum[n];    for(int i=1;i<n;i++)    {           dp[i][0] = -inf;       dp[i][1] = inf;    }       for(int i=n-1;i>=2;i--)       {           dp[i][0] = max(dp[i+1][1]+sum[i],dp[i+1][0]);           dp[i][1] = min(dp[i+1][0]-sum[i],dp[i+1][1]);       }       cout<<dp[2][0]<<endl;    return 0;}

他在后续过程又优化了代码,但是我觉得不如上面那个易懂

#include <bits/stdc++.h>  using namespace std;  int n, p[200005];  int main()  {      cin >> n;      for(int i = 1; i <= n; ++i)      {          cin >> p[i];          p[i] += p[i-1];      }      int ans = p[n];      for(int i = n-1; i >= 2; --i)      ans = max(ans, p[i]-ans);      cout << ans << endl;      return 0;  }  

F:
题意:
给一个n长度的序列,问从序列中找一个数,把大于它的数变成它或者它的倍数(向下取整,即减去一些值),并使得这些数的和最大

思路:
序列中的数最大为200000 ,我们可以枚举i(1<=i<=200000)作为base,然后枚举i的倍数j分段去累加就好了,类似于素数筛的思想,再加上前缀和处理一下

#include<bits/stdc++.h>using namespace std;const int maxn = 2e5+7;int cnt[maxn+10]={0};int main(){     int n;     cin>>n;     int x;     for(int i=1;i<=n;i++)     {         scanf("%d",&x);         cnt[x]++;     }     for(int i=1;i<=maxn;i++)     {         cnt[i]+=cnt[i-1];     }//    for(int i=1;i<=16;i++)//    printf("%d ",cnt[i]);     long long ans = 0,tmp;     for(int i=1;i<=maxn;i++)     {         if(cnt[i]-cnt[i-1])//如果i在数列中出现过         {             tmp = 0;             for(int j=0;j<=maxn;j+=i)             {                 tmp+=(long long)j*(cnt[min(maxn,j+i-1)]-cnt[j-1]);                 //printf("%d %d %d\n",j,j+i-1,tmp);                 //从j到j+i-1,统计一次                 //因为按照题意大于i的数都只能向下减去某些值成为i的倍数             }             ans = max(ans,tmp);             //cout<<endl;         }     }     cout<<ans<<endl;}
原创粉丝点击