E.Color Sequence
算法:数学 + 前缀和
因为合法字串含的每一种颜色都是偶数,所以我们可以利用异或判断字串是否合法,因为颜色的种类不超过20,所以我们可以直接用int存下,任何在二进制下每一位代表一种颜色,即0表示:2(1), 10(1), 1表示:2(10), 10(2), 2表示:2(100), 10(4)…
那么怎么去计算里面有多个合法的字串呢?
首先我们得知道,连续异或一个数两次,那么这个数就等于原来那个数,即:a ^ b ^ b = a,
有此可知,只要s[i] == s[j],则 i + 1 ~ j 这段字串一定符合要求。
下面说一下实现,首先我们可以用s[ ]数组存下来他们的异或前缀和,然后用mp[ ]来记录有多少种可能
参考代码:
#include <iostream>
using namespace std;
const int N = 1e6 + 10, M = 3e6;
typedef long long LL;
int a[N], s[N], mp[M];
int main()
{
int n;
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> a[i];
s[i] = s[i - 1] ^ (1 << a[i]);
}
LL ans = 0;
// mp[0] = 1;
// 这里必须从0开始,或者也可以令mp[0] = 1
for(int i = 0; i <= n; i++)
{
ans += mp[s[i]];
mp[s[i]]++;
}
cout << ans << endl;
}
H.Sequence
算法:线段树 + 二分
最近刚学了线段数,属于是现学现用吧,感觉这道题不难在线段树,而是二分上,实属是难调
这道题非常容易看出是线段树的题,只用用线段树来维护最小值,然后在用二分去找左边最近的小于自己的,和右边最近小于自己的(包含自己),然后答案就是,左边的个数 * 右边的个数
这里需要注意的是,找左边的话,在有右区间找,找右边的话,在左区间找,然后因为是使距离尽可能大,直接上二分模板即可
参考代码:
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10,INF = 0x3f3f3f3f;
struct Node
{
int l, r;
int minn;
}tr[4 * N];
int n, m;
int a[N];
void pushup(int u)
{
tr[u].minn = min(tr[u << 1].minn, tr[u << 1 | 1].minn);
}
void build(int u, int l, int r)
{
if(l == r)
{
tr[u] = {l, r, a[l]};
return ;
}
int mid = l + r >> 1;
tr[u] = {l, r};
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
pushup(u);
}
void modify(int u, int x, int y)
{
if(tr[u].l == x && tr[u].r == x)
{
tr[u] = {x, x, y};
return ;
}
int mid = tr[u].l + tr[u].r >> 1;
if(x <= mid) modify(u << 1, x, y);
else modify(u << 1 | 1, x, y);
pushup(u);
}
int ask(int u, int l, int r)
{
if(l <= tr[u].l && r >= tr[u].r) return tr[u].minn;
int mid = tr[u].l + tr[u].r >> 1;
int ans = INF;
if(l <= mid) ans = min(ans, ask(u << 1, l, r));
if(r > mid) ans = min(ans, ask(u << 1 | 1, l, r));
return ans;
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
build(1, 1, n);
int op, x, y;
while(m--)
{
cin >> op;
if(op == 1)
{
scanf("%d%d", &x, &y);
a[x] = y;
modify(1, x, y);
}
else
{
scanf("%d", &x);
int l = 1, r = x;
while(l < r)
{
int mid = l + r + 1 >> 1;
// 在右区间找
if(ask(1, x - mid + 1, x) >= a[x]) l = mid;
else r = mid - 1;
}
int ans1 = l;
l = 1, r = n - x + 1;
while(l < r)
{
int mid = l + r + 1 >> 1;
// 在左区间找
if(ask(1, x, x + mid - 1) >= a[x]) l = mid;
else r = mid - 1;
}
int ans2 = l;
printf("%lld\n", (LL)ans1 * ans2);
}
}
return 0;
}
I.Simple Math Problem
一道找规律题,直接看可能看不出什么规律,我们可以斜着看,就是一个菱形,这样规律就比较就挺容易找到,规律就不多了说了
参考代码:
#include <iostream>
using namespace std;
typedef long long LL;
int main()
{
int n, x, y;
cin >> n >> x >> y;
x++, y++;
// x + y - 1就是代表是第几行,这题要分上半部分和下半部分求解
if(x + y - 1 <= n)
{
LL ans = (LL)(x + y - 1) * (x + y - 2) / 2 + x - 1;
cout << ans << endl;
}
// 下半部分求解,因为是对称的,所以可以利用上半部分来求解,即 n * n - 上半部分
else
{
x = n - x + 1, y = n - y + 1;
LL t = (LL)(x + y - 1) * (x + y - 2) / 2 + x ;
// cout << t << endl;
LL ans = n * n - t;
cout << ans << endl;
}
return 0;
}
K.Travel Expense
算法:图论 + 二分
这个很容易看的出是多源最短问题,就是在判断答案的时候,用二分求找答案,因为答案要尽可能大,所以直接是二模板即可
参考代码:
#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 110, INF = 1e9;
int n, m;
int s, t, b;
int d[N][N];
void init()
{
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
{
if(i == j) d[i][j] = 0;
else d[i][j] = INF;
}
}
void floyd()
{
for(int k = 1; k <= n; k++)
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
{
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
}
bool judge(int mid)
{
// printf("hhh%d\n", mid);
LL sum = 0;
for(int i = 1; i <= d[s][t]; i++)
{
sum += (LL)pow(mid, i);
// printf("***%lld\n", sum);
if(sum > b) return false;
}
return true;
}
int main()
{
cin >> n >> m;
init();
while(m--)
{
int a, b;
cin >> a >> b;
d[a][b] = 1; d[b][a] = 1;
}
floyd();
int q;
cin >> q;
while(q--)
{
scanf("%d%d%d", &s, &t, &b);
// cout << d[s][t] << endl;
int l = 0, r = b;
while(l < r)
{
int mid = l + r + 1 >> 1;
// 因为答案要求最大,所以这里要判断答案能否更大,即l = mid
if(judge(mid)) l = mid;
else r = mid - 1;
}
cout << l << endl;
}
return 0;
}