CF 1398

Educational Codeforces Round 93

A

签到题
题意:给一个不下降序列,求是否存在三个数,不能组成三角形。
思路:考虑a[1],a[2],a[n]即可。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
using namespace std;
const int N = 5e4 + 10;
int a[N];
int main()
{
	int T, n;
	scanf("%d", &T);
	while(T)
	{
		T--;
		int flag = 0;
		scanf("%d", &n);
		for(int i = 1; i <= n; i++)
			scanf("%d", &a[i]);
		if(a[n] >= a[1] + a[2])
		{
			printf("1 2 %d\n",n);
			flag = 1;
		}
		if(!flag )
			printf("-1\n");
	 } 
	return 0;
}

B

签到题
题意:给一个0/1串,A/B两人轮流操作,每次操作可以删除串内连续的0或者连续的1,每次的得分为1的个数,两个人都最大化得分,求A的得分。
思路:贪心,取连续的1比取0更优,每次贪心取最长的连续的1即可。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
using namespace std;
const int N = 110;
int h[N];
char s[N];
int main()
{
	int T;
	scanf("%d", &T);
	while(T)
	{
		T--;
		scanf("%s", s+1);
		int n = strlen(s + 1);
		int tot = 0;
		int cnt = 0;
		memset(h,0,sizeof(h));
		for(int i = 1;i <= n; i++)
		{
			if(s[i] == '0')
			{
				h[++cnt] = tot;
				tot =0;
			}
			else
				tot++;
		}
		h[++cnt] = tot; 
		sort(h + 1, h + cnt + 1);
		int sum = 0;
		for(int i = cnt ; i > 0; i -= 2)
		{	sum += h[i];}
		printf("%d\n",sum);
	 } 
	return 0;
}

C

签到题
题意:给一个数组, ∑ i = l r a i = r − l + 1 \sum_{i = l}^{r}a_{i} = r-l + 1 i=lrai=rl+1的区间的个数。
思路:统计 s u m r − s u m l = r − l sum_{r}-sum_{l} = r- l sumrsuml=rl的(l,r)对数。上式移项,得 s u m r − r = s u m l − l sum_{r} -r = sum_{l} - l sumrr=sumll。map统计 s u m i − i sum_{i} - i sumii的个数即可。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<map>
#define LL long long
using namespace std;
const int N = 1e5 + 10;
char s[N];
int sum[N];
map<int, int>mp;
int main()
{
	int T;
	scanf("%d", &T);
	while(T)
	{
		T--;
		int n;
		scanf("%d", &n);
		scanf("%s", s + 1);
		LL ans = 0;
		mp[0] = 1;
		for(int i = 1;i <= n; i++)
		{
			sum[i] = sum[i - 1] + s[i] - '0';
			ans += mp[sum[i] - i];
			mp[sum[i] - i] ++;
		}
		cout<<ans<<endl;
		for(int i = 1;i <= n; i++)
			mp[sum[i] - i] = 0;
	 } 
	return 0;
}

D

DP
题意:给三种不同颜色的木条,同种颜色的木条长度成对出现。选择一些木条组成矩形,每个矩形必须由两对不同颜色的木条组成。求最大面积。
思路:每种颜色的木条从大到小排序。f[i][j][k]表示三种颜色分别用了i,j,k个能构成的最大面积。枚举下一个矩形的边进行转移。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
using namespace std;
const int N = 205;
LL a[N],b[N],c[N];
LL f[N][N][N];
int cmp(LL x, LL y)
{
	return x > y;
}
int main()
{
	int A, B, C;
		scanf("%d%d%d", &A, &B, &C);
		for(int i = 1; i <= A; i++)
			cin>>a[i];
		for(int i = 1;i <= B; i++)
			cin>>b[i];
		for(int i = 1;i <= C; i++)
			cin>>c[i];
		sort(a + 1, a + A +1, cmp);
		sort(b + 1, b + B + 1, cmp);
		sort(c + 1 , c + C +1, cmp);
		LL ans = 0;
		for(int i =0;i <= A; i++)
			for(int j = 0; j <= B ; j++)
				for(int k = 0; k <= C; k++)
				{
					ans = max(ans, f[i][j][k]);
					if(i + 1 <= A && j + 1 <= B)
						f[i + 1][j + 1][k] = max(f[i + 1][j + 1][k], f[i][j][k] + a[i + 1] * b[j + 1]);
					if(i + 1<= A && k + 1 <= C)
						f[i + 1][j][k + 1] = max(f[i + 1][j][k + 1], f[i][j][k] + a[i + 1] * c[k + 1]);
					if(j + 1 <= B && k + 1 <= C)
						f[i][j + 1 ][k + 1] = max(f[i][j + 1][k + 1], f[i][j][k] + b[j + 1]* c[k + 1]); 
				}
		cout<<ans;
	return 0;
}

