C-Tk王国的括号
模拟链表删除的写法。由于从左往右遍历,因此next指针可以不需要(因为总是要检查下一个)。
每次检查当前节点和链表的前一个节点,如果匹配,那么删除这两个节点。详见代码。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef vector<int> vi;
typedef unsigned long long ull;
const int N = 200020;
int n;
char s[N];
int pre[N];
bool match(char x, char y) {
if(x >= 'N' && x <= 'Z') {
return x - 'A' + y - 'A' == 25;
} else if(x >= 'a' && x <= 'm') {
return x - 'a' + y - 'a' == 25;
}
return false;
}
int main(){
// freopen("in.txt", "r", stdin);
scanf("%d", &n);
scanf("%s", s + 1);
for(int i = 1; i <= n; ++ i) {
pre[i] = i - 1;
// nxt[i] = i + 1;
}
int d = 0;
for(int i = 1; i <= n; ++ i) {
if(match(s[pre[i]], s[i])) {
d += 2;
int t = pre[pre[i]];
pre[i + 1] = t;
}
}
printf("%d\n", n - d);
return 0;
}
D-魔法棒
观察到每次分裂都会增加a2−1a^2-1a2−1个节点,因此答案就是需要我们构造出一种∑(ai2−1)=x−1\sum{(a_i^2-1)}=x-1∑(ai2−1)=x−1
观察一下a2−1a^2-1a2−1这个数列:3,8,15,24,35,48...3,8,15,24,35,48...3,8,15,24,35,48...
容易注意到(!)构造得到的和或者是3的倍数,或者可以由一个8加上3的倍数,或者16加上3的倍数。
∴\therefore∴大于等于15时都有解。剩下的1-14随便搞搞。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef vector<int> vi;
typedef unsigned long long ull;
ll n;
bool f[15];
void solve() {
scanf("%lld", &n);
n --;
if(n >= 15)
printf("Yes\n");
else {
memset(f, 0, sizeof(f));
f[0] = 1;
for(int i = 2; i * i - 1 <= n; ++ i) {
int a = i * i - 1;
for(int j = 0; j < 15; ++ j) {
f[a + j] |= f[j];
}
}
if(f[n])
printf("Yes\n");
else
printf("No\n");
}
}
int main(){
// freopen("in.txt", "r", stdin);
int t;
scanf("%d", &t);
while(t--) solve();
return 0;
}
E-字符串min-18
老实说这场的E不是很trick,但一开始不会做因为看起来很复杂。
看了一下题解,马上有了一个普遍性的划分:遍历0和1的每一位分界线,然后把问题分解成两个子问题。
1.设p0(i)p_0(i)p0(i)是到i位的前缀全改成0的最小代价
2.设s1(i)s_1(i)s1(i)是从i位的后缀全改成1的最小代价。此处都为1-index
容易得出答案=min(p0(i)+s1(i+1))\min(p_0(i)+s_1(i+1))min(p0(i)+s1(i+1))
当然这样是不对的,因为前面有可能翻了奇数次,此时等价于后缀全改成0.因此还要记下翻转的次数,以及后缀改为0的最小代价s0(i)s_0(i)s0(i)。
问题1比较好求,当si≠si−1s_i \neq s_{i-1}si=si−1时再翻一次,反之就不用翻。
问题2稍微麻烦一点,比如我们计算后缀全改成1的代价,以00010010为例。
i=1,si=0i=1,s_i=0i=1,si=0时第一位要翻成1,这时第二位第三位不需要翻(因为和第一位一起都是0),需要去检查右边首次出现1的第四位,即s1(1)=s0(4)+ais_1(1)=s_0(4)+a_is1(1)=s0(4)+ai
i=2,3i=2,3i=2,3时同上。
i=4i=4i=4时不需要翻动。s1(4)=s1(5)s_1(4)=s_1(5)s1(4)=s1(5)
这样的话还需要从右往左维护右边首次出现1的位置。
计算后缀全改成0的方法也同上。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef vector<int> vi;
typedef unsigned long long ull;
const int N = 200020;
char s[N];
ll p0[N];
ll flip[N];
ll s0[N], s1[N];
ll a[N];
void solve() {
int n;
scanf("%d", &n);
scanf("%s", s + 1);
for(int i = 1; i <= n; ++ i)
scanf("%lld", &a[i]);
s[0] = '0';
for(int i = 1; i <= n; ++ i) {
flip[i] = s[i] == s[i - 1] ? flip[i - 1] : flip[i - 1] + 1;
p0[i] = s[i] == s[i - 1] ? p0[i - 1] : p0[i - 1] + a[i];
}
int next1 = n + 1, next0 = n + 1;
s1[n + 1] = 0, s0[n + 1] = 0;
for(int i = n; i >= 1; -- i) {
if(s[i] == '1') {
s1[i] = s1[i + 1];
s0[i] = s1[next0] + a[i];
next1 = i;
} else {
s1[i] = s0[next1] + a[i];
s0[i] = s0[i + 1];
next0 = i;
}
}
ll ans = 1ll << 60;
for(int i = 0; i <= n; ++ i) {
ll t = p0[i] + ((flip[i] & 1) ? s0[i + 1] : s1[i + 1]);
ans = min(ans, t);
}
printf("%lld\n", ans);
}
int main(){
// freopen("in.txt", "r", stdin);
int t;
scanf("%d", &t);
while(t--) {
solve();
}
return 0;
}
F-Zeeman的异或数组
线性基板子题
第一个d[i]=0的位置就得到无法用基构造的整数1<<i
线性基和线性空间中的基概念非常类似,做的时候可以类比一下。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef vector<int> vi;
typedef unsigned long long ull;
const int N = 500050;
ll d[64];
ll a[N];
int n;
void addx(ll x) {
for(int i = 60; i >= 0; -- i) {
if(x & (1ll << i)) {
if(d[i])
x ^= d[i];
else {
d[i] = x;
break;
}
}
}
}
void solve() {
scanf("%d", &n);
memset(d, 0, sizeof(d));
for(int i = 1; i <= n; ++ i ){
scanf("%lld", &a[i]);
addx(a[i]);
}
for(int i = 0; i <= 60; ++ i) {
if(d[i] == 0) {
printf("%lld\n", 1ll << i);
break;
}
}
}
int main(){
// freopen("in.txt", "r", stdin);
int t;
scanf("%d", &t);
while(t--) solve();
return 0;
}