Educational Codeforces Round 30 题解

比赛链接:http://codeforces.com/contest/873

A. Chores

题意:最大的k个数用x替换之后,求和。

#include <bits/stdc++.h>
using namespace std;

int main(){
    int ans=0,n,k,x,a[110];
    scanf("%d%d%d",&n,&k,&x);
    for(int i=1; i<=n; i++) scanf("%d", &a[i]);
    for(int i=1; i<=n; i++){
        if(n-i<k) ans+=x;
        else ans+=a[i];
    }
    return 0*printf("%d\n", ans);
}

B. Balanced Substring

题意:给个01序列,求最长的连续区间使使得0的个数和1的个数相等。

解法:把0用-1替换,然后求扫一遍求前缀和,如果运到相等的更新答案,前缀和存在负数,所以用map来映射。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100010;
char s[maxn];
int a[maxn];
unordered_map<int,int> pos;
int main(){
    int n;
    scanf("%d", &n);
    scanf("%s", s);
    for(int i=0; i<n; i++){
        if(s[i]=='0') a[i+1]=-1;
        else a[i+1]=1;
    }
    int sum = 0, ans = 0;
    pos[0] = 1;
    for(int i=1; i<=n; i++){
        sum += a[i];
        if(pos[sum]) ans = max(ans, i-pos[sum]+1);
        else pos[sum] = i+1;
    }
    return 0*printf("%d\n", ans);
}

C. Strange Game On Matrix

题意:给你n*m的矩阵和k,让你在每一列的里找i最小的1,从这个1向下出发(包含),直到长度为len=min(k,n-i+1)。其中的1的个数加到总分数中。每一列找完之后,得到总分数。现在可以操作:把任意个1变为0。现在想要总分数最大。求最大总分数和此时操作的最小次数。

解法:列单独处理,贪心找最大的连续len里面1的个数。

#include <bits/stdc++.h>
using namespace std;
int n,m,K,a[110][110];
bool vis[110][110];
int mx,lim,pos,cnt,b[110];
int getmin(int x, int y){
    return x<y?x:y;
}
int main(){
    scanf("%d%d%d", &n,&m,&K);
    for(int i=1; i<=n; i++){
        for(int j=1; j<=m; j++){
            scanf("%d", &a[i][j]);
        }
    }
	int ans2 = 0;
    memset(vis, 0, sizeof(vis));
    for(int j=1; j<=m; j++){
        mx = 0;
        pos = 1;
        for(int i=1; i<=n; i++) b[i] = a[i][j];
        for(int i=1; i<=n; i++){
            lim = K<(n-i+1)?K:(n-i+1);
            cnt = 0;
            for(int kk=i; kk<=i+lim-1; kk++){
                if(b[kk]==1) ++cnt;
            }
            if(cnt>mx){
                pos = i;
                mx = max(mx, cnt);
            }
        }
        for(int i=pos; i<=pos+(K<(n-pos+1)?K:(n-pos+1))-1; i++) vis[i][j]=1;
	    for(int i=1; i<pos; i++) if(a[i][j]==1) ans2++;
    }
    int ans1=0;
    for(int i=1; i<=n; i++){
        for(int j=1; j<=m; j++){
            if(vis[i][j]){
                ans1+=(a[i][j]==1);
            }
        }
    }
    return 0*printf("%d %d\n", ans1, ans2);
}

D. Merge Sort

题意:对于一个[l,r)的区间,如果是乱的,你会先mergesort一下,如果已经是递增的,那么就不用继续往下操作了;如果不是递增的,那么就要分一下,对左边[l,mid)操作,右边[mid,r)操作,等到左边和右边的都排完,然后最后再排一下,这样的操作次数是2。

解法:对于每一个样例,k肯定是个奇数,因为你最开始要操作一次全区间,然后才开始递归,而且k不会大于等于2*n,因为全部分开,操作最多也就是2*n-1(自己画一下图就知道了)。我们就从末状态开始,往前走,每次dfs把a[mid]和a[mid-1]换一下,因为a[mid-1]是在左边区间的,a[mid]是在右边区间的,所以你需要左边排一下,右边排一下,这样操作次数是2,最后排一下,就是这个最后排一下,可以把我们交换的还原。代码极短。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+6;
int n, k, a[maxn];
void cdq(int l, int r){
    if(k==1||r<=l+1) return;
    k-=2;
    int mid = (l+r)/2;
    swap(a[mid], a[mid-1]);
    cdq(l,mid);
    cdq(mid,r);
}
int main(){
    scanf("%d%d",&n,&k);
    if(k>=2*n||k%2==0) return 0*puts("-1");
    for(int i=1; i<=n; i++) a[i]=i;
    cdq(1,n+1);
    for(int i=1; i<=n; i++) printf("%d ",a[i]); printf("\n");
}