E

线段树
题意:两种技能,都可以产生一定利润,但第二种可以使下一轮的收益翻倍。一些询问,插入或删除某一技能,询问最大收益。
思路:如果有k个第二种技能,那么有k次翻倍的机会,(但其中最多有k-1次是使用二技能的)。离线操作,线段树维护区间技能数、一技能数、二技能数、所有技能收益和。查询时,先查询收益最大的前k个技能有多少是二技能,如果全都是,那么选1个最大的一技能和k-1个最大的二技能;否则,选k个二技能进行翻倍。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long 
using namespace std;
const int N = 2e5 + 10;
int bsize[N * 4], asize[N * 4], size[N * 4], h[N];
LL sum[N * 4];
void pushup(int x)
{
	sum[x] = sum[x * 2] + sum[x * 2 + 1];
	asize[x] = asize[x * 2] + asize[ x * 2 + 1];
	bsize[x] = bsize[ x* 2] + bsize[x * 2 + 1];
	size[x] = size[x * 2] + size[x * 2 + 1]; 
}
void update(int x, int l, int r, int pos, int dele, int typ)
{
	if(l == r)
	{
		sum[x] += dele * h[pos];
		if(typ == 0)	asize[x]+=dele;
		else	bsize[x]+=dele;
		size[x] += dele;
		return;
	}
	int mid = (l + r)>>1;
	if(pos > mid)	update(x * 2 + 1, mid + 1, r, pos, dele, typ);
	else	update(x * 2, l, mid, pos, dele, typ);
	pushup(x);
} 
int query_cnt(int x, int l, int r, int tot)
{
	if(tot <= 0)
		return 0;
	int mid = (l + r)>>1;
	if(size[x] <= tot)
		return bsize[x];
	if(size[x * 2 + 1] >= tot)
		return query_cnt(x * 2 +1, mid + 1, r, tot);
	else
		return query_cnt(x * 2, l, mid, tot - size[x * 2 + 1]) + bsize[x * 2 + 1];
}
LL query_sum(int x, int l, int r, int tot)
{
	if(tot <= 0)
		return 0;
	int mid = (l + r)>>1;
	if(size[x] <= tot)
		return sum[x];
	if(size[x * 2 + 1] >= tot)
	{ 
		return query_sum(x * 2 +1, mid + 1, r, tot);
		} 
	else
	{ 
		return query_sum(x * 2, l, mid, tot - size[x * 2 + 1]) + sum[x * 2 + 1];
		} 
}
LL query_maxa(int x, int l, int r)
{
	if(l == r)
	{ 
		if(asize[x] == 0)
			return 0;
		else 
			return h[l];
	} 
	int mid = (l + r)>>1;
	if(asize[x * 2 + 1] == 0)
		return query_maxa(x * 2, l, mid);
	else
		return query_maxa(x * 2 + 1, mid + 1, r);
}
int tp[N],d[N],del[N];
int main()
{
	int n,m;
	scanf("%d", &n);
	for(int i = 1; i <= n; i++)
	{
		scanf("%d%d",&tp[i],&d[i]);
		if(d[i] < 0)
		{
			d[i] = -d[i];
			del[i] = -1;
		}
		else
			del[i] = 1;
		h[i] = d[i];
	}
	sort(h + 1, h + n + 1);
	m = unique(h + 1, h + n + 1)- h - 1;
	int tot = 0;
	LL sum = 0;
	for(int i = 1; i <= n; i++)
	{
		int x = lower_bound(h + 1, h + m + 1, d[i]) - h;
		update(1, 1, m, x, del[i], tp[i]);
		if(tp[i] == 1)	tot+=del[i];
		sum += del[i] * d[i];
		if(query_cnt(1, 1, m, tot) == tot && tot)
			cout << sum + query_sum(1, 1, m, tot - 1) + query_maxa(1, 1, m)<<endl;
		else
			cout<<sum + query_sum(1, 1, m, tot)<<endl;
	}
	return 0;
}

