闲话
本场出了ABCD,因为思路不正确卡了很久D.非常的无语.
A. Bad Triangle
题目大意:给定\(n\)个元素的不降序列,找出三个不同位置的元素使他们三个不能组成一个三角形.
思路
直接拿最小的两个和最大的一个拼就可以了.
代码
#include
using namespace std;
typedef long long ll;
const int N = 3e5+7;
ll a[N];
int main()
{
int T;scanf("%d",&T);
while(T--)
{
int n;scanf("%d",&n);
for(int i = 1;i <= n;++i) scanf("%lld",&a[i]);
if(a[1] + a[2] > a[n]) puts("-1");
else printf("1 2 %d\n",n);
}
return 0;
}
B. Substring Removal Game
题目大意:两个人在玩一个游戏,这个游戏在一个字符串s上进行.每次可以从字符串里取出任意长的一段连续的相同的子串.得分是取出的1的个数,问先手的人最多可以有多少分.
思路
由于位置不固定,所以直接找到所有的连续的1的序列再按长度排序,隔着选就可以了.
代码
#include
using namespace std;
typedef long long ll;
const int N = 205;
char s[N];
int main()
{
int T;scanf("%d",&T);
while(T--)
{
scanf("%s",s + 1);getchar();
int n = strlen(s + 1);s[++n] = '#';
vector lists;
for(int i = 1;i <= n;++i)
{
if(s[i] == '#') continue;
if(s[i] == '1')
{
int j = i,cur = 0;
while(s[j] == '1')
{
s[j] = '#';
++j;
++cur;
}
lists.push_back(cur);
}
}
sort(lists.begin(),lists.end());reverse(lists.begin(),lists.end());
int res = 0;
for(int i = 0;i < lists.size();++i)
{
if(i % 2 == 0)
res += lists[i];
}
printf("%d\n",res);
}
return 0;
}
C. Good Subarrays
题目大意:给定一个\(n\)个元素的序列a,求满足\(\sum\limits_{i=l}^r a_i = r - l + 1\)的区间个数.
思路
先把求和拆成前缀和,得到\(s_r - s_{l-1} = r - l + 1\).固定区间右端点\(r\)找满足条件的\(l\)即可.注意\(l\)可以与\(r\)相等,因此\(map\)维护的时候要首先把\(r\)的影响加进去再计算,而不是先算再加入.
代码
#include
using namespace std;
typedef long long ll;
const int N = 1e5+7;
#define int ll
char a[N];
int s[N];
signed main()
{
int T;scanf("%lld",&T);
while(T--)
{
int n;scanf("%lld",&n);
scanf("%s",a + 1);getchar();
ll res = 0;
map tb;
for(int i = 1;i <= n;++i)
{
++tb[s[i - 1] - i + 1];
// cout << s[i - 1] - i + 1 << " ";
s[i] = s[i - 1] + (a[i] - '0');
res += tb[s[i] - i];
// cout << s[i] - i << endl;
}
printf("%lld\n",res);
}
return 0;
}
D. Colored Rectangles
题目大意:给定若干个红色,绿色,蓝色的一对长度一样的棍子.问用这些棍子组成的颜色不同的矩形的面积的最大总和是多少.注意不能把两个相同颜色的一对棍子拆成两个分别去用.其次颜色不同指的是在两个集合里选的两对棍子.
数据范围:
\(1 \leq R,G,B \leq 200\)
思路
首先比较容易想到的是贪心,但是这个题会跟棍子的数目有关,贪心会有很多问题,而且打补丁的方式是解决不了的.
考虑\(O(n^3)\)的DP,由于一共就三种元素,不妨就直接按定义直接设计状态:
状态:\(f[i,j,k]\)表示红色用了\(i\)个,绿色用了\(j\)个,蓝色用了\(k\)个的前提下得到的矩形面积总和的最大值.
入口:全为0即可,不需要额外处理.
转移:三种匹配方式直接枚举即可.
出口:所有值的最大值.
没什么好说的,转移方程太过于简单了.注意防范可能的爆int
.
不过今天群友聊天的时候特别提了一下这个D题.我一开始想的贪心,实际上有个结论是和DP做法联系很紧密的,就是对于两对选择比如:\(a,b\)和\(c,d\)各是两种颜色,在\(a \leq b\) 且 \(c \leq d\)的情况下,假如说两者不是较小的和较小的匹配,而是较小的和较大匹配,也就是错开的话,会发现得到的结果不会更好.进而推广可以得到:所有的选择一定是极值与极值的选择,不能错开选择.这就是这个DP的原理,当然估计好多人就理所当然的猜过去了.
当然这里的状态并不是直接表示说选哪个哪个,而是指在有多少个棍子给你的情况下,能拿到多少的值,对于当前的这个状态来说,他的前一个状态变过来,要加入两个新的最值,这就是前面那个结论的用处,这样的两个最值加过来只能是这两个最值自己匹配.所以说这里其实没有一个匹配谁的问题,整个DP过程暗含了这个事.其次的话,这个题其实排序规则是无所谓的,换成升序也可以过.
代码
#include
using namespace std;
typedef long long ll;
#define int ll
const int N = 205;
int r[N],g[N],b[N];
int f[N][N][N];
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);
int R,G,B;cin >> R >> G >> B;
for(int i = 1;i <= R;++i) cin >> r[i];sort(r + 1,r + R + 1,greater());
for(int i = 1;i <= G;++i) cin >> g[i];sort(g + 1,g + G + 1,greater());
for(int i = 1;i <= B;++i) cin >> b[i];sort(b + 1,b + B + 1,greater());
int res = 0;
for(int i = 0;i <= R;++i)
{
for(int j = 0;j <= G;++j)
{
for(int k = 0;k <= B;++k)
{
auto& v = f[i][j][k];
if(i >= 1 && j >= 1) v = max(v,f[i - 1][j - 1][k] + r[i] * g[j]);
if(j >= 1 && k >= 1) v = max(v,f[i][j - 1][k - 1] + g[j] * b[k]);
if(i >= 1 && k >= 1) v = max(v,f[i - 1][j][k - 1] + r[i] * b[k]);
res = max(res,v);
}
}
}
cout << res << endl;
return 0;
}
E. Two Types of Spells
题目大意:有两种卡,一种是造成\(x\)点伤害,一种是造成\(y\)点伤害,并且让下一次的伤害翻倍.现给出\(n\)个操作,每个操作是学习到一个技能或者忘记一个技能,对每次修改输出用当前手上的卡最多能打出多少点的伤害.卡一回合只能用一次,只要不忘记下一轮还能用.
数据范围:
\(1 \leq n \leq 2 * 10^5\)
\(-10^9 \leq d_i \leq 10^9\)
数据保证你不会有相同伤害的卡.并且每个删除操作是合法的.
思路
先肯定模拟一下样例,就可以发现这个叠伤害的过程一个跟最小伤害的翻倍卡有关,因为整个过程总有一个翻倍卡不能翻倍,那既然这样肯定就让那个最小的去开始翻倍秀.其次,假如翻倍有\(t\)次的话,那么应该还要找前\(t\)大的值的和.
接下来尝试具体想一下整个过程:
-
如果新加入一个卡
此时对于一个新的卡来说,把他的权值记作是\(d\).那么对于新加入的一张卡来说,先不管他是不是翻倍的,首先可以把他加入前\(t\)个最大值的集合里去,那么这一步需要搞一个按\(rank\)查数的工具,显然要写一个平衡树了,假设已经实现了一个平衡树,继续往下推,这一步的具体操作就是先查一下\(d\)在里面可以排多少\(rank\),假如\(rank \leq t\)那么就可以替换掉\(t\)了,直接一加一减维护就可以了.那么如果加入了一张翻倍卡会怎样呢?会发现如果加了一张翻倍的,\(t\)也会增加一,那么就再加入一个,也就是先把\(t+1\),完了之后再插一次\(d\).
-
如果删除一张卡
同样的,先不看这个卡具体是不是翻倍卡,如果这个卡的\(rank\)是在\(t\)以内的,那么我就是要把\(cnt+1\)的值给拿过来,再把这个值删掉.同样的像上面一样查找就好了.其次如果他确实是一张翻倍卡,那么就也一样的去查\(cnt+1\).再把值重复更新一下.
那么具体应该怎么搞前\(t\)大的元素就推完了.知道了这个信息之后,可以顺带的维护一下整个序列的所有的和\(s\)以及前\(t\)个最大的和\(s_1\),这个很好做就顺便加减一下就好了.那么接着往下想怎么算答案:假设当前所有的翻倍卡的最小值是\(x\)的话,
- 如果\(x\)的排名是在\(t\)以内的,也就是\(rank \leq t\)的话,显然他自己是不能作为翻倍的加入进去的,必须把它抠出来,也就是说,现在翻倍的和,实际上是前\(t+1\)个数的和减掉\(x\),因此想到这里,我们还要维护一个前\(t+1\)最大值的和\(S_2\).这个过程仿照上面同样维护就可以了.答案就是翻倍的部分加没翻倍的,实际上也就是总和加上要翻倍的部分,也就是\(s + s_2 - x\).
- 反之,也就是不在\(t\)以内,这个时候\(x\)就不用纳入考虑了,直接让前面的翻倍再加上去就好了,答案就是\(s_1+s\).
之后对于怎么动态的查找最小值\(x\).显然加一个\(set\)就够了.
细节问题参考代码吧.
最后提一下:这个题的查找是按最大值排的排名,但是我的板子是最小的排名,我直接拉了个板子发现不对劲又自己打了半天fhq,发现好多内容都忘得差不多了...看来有时候还是要复习一下板子的内容,突然一下都不会写了.
代码
#include
using namespace std;
typedef long long ll;
#define int ll
const int M = 1e6 + 7 + 2e5;
int n,idx,m;
int root = 0;
struct node
{
int l,r;
int val,key;
int size;
}tree[M];
void update(int u)
{
tree[u].size = tree[tree[u].l].size + tree[tree[u].r].size + 1;
}
void split(int u,int x,int &l,int &r)
{
if(!u)
{
l = r = 0;
return ;
}
if(tree[u].val >= x)
{
l = u;
split(tree[u].r,x,tree[u].r,r);
}
else
{
r = u;
split(tree[u].l,x,l,tree[u].l);
}
update(u);
}
void getnode(int x)
{
++idx;
tree[idx].size = 1;
tree[idx].l = tree[idx].r = 0;
tree[idx].val = x;tree[idx].key = rand();
}
int merge(int l,int r)
{
if(!l || !r) return l + r;
if(tree[l].key < tree[r].key)
{
tree[l].r = merge(tree[l].r,r);
update(l);return l;
}
else
{
tree[r].l = merge(l,tree[r].l);
update(r);return r;
}
}
int kth(int u,int k)
{
if(k <= tree[tree[u].l].size) return kth(tree[u].l,k);
else if(k == tree[tree[u].l].size + 1) return u;
else
{
k -= tree[tree[u].l].size + 1;
return kth(tree[u].r,k);
}
}
int find_rank_by_value(int v)
{
int l,r;
split(root,v + 1,l,r);
int res = tree[l].size + 1;
root = merge(l,r);
return res;
}
void insert(int x)
{
int l,r;
split(root,x,l,r);
getnode(x);
root = merge(merge(l,idx),r);
}
void remove(int x)
{
int l,r,p;
split(root,x,l,r);
split(l,x + 1,l,p);
p = merge(tree[p].l,tree[p].r);
root = merge(merge(l,p),r);
}
int find_value_by_rank(int x)
{
return tree[kth(root,x)].val;
}
signed main()
{
scanf("%lld",&n);
int cnt = 0,s1 = 0,s2 = 0,s = 0,all = 0;
set st;
for(int i = 1; i <= n;++i)
{
int type,d;scanf("%lld%lld",&type,&d);
s += d;
int real = abs(d);
int rk = find_rank_by_value(real);
int res = 0;
if(d > 0)
{
if(rk <= cnt) s1 += real - find_value_by_rank(cnt);
if(rk <= cnt + 1) s2 += real - find_value_by_rank(cnt + 1);
insert(real);
if(type == 1)
{
++cnt;
s1 += find_value_by_rank(cnt);
s2 += find_value_by_rank(cnt + 1);
st.insert(d);
}
}
else
{
if(rk <= cnt) s1 -= real - find_value_by_rank(cnt + 1);
if(rk <= cnt + 1) s2 -= real - find_value_by_rank(cnt + 2);
remove(real);
if(type == 1)
{
s1 -= find_value_by_rank(cnt);
s2 -= find_value_by_rank(cnt + 1);
--cnt;
st.erase(real);
}
}
if(cnt && find_rank_by_value(*(st.begin())) <= cnt)
res = s + s2 - *(st.begin());
else res = s + s1;
printf("%lld\n",res);
}
return 0;
}