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=r−l+1的区间的个数。
思路:统计
s
u
m
r
−
s
u
m
l
=
r
−
l
sum_{r}-sum_{l} = r- l
sumr−suml=r−l的(l,r)对数。上式移项,得
s
u
m
r
−
r
=
s
u
m
l
−
l
sum_{r} -r = sum_{l} - l
sumr−r=suml−l。map统计
s
u
m
i
−
i
sum_{i} - i
sumi−i的个数即可。
#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,求最多可以进行几轮。
思路:
- 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串的起始位置,且这些位置不可向前延申。
- 对每一个k分别考虑。对于一个位置now,如果now可以直接向后延续k个,即 n x t 0 , n o w ≥ k nxt_{0,now} \geq k nxt0,now≥k或者 n x t 1 , n o w ≥ k nxt_{1,now} \geq k nxt1,now≥k,那么直接向后延申。否则,在pos队列里面找合适的位置,即 p o s 0 , k pos_{0,k} pos0,k或 p o s 1 , k pos_{1,k} pos1,k里面第一个大于等于now的位置,向后延申。
- 用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;
}