E. Awards For Contestants

题意:给定n个正整数,要把它们放进三个组(分别为1号组,2号,3号),也可以不放。每组至少要有一个数。同时对于对于1,2,3号组,两两直接必须满足cnt[p]≤2*cnt[q],其中cnt[x]表示x号组有多少个数。对于两个数x,y,如果x < y,那么y不能放在编号比x大的组。 定义c[x]表示x号组的最大数,d[x]是x号组的最小数。特殊地,c[-1]表示没有放的数的最大值,d[-1]同理。现在求一个合法分配方案,最大化{d[1]-c[2],d[2]-c[3],d[3]-c[-1]}的字典序。n小于等于3000,正整数大小不超过5000

解法:先看正解。首先给n个数降序排序,容易发现这等价于把排序后的数组分成4段,其中前三段是要满足cnt两两不超过彼此两倍的条件。 然后可以枚举第1组的边界和第2组的边界,然后合法的第三组的边界就是一个区间,查询区间内的最大值即可,其中查最大值可以O(1)(利用RMQ)。然后CF上看到这个题可以直接暴力。。。O(n^3)虽然是达不到O(n^3)的。。

#include <bits/stdc++.h>
using namespace std;
#define c(x) a[x].val-a[x+1].val
struct node{
    int id,val;
}a[3010];
bool cmp(const node &x,const node &y){
    return x.val>y.val;
}
int n,x,y,z,tot1,tot2,ans1,ans2,ans3,p[4],Ans[3010];
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        a[i].id=i;
        scanf("%d",&a[i].val);
    }
    sort(a+1,a+n+1,cmp);
    for(int i=1; i<=n; i++){
        tot1 = min(n, i+i*2);
        for(int j=i+(i+1)/2;j<=tot1;j++){
            tot2=min(j+min(i,j-i)*2, n);
            for(int k=j+max(i+1,j-i+1)/2;k<=tot2;k++){
                if(c(i)>ans1||(c(i)==ans1&&c(j)>ans2)||(c(i)==ans1&&c(j)==ans2&&c(k)>ans3)){
                    ans1 = c(i), ans2 = c(j), ans3 = c(k), p[1] = i, p[2] = j, p[3] = k;
                }
            }
        }
    }
    for(int i=1; i<=n; i++) Ans[a[i].id] = lower_bound(p+1,p+4,i)-p;
    for(int i=1; i<=n; i++) printf("%d ", Ans[i]==4?-1:Ans[i]);
}

F. Forbidden Indices


#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 200010;
const int M = 200010;
const int LOG = 18;
int mi[N][LOG];
//后缀数组模板,0base
int n;	//n表示原有字符串的长度
char s[N];	//s[i]表示原有字符串的第i位
int sa[N];	//sa[i]表示第i小的后缀对应的起点下标
int sb[N];	//sb[i]为辅助数组,表示起点下标为i,对应的后缀(倍增前一半)是第几小
int sc[N];	//sc[i]为辅助数组,表示第i小的后缀(倍增后一半)对应的起点下标
int cnt[M];	//cnt[i]为辅助数组,表示基数排序数值大小为i的字符的个数(以及前缀和)

			//步骤一:建立后缀数组,求出sa[]
