【Codeforces】Educational Round 61

Posted on 2019-03-08 22:36  opethrax  阅读(217)  评论(0编辑  收藏  举报

今天刚打完一场,只做出来一道题,开错题了然后没钻出来,机房的小伙伴都切了四道题了...可能又要掉了,要继续努力了。

比较顺利的做出了ABC,然后看人数去推了F题,不过没什么结果。

然后在改代码的时候结识了白学长,:)

话说这几天好燥啊,G题其实代码挺好理解但愣是拖了两天啊。

 

Problem A. Regular Bracket Sequence


 

题目大意:现在给出 种括号依次为 "((" , "()" , ")(" , "))" ,给出每种括号的数量,求所有的括号能不能构成一个合法的括号序列(所有括号都成对出现)

数据范围:1≤括号数量≤109

第二种括号显然对答案没有影响,第三种括号只要插在第一种和第四种中间就行,但第一种和第四种括号数量必须相等。

代码:只贴一行

if(cnt[1]==cnt[4]&&(cnt[1] || cnt[3]==0)){printf("1"); return 0;}

 

Problem B. Discounts


 

题目大意:现在你要购买 n 件物品,有 m 张优惠卷。使用一张优惠卷可以使某 qi 件物品中价格最低的物品免费,每次只能使用一张优惠卷。求每一张优惠卷使用后的最小开销。

数据范围:1≤m<n≤3*105,1≤物品价格≤109

排序按题意去掉第 q大的物品即可

代码:不贴了

 

Problem C. Painting the Fence


 

题目大意:在长度为 n 的区间内有 q 条线段,每条线段可以覆盖一段区间。现在要去掉两条线段,求剩下 q-2 条线段的最大覆盖长度

数据范围:3≤n,q≤5000

数据明显有坑...考虑到被移除的两条线段的关系:如果两条线段同时覆盖了一段区间,去掉这两条线段后公用的这部分可能不被覆盖。

在 3s 的时限下可以 O(n2) 枚举这两条线段,但是还有计算他们是否有重合部分及这部分对答案的影响。可以 O(n2) 暴力覆盖(或者优雅的差分,但本人差分学得不好)。

一条线段被移除后不被覆盖的区间是只被其覆盖1次的区间,两条线段移除后中间那部分(如果有)就是被覆盖2次的区间。所以 O(n) 预处理出1的前缀和与2的前缀和。

然后 O(n2) 枚举两条线段,统计最值。

代码:

 1 #include<stdio.h>
 2 #include<string.h>
 3 #include<algorithm>
 4 using namespace std;
 5 
 6 const int N=5050;
 7 
 8 int n,q,ans=N;
 9 int color[N],tot,sum1[N],sum2[N];
10 struct sect{
11     int l,r;
12     int len;
13 }s[N];
14 
15 int main(){
16     int a,b,cnt,l1,l2;
17     scanf("%d%d",&n,&q);
18     for(int i=1;i<=q;i++){
19         scanf("%d%d",&s[i].l,&s[i].r);
20         a=s[i].l; b=s[i].r;
21         for(int j=a;j<=b;j++)++color[j];
22     }
23     for(int i=1;i<=n;i++){
24         sum1[i]=sum1[i-1]+(color[i]==1);
25         sum2[i]=sum2[i-1]+(color[i]==2);
26         if(color[i])++tot;
27     }
28     for(int i=1;i<=q;i++)s[i].len=sum1[s[i].r]-sum1[s[i].l-1];
29     for(int i=1;i<=q;i++){
30         l1=s[i].len;
31         for(int j=1;j<=q;j++){
32             if(i==j)continue;
33             l2=s[j].len;
34             a=max(s[i].l,s[j].l);
35             b=min(s[i].r,s[j].r);
36             if(b>=a){
37                 cnt=sum2[b]-sum2[a-1];
38                 ans=min(ans,l1+l2+cnt);
39             }
40             else ans=min(ans,l1+l2);
41         }
42     }
43     ans=tot-ans;
44     printf("%d",ans);
45     return 0;
46 }
~

 

Problem D. Stressful Training


 

题目大意:一个教练带着他傻头傻脑的学生去参加一场计算机比赛,但是这帮人全部忘带了充电线!每一台电脑在比赛开始时有初始电量 ai ,每分钟耗费 bi 的电量。比赛时长 k 分钟。教练穷啊,所以他只卖了一条充电线。这条充电线的功率可以被选择,但是一旦确定就不能改变了。现在想知道选择功率最小是多少的充电线可以帮助学生们扛到比赛结束。 

数据范围:1≤n,k≤2*10,1≤a≤1012,1≤b≤107