F

贪心、双指针
题意:给一个由0、1、?组成的串,当有连续的k个0或1出现时,即可算一轮结束,(?既可以看作0,又可以看作1),对于1~n之间的每一个k,求最多可以进行几轮。
思路

  1. n x t 0 / 1 , i nxt_{0/1,i} nxt0/1,i表示从第i位起,连续的0/1串可以延续到的最远距离。 p o s 0 / 1 , i pos_{0/1,i} pos0/1,i所有记录长度为i的0/1串的起始位置,且这些位置不可向前延申。
  2. 对每一个k分别考虑。对于一个位置now,如果now可以直接向后延续k个,即 n x t 0 , n o w ≥ k nxt_{0,now} \geq k nxt0,nowk或者 n x t 1 , n o w ≥ k nxt_{1,now} \geq k nxt1,nowk,那么直接向后延申。否则,在pos队列里面找合适的位置,即 p o s 0 , k pos_{0,k} pos0,k p o s 1 , k pos_{1,k} pos1,k里面第一个大于等于now的位置,向后延申。
  3. 用queue会炸空间,只能用vector
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#define LL long long 
using namespace std;
const int N = 1e6 + 10;
vector<int>pos[2][N];//用queue炸空间 
int nxt[2][N], ptr[2][N];
char s[N];
int n;
void get_pos(int id)
{
	for(int l = 1; l <= n;)
	{
		if(s[l] == '0' + (id ^ 1))
			l++;
		else
		{
			int r = l + 1;
			while(r < n + 1 && s[r] != '0' + (id ^ 1))
				r++;
			for(int len = 1; len <= r - l; len ++)
			{
				pos[id][len].push_back(l);
			}
			l = r;
		}
	}
}
int main()
{
	scanf("%d", &n);
	scanf("%s", s + 1);
	nxt[0][n + 1] = 0;
	nxt[1][n + 1] = 0;
	for(int i = n; i > 0; i--)
	{
		if(s[i] != '1')	nxt[0][i] = nxt[0][i + 1] + 1;
		if(s[i] != '0') nxt[1][i] = nxt[1][i + 1] + 1;
	}
	get_pos(0);
	get_pos(1);
	for(int i = 1; i <= n; i++)
	{
		int cnt = 0;
		int now = 1;
		while(now <= n)
		{
			if(nxt[0][now] >= i || nxt[1][now] >= i)
				now = now + i;
			else
			{
				while(ptr[0][i] < pos[0][i].size() && pos[0][i][ptr[0][i]] < now)
					ptr[0][i]++;
				while(ptr[1][i] < pos[1][i].size() && pos[1][i][ptr[1][i]] < now)
					ptr[1][i]++;
				if(ptr[0][i] < pos[0][i].size() && ptr[1][i] < pos[1][i].size())
				{
					now = min(pos[0][i][ptr[0][i]], pos[1][i][ptr[1][i]]) + i; 
				}
				else
				{
					if(ptr[0][i] >= pos[0][i].size() && ptr[1][i] >= pos[1][i].size())
						break;
					else
					{
						if(ptr[0][i] >= pos[0][i].size())
							now = pos[1][i][ptr[1][i]] + i;
						else
							now = pos[0][i][ptr[0][i]] + i;
					}
				}
			}
			cnt += 1;
		}
		cout<<cnt<<" ";
	}
	cout<<endl;
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值