本场链接:Codeforces Round #700 (Div. 2)
A. Yet Another String Game
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
const int N = 55;
char s[N];
int main()
{
int T;scanf("%d",&T);
while(T--)
{
scanf("%s",s + 1);int n = strlen(s + 1);
forn(i,1,n)
{
if(i & 1) s[i] = (s[i] == 'a' ? 'b' : 'a');
else s[i] = (s[i] == 'z' ? 'y' : 'z');
}
printf("%s
",s + 1);
}
return 0;
}
B. The Great Hero
思路
这个题唯一一点比较特殊的点在于:如果人无论如何都会死,事实上也不一定就是NO
,因为可能出现"同归于尽"的情况.这个时候可以讨论一下:首先第一种就是所有人直接一个一个敲死都不会让人死亡,这种肯定直接是YES
,那么剩下的情况就是人无论如何都会死,可以枚举一下每个怪物作为最后一个去击杀的时候是否会出现"同归于尽"的情况,具体来说可以先把所有怪物完全击杀的代价算出来做和,然后枚举每个怪物作为最后一个的时候,当前剩余的血量和怪物的代价算一下是否会同归于尽就可以了.
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
const int N = 1e5+7;
struct Node
{
ll a,b,cost;
bool operator<(const Node& r) const
{
return cost < r.cost;
}
}a[N];
int main()
{
ios::sync_with_stdio(0);cin.tie(0);
int T;cin >> T;
while(T--)
{
ll A,B,n;cin >> A >> B >> n;
forn(i,1,n) cin >> a[i].a;
forn(i,1,n) cin >> a[i].b;
forn(i,1,n) a[i].cost = (a[i].b + A - 1) / A * a[i].a;
ll sum = 0;forn(i,1,n) sum += a[i].cost;
if(B >= sum)
{
cout << "YES
";
continue;
}
bool ok = 0;
forn(i,1,n)
{
if(B <= sum - a[i].cost) continue;
ll ts = (a[i].b + A - 1) / A;
// cout << ts << " " << B - sum + a[i].cost << endl;
if((ts - 1) * a[i].a <= B - sum + a[i].cost)
{
ok = 1;
break;
}
}
if(ok) cout << "YES
";
else cout << "NO
";
}
return 0;
}
C. Searching Local Minimum
思路
有一个更巧妙的思考方式:我们事实上可以把这个问题看做是在维护一个区间([l,r])并且始终保证(l)端点的值小于他左边一个位置的值,(r)端点的值小于他右边一个位置的值.接下来如果我们能找到一个收缩这个区间到一个点的办法,也就是说一定能保证(l=r)的话,自然就找到了答案,这个收缩区间的方法可以求整个区间的中点(mid),那么现在的问题就是看(l)和(r)谁应该被移动到(mid),如果直接看(mid)和两个端点的值的关系肯定是没用的,不妨看(mid)和(mid+1)的大小关系,剩下的做法就是顺理成章的了.
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
const int N = 1e5+7;
int a[N];
int main()
{
ios::sync_with_stdio(0);cin.tie(0);
int n;cin >> n;
if(n == 1)
{
cout << "? 1" << endl;
int x;cin >> x;
cout << "! " << x << endl;
return 0;
}
int l = 1,r = n;
while(l < r)
{
int mid = l + r >> 1;
if(!a[mid])
{
cout << "? " << mid << endl;
cin >> a[mid];
}
if(!a[mid + 1])
{
cout << "? " << mid + 1 << endl;
cin >> a[mid + 1];
}
if(a[mid] < a[mid + 1]) r = mid;
else l = mid + 1;
}
cout << "! " << l << endl;
return 0;
}
Painting the Array
由于线段树dp做法可以过两个就放在一起了
D1 思路
假设我们现在在考虑(a_i)加入两个序列的影响,那么在(a_i)实际的加入产生影响之前,我们只知道(a_{i-1})一定是其中一个序列的末尾元素,另外有一个序列还不知道,对于这个题来说我们可以说只关注一个序列的末尾元素,所以其中一个序列的信息已经完整的知道了,另外一个不妨做一个dp,用(f[x])表示末尾为(x)的序列的答案最大值是多少.现在这个问题可以这样处理了:首先我们知道(a_{i-1})一定是其中一个序列的末尾,其次可以用dp的方式维护另外一个序列的各种情况下的答案.
考虑(a_i)加入两个序列产生的影响:
- 如果(a_i eq a_{i-1}),那么可以直接把(a_i)丢到(a_{i-1})那条序列的末尾,这个时候,另外一条序列里面所有答案都应该(+1).
- 如果(i>1)的话,我们也可以把(a_i)放在另外一个序列的末尾,但是这个时候产生了一个问题:对于下一次来(a_{i+1})的时候,现在dp维护的值末尾的元素是(a_i),根据定义来说应该维护的是另一个序列才对.也就是说此时应该交换一下两个序列.这个时候应该让(f[a[i - 1]]),也就是末尾值是上一个值的答案的最大值,增加以(a_i)为末尾的另外一个序列会产生的影响.更详细的说,这里根据直觉来看的话应该是让(f[a[i]] = max{f[x] + [x eq a[i]]}),其中(x)是那条序列的末尾元素,值可以任取.符合直觉的是:让(a_i)变成新的末尾,直接更新(a_i)这个位置上的值,但是这里因为维护的对象要交换一次,所以事实上应该是修改(f[a[i - 1]] = max{f[x] + [x eq a[i]]}).
- 然后我们可以发现粗暴维护dp的值肯定是不行的,但是他形式上的操作等同于区间加/区间取max/单点更新max.所以可以用一个线段树来维护值最后答案就是所有的最大值.
D1代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
const int N = 2e5+7;
struct Node
{
int l,r,v;
int add;
}tr[N * 4];
int a[N];
void pushup(int u)
{
tr[u].v = max(tr[u << 1].v,tr[u << 1 | 1].v);
}
void pushdown(int u)
{
auto& s = tr[u];
auto& lf = tr[u << 1];
auto& rt = tr[u << 1 | 1];
if(s.add)
{
lf.add += s.add;
lf.v += s.add;
rt.add += s.add;
rt.v += s.add;
s.add = 0;
}
}
void build(int u,int l,int r)
{
if(l == r) tr[u] = {l,r,0,0};
else
{
int mid = l + r >> 1;
tr[u] = {l,r,0,0};
build(u << 1,l,mid),build(u << 1 | 1,mid + 1,r);
}
}
void modify(int u,int l,int r,int v)
{
if(tr[u].l >= l && tr[u].r <= r)
{
tr[u].v += v;
tr[u].add += v;
return ;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) modify(u << 1,l,r,v);
if(r > mid) modify(u << 1 | 1,l,r,v);
pushup(u);
}
void modify(int u,int p,int v)
{
if(tr[u].l == p && p == tr[u].r)
{
tr[u].v = max(tr[u].v,v);
return ;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(p <= mid) modify(u << 1,p,v);
if(p > mid) modify(u << 1 | 1,p,v);
pushup(u);
}
int query(int u,int l,int r)
{
if(tr[u].l >= l && tr[u].r <= r) return tr[u].v;
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1,res = 0;
if(l <= mid) res = max(res,query(u << 1,l,r));
if(r > mid) res = max(res,query(u << 1 | 1,l,r));
return res;
}
int main()
{
int n;scanf("%d",&n);
forn(i,1,n) scanf("%d",&a[i]);
build(1,1,n);
forn(i,1,n)
{
int rt = max({query(1,1,a[i] - 1) + 1,query(1,a[i] + 1,n) + 1,query(1,a[i],a[i])});
if(a[i] != a[i - 1]) modify(1,1,n,1);
if(i > 1) modify(1,a[i - 1],rt);
}
printf("%d",tr[1].v);
return 0;
}
D2
有了上面的步骤,D2也是很自然的把max换成min就可以了.但是没这么直接,还有一点小问题:首先D1里面讨论的时候事实上忽略了初值的问题,也就是(f[0]=0),这个在D1里面求最大值是不会有区别的,因为除了第一次不会有任何转移需要(0)位的元素,但是在D2里面求最小值是有可能的,而线段树的下标是从(1)开始的,所以我的代码里面把所有的位置全体往后挪动了一位,用(1)位的值表示(0)位.
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
const int N = 2e5+7,INF = 1e9;
struct Node
{
int l,r,v;
int add;
}tr[N * 4];
int a[N];
void pushup(int u)
{
tr[u].v = min(tr[u << 1].v,tr[u << 1 | 1].v);
}
void pushdown(int u)
{
auto& s = tr[u];
auto& lf = tr[u << 1];
auto& rt = tr[u << 1 | 1];
if(s.add)
{
lf.add += s.add;
lf.v += s.add;
rt.add += s.add;
rt.v += s.add;
s.add = 0;
}
}
void build(int u,int l,int r)
{
if(l == r) tr[u] = {l,r,INF,0};
else
{
int mid = l + r >> 1;
tr[u] = {l,r,INF,0};
build(u << 1,l,mid),build(u << 1 | 1,mid + 1,r);
}
}
void modify(int u,int l,int r,int v)
{
if(tr[u].l >= l && tr[u].r <= r)
{
tr[u].v += v;
tr[u].add += v;
return ;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) modify(u << 1,l,r,v);
if(r > mid) modify(u << 1 | 1,l,r,v);
pushup(u);
}
void modify(int u,int p,int v)
{
if(tr[u].l == p && p == tr[u].r)
{
tr[u].v = min(tr[u].v,v);
return ;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(p <= mid) modify(u << 1,p,v);
if(p > mid) modify(u << 1 | 1,p,v);
pushup(u);
}
int query(int u,int l,int r)
{
if(tr[u].l >= l && tr[u].r <= r) return tr[u].v;
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1,res = INF;
if(l <= mid) res = min(res,query(u << 1,l,r));
if(r > mid) res = min(res,query(u << 1 | 1,l,r));
return res;
}
int main()
{
int n;scanf("%d",&n);
forn(i,1,n) scanf("%d",&a[i]);
build(1,1,n + 1);
modify(1,1,0);
forn(i,1,n)
{
int rt = min({query(1,1,a[i]) + 1,query(1,a[i] + 2,n + 1) + 1,query(1,a[i] + 1,a[i] + 1)});
if(a[i] != a[i - 1]) modify(1,1,n + 1,1);
if(i > 1) modify(1,a[i - 1] + 1,rt);
}
printf("%d",tr[1].v);
return 0;
}