void build_sa(int charset)	//charset是字符集的初始大小,同时也对应着当前基数排序数值的大小的上限
{
	//利用指针的意义是减小数组交换的时间
	int *x = sb, *y = sc;

	//第一步:做区间边界差值为0的基数排序
	for (int i = 0; i < charset; ++i)cnt[i] = 0;			//初始化
	for (int i = 0; i < n; ++i)++cnt[x[i] = s[i]];			//计数,{m==26的话,这里可以-'a'('A')}
	for (int i = 1; i < charset; ++i)cnt[i] += cnt[i - 1];	//前缀和
	for (int i = n - 1; i >= 0; --i)sa[--cnt[x[i]]] = i;	//sa初始化

															//第二步:倍增枚举区间边界差值len,len的最大也严格小于n
	for (int len = 1; len < n; len <<= 1)
	{
		//用y[i]记录,以长度为len/2的后半段作关键字,把长度为len的字符串做排序后,第i小字符串的起点
		int p = 0;
		for (int i = n - len; i < n; ++i)y[p++] = i;
		for (int i = 0; i < n; ++i)if (sa[i] >= len)y[p++] = sa[i] - len;

		//再结合字符串前半段的关键字大小,
		for (int i = 0; i < charset; ++i)cnt[i] = 0;				//初始化
		for (int i = 0; i < n; ++i)++cnt[x[i]];						//计数
		for (int i = 1; i < charset; ++i)cnt[i] += cnt[i - 1];		//前缀和
		for (int i = n - 1; i >= 0; --i)sa[--cnt[x[y[i]]]] = y[i];	//sa合并

																	//后缀大小排名重编号 (不要忘记,这是0base模板 最小后缀所对应下标是第0小)
		p = 1; y[sa[0]] = 0;
		for (int i = 1; i < n; ++i)
		{
			y[sa[i]] =
				(x[sa[i - 1]] == x[sa[i]] && x[sa[i - 1] + len] == x[sa[i] + len]) ?
				p - 1 : p++;
		}
		swap(x, y);
		charset = p;			//对字符集大小做上限更新
		if (charset == n)break;	//排序完毕
	}
}
/*注意:这里遭遇了不以'$'封堵字符串尾端便使得答案有影响的问题,
我们发现问题其实出在后缀大小排名的重编号上。
我们依次枚举了当前排名为第i小的后缀,让它和前者比较双关键字的大小关系。
然后这里便可以发现,我们会遇到位置的溢出,我们用'$'封堵之后——
一旦某一段溢出了,它就会立刻显得与众不同,使得排序不出错。*/

//步骤二,求出rk[]数组以及height[]数组,定义height[i]=suffix(sa[i-1])和suffix(sa[i])的最长公共前缀
int rk[N], height[N];
int G;
void cal_height()
{
	/*h[i]表示排名第rk[i]小的后缀与排名第rk[i]-1小后缀的最长公共前缀。
	也就是以位置i为开头的后缀和比它排名小1后缀的最长公共前缀。
	h数组有以下性质:h[i]>=h[i-1]-1。*/

	for (int i = 0; i < n; ++i)
	{
		rk[sa[i]] = i;
	}
	for (int i = 0, k = 0; i < n; height[rk[i++]] = k)
	{
		if (k)--k;															//用k来记录h[],每次-=1
		if (rk[i]) for (int j = sa[rk[i] - 1]; s[i + k] == s[j + k]; ++k);	//rk[]>1时才可以进行转移
	}
	/*
	for (int i = 0; i < n; ++i)
	{
		printf("%d ", height[i]);
	}puts("");
	*/
}
void RMQinit(){
    for(int i=0; i<n; i++) mi[i][0]=height[i];
    for(int j=1; (1<<j)<=n; j++)
        for(int i=0; i+(1<<j)<=n; i++)
            mi[i][j] = min(mi[i][j-1], mi[i+(1<<j-1)][j-1]);
}
int getLCP(int x, int y){
    x++;
    int k = log2(y-x+1);
    return min(mi[x][k], mi[y-(1<<k)+1][k]);
}
char t[N];
int main(){
    scanf("%d", &n);
    scanf("%s %s", s,t);
    strrev(s);
    strrev(t);
    n++;
    build_sa(128);
    cal_height();
    RMQinit();
    LL ans = 0;
    int st = 0;
    vector <int> v1;
    for(int i=0; i<n; i++){
        if(t[sa[i]] == '1') continue;
        if(st) v1.push_back(getLCP(st, i));
        st = i;
        if(i) ans = max(ans, 1LL*(n-sa[i]-1));
    }
    stack <int> len, pos;
    for(int i=0; i<v1.size(); i++){
        int now=i;
        while(!len.empty()&&len.top()>=v1[i]){
            ans = max(ans, (LL)len.top()*(i-pos.top()+1));
            now = pos.top();
            pos.pop();
            len.pop();
        }
        pos.push(now);
        len.push(v1[i]);
    }
    while(!len.empty()){
        ans = max(ans, (LL)len.top()*(LL)(v1.size()-pos.top()+1));
        len.pop();
        pos.pop();
    }
    return 0*printf("%lld\n", ans);
}




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值