感觉可以二分,功率越大可以坚持的时间就越长,反之亦然。

那么怎么二分呢?看了别人的代码受到的启发: k 分钟就是可以用 k 次,然后在每台电脑没电的那个时候分配给他1次充电机会,然后给时间开一个桶,存某个时间分配的充电次数。对这个桶求前缀和,然后判断合不合法就行了。时间复杂度 O(n) 。

代码:

 1 #include<stdio.h>
 2 #include<string.h>
 3 #include<algorithm>
 4 using namespace std;
 5 
 6 typedef long long LL;
 7 const int N=200050;
 8 
 9 int n,k;
10 LL a[N],b[N];
11 int s[N];
12 bool check(LL x){
13     for(int i=1;i<=k;i++)s[i]=0;
14     int res=k;
15     LL chg;
16     for(int i=1;i<=n;i++){
17         chg=a[i];
18         while(chg<k*b[i]){
19             if(!res)return 0;
20             ++s[chg/b[i]+1];
21             --res; chg+=x;
22         }
23     }
24     for(int i=1;i<=k;i++)s[i]+=s[i-1];
25     for(int i=1;i<=k;i++)if(s[i]>i)return 0;
26     return 1;
27 }
28 int main(){
29     scanf("%d%d",&n,&k); --k;
30     for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
31     for(int i=1;i<=n;i++)scanf("%lld",&b[i]);
32     LL l=0,r=2000000000000;
33     LL mid,ans=-1;
34     while(l<=r){
35         mid=(l+r)>>1;
36         if(check(mid)){
37             ans=mid; r=mid-1;
38         }
39         else l=mid+1;
40     }
41     printf("%lld",ans);
42     return 0;
43 } 
~

 

Problem E. Knapsack


 

题目大意:有 8 种物品权值为 1~8 。求在 w 以内这些物品可以凑出来的最大价值。

数据范围:1≤w≤1018,1≤cnt≤1016

第一反应当然是背包啦。但是这么大的数据当然不行了。但是也存在别的动态规划的做法,不过没看懂。

下面提供的解法来着 FlyWhite 学长:

求所有物品的价值和,小于等于 w 就输出。否则

取 1~8 的最小公倍数 840 。如果可以取一些物品得到权值 x ,那么也可以得到 x+840*k 。

最后的结果就相当是从总价值和 sum 中去掉一些物品,他们的价值一定可以表示成 x+840*k

得到

sum-x-840*k1≤w

同样的 sum 也可以写成 x+840*k 的形式

相当于

(sum-x)%840+840*k≤w

现在只要确定对于每一个 x 的 k 的最大值就可以算出那个最大的 ans 了。

本来想着枚举 k 的,但是学长提供了更简洁的方法

令 mod=(sum-x)%840

mod+840*k+y=w

求 y 的最小值同样可以得到最大的 ans

y=w-840*k-mod

min(y)=(w-mod)%840

感觉很厉害有没有。

代码:

 1 #include<stdio.h>
 2 #include<string.h>
 3 #include<algorithm>
 4 #include<bitset>
 5 using namespace std;
 6 
 7 typedef long long LL;
 8 const LL M=840;
 9 
10 LL w,a[10],sum,ans;
11 bitset<M> f;
12 
13 int main(){
14     LL t;
15     scanf("%lld",&w);
16     for(int i=1;i<=8;i++){
17         scanf("%lld",&a[i]);
18         sum+=a[i]*i;
19     }
20     if(sum<=w){printf("%lld",sum); return 0;}
21     f[0]=1;
22     for(int i=1;i<=8;i++){
23         t=min(a[i],M/i);
24         for(int j=0;j<t;j++)f|=(f<<i);
25     }
26     for(int i=0;i<=840;i++) if(f[i]){
27         t=(sum-i)%M;
28         ans=max(ans,w-((w-t)%M+M)%M);
29     }
30     printf("%lld",ans);
31     return 0;
32 }
~

 

Problem F. Clear the String


 

题目大意:一个长度为 n 由小写拉丁字母组成的字符串 s ,如果它的一个字串的字母全部相等,就可通过一次操作以删除这个字串。求删除整个字符串最少需要多少次操作。

数据范围:1≤n≤500

感觉有点像祖玛。有一个 O(n2) 的 dp 方案。

令 f[i][j] 表示 i~j 段全部删除的最小操作数。

如果 i~j 段两端点字母相同,那么可以把 f[i][j-1] 和 f[i+1][j] 两段不花费代价合并,也可以先清除 f[i+1][j-1] 段。

否则就枚举断点,用 f[i][k]+f[k+1][j] 更新答案。

代码:

 1 #include<stdio.h>
 2 #include<string.h>
 3 #include<algorithm>
 4 using namespace std;
 5 
 6 const int N=550;
 7 
 8 int n;
 9 int f[N][N];
10 char s[N];
11 
12 int main(){
13     scanf("%d",&n);
14     scanf("%s",s+1);
15     memset(f,0x3f,sizeof(f));
16     for(int i=1;i<=n;i++)f[i][i]=1;
17     for(int l=1;l<n;l++){
18         for(int i=1,j;i+l<=n;i++){
19             j=i+l;
20             if(s[i]==s[j]){
21                 f[i][j]=min(f[i][j-1],f[i+1][j]);
22                 if(l==1)f[i][j]=1;
23                 else f[i][j]=min(f[i+1][j-1]+1,f[i][j]);
24             }
25             else for(int k=i;k<j;k++){
26                 f[i][j]=min(f[i][k]+f[k+1][j],f[i][j]);
27             }
28         }
29     }
30     printf("%d",f[1][n]);
31     return 0;
32 }
~

 

Problem G. Greedy Subsequences


 

题目大意:求一个长度为 n 的序列的所有长度为 k 的子区间的最长上升子序列长度。

数据范围:1≤n,k≤106

感觉收获蛮多的一道题了,虽然拖得略久了点。

回忆一些我们是怎么求最长上升子序列的:

令 f[i] 表示以i结尾的最长上升子序列的长度,每次从i之前的所有位置 a[x]<a[i] 的 f[x] 转移过来。

现在我们令 s[i] 表示以第i位开头的最长上升子序列长度。当i之后出现一个大于i的数字时,++s[i]。

在当前的前i-1位的s[i]计算好时,把第i个数字放在第i个位置,这是需要把前i-1个位置上所有小于a[x]<a[i]的s[x]更新。

如果我们求出了每个数字前面比它小的数字是那些,只要在这个数字放进这个序列的时候更新这些位置上的s就可以了。

我们用一个栈倒着把当前的数字和栈顶比较,若a[i]>a[stack[top]]就把stack[top]左边第一个比它大的数字的位置 i 存下来(pre[i])。然后把a[i]入栈。

之后我们就得到了每个数字左边第一个比它的的数字的位置。

我们开始挨个把数字接到现在的序列的后面,然后把他自己的位置 i 到 pre[i]+1 这一段的s区间加1。

再查询 i-k+1~i这个区间的最大值,就是这个区间的最长上升子序列的长度了~

代码:

 1 #include<stdio.h>
 2 #include<string.h>
 3 #include<algorithm>
 4 using namespace std;
 5 
 6 const int N=1000050;
 7 
 8 int n,k;
 9 int a[N], sta[N],top,pre[N];
10 int s[N<<2],tag[N<<2];
11 
12 void update(int rt,int c){
13     s[rt]+=c;
14     tag[rt]+=c;
15 }
16 void PushUp(int rt){
17     s[rt]=max(s[rt<<1],s[rt<<1|1]);
18 }
19 void PushDown(int rt){
20     if(tag[rt]){
21         update(rt<<1,tag[rt]); update(rt<<1|1,tag[rt]);
22         tag[rt]=0;
23     }
24 }
25 void add(int L,int R,int l,int r,int rt){
26     if(L<=l&&r<=R)return update(rt,1);
27     PushDown(rt);
28     int mid=(l+r)>>1;
29     if(L<=mid)add(L,R,l,mid,rt<<1);
30     if(R>mid)add(L,R,mid+1,r,rt<<1|1);
31     PushUp(rt);
32 }
33 int query(int L,int R,int l,int r,int rt){
34     if(L<=l&&r<=R)return s[rt];
35     PushDown(rt);
36     int mid=(l+r)>>1,ret=0;
37     if(L<=mid)ret=max(ret,query(L,R,l,mid,rt<<1));
38     if(R>mid)ret=max(ret,query(L,R,mid+1,r,rt<<1|1));
39     return ret;
40 }
41 
42 int main(){
43     scanf("%d%d",&n,&k);
44     for(int i=1;i<=n;i++)scanf("%d",&a[i]);
45     for(int i=n;i;i--){
46         while(top&&a[sta[top]]<=a[i])pre[sta[top]]=i,--top;
47         sta[++top]=i;
48     }
49     for(int i=1;i<=n;i++){
50         add(pre[i]+1,i,1,n,1);
51         if(i>=k)printf("%d ",query(i-k+1,i,1,n,1));
52     }
53     return 0;
54 }
~