B. Swaps(思维)
两个序列,a序列是一个1 ~ 2n的奇数排列,b是一个1 ~ 2n的偶数排列,如何交换使得a的字典序小于b。求最小的交换次数。
思维题。两种写法,都是很棒的写法。和排序都有关,第一种没有直接排序,但是也有排序的思想。都是利用贪心,缩小了答案的范围。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 5, mod = 998244353;
int a[N], b[N];
struct node {
int val, pos;
} a1[N], b1[N];
signed main()
{
/* ios::sync_with_stdio(0);
cin.tie(0); */
int t;
cin >> t;
a[0] = 1e9 + 7;
while(t--) {
int n;
scanf("%lld", &n);
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
a[i] = min(a[i], a[i - 1]);
}
for (int i = 1; i <= n; i++) scanf("%lld", &b[i]);
int num = n;
int ans = 1e9 + 7;
for (int i = 1; i <= n; i++) {
while(b[i] > a[num]) num--;
ans = min(ans, num + i - 1);
}
cout << ans << endl;
}
return 0;
}
如果a [ i ] > b [ num ] 的话,那么b [ num ] 之前的数一定无法和 a [ i ] 之后的数进行匹配。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 5, mod = 998244353;
int b[N];
struct node {
int val, pos;
bool operator < (const node &k) const {
return val < k.val;
}
} a[N];
signed main()
{
/* ios::sync_with_stdio(0);
cin.tie(0); */
int t;
cin >> t;
while(t--) {
int n;
scanf("%lld", &n);
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i].val);
a[i].pos = i;
}
for (int i = 1; i <= n; i++) scanf("%lld", &b[i]);
sort(a + 1, a + 1 + n);
int ans = 1e9 + 7;
int num = 1;
for (int i = 1; i <= n; i++) {
while(a[i].val > b[num]) num++;
ans = min(ans, num - 1 + a[i].pos - 1);
}
cout << ans << endl;
}
return 0;
}
(E) Easy Scheduling
很多时候,尽量的的模拟题意,就能避开大多数wa。
这题就是一颗二叉树,每次最多选择p个节点进入准备,但是一开始只能选1个节点,然后是2,4,8个。但是不能超过p个。所以一个循环模拟就好了。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 500 + 5, mod = 998244353;
signed main()
{
/* ios::sync_with_stdio(0);
cin.tie(0); */
int t;
cin >> t;
while(t--) {
int h, p;
scanf("%lld%lld", &h, &p);
int sum = (long long)pow(2, h) - 1;
// cout << sum << endl;
int k = 1;
int ans = 0;
for (int i = 1; k <= p; k *= 2, i++) {
sum -= k;
ans = i;
if (sum <= 0) {
// ans = i;
break;
}
}
int a1 = ceill(sum * 1.0 / p);
cout << a1 + ans << endl;
}
return 0;
}
(D) Backspace(子序列+模拟)
输入字符是一个一个输入的,如果将其中一个字符替换成删除,那么就相当于原串删除两个字符。
这题倒过来验证t是否为s的子序列就好了,匹配得上,那就继续匹配,匹配不上,那就模拟删除操作,不知道dp怎么写。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 5, mod = 998244353;
signed main()
{
/* freopen("data5.in","r",stdin);
freopen("data5.out","w",stdout); */
ios::sync_with_stdio(0);
cin.tie(0);
int T;
cin >> T;
while(T--) {
string s, t;
cin >> s >> t;
int lens = s.length(), lent = t.length();
int i = lens, j = lent;
bool ck = 0;
for (; i >= 0; ) {
if (s[i] == t[j]) i--, j--;
else i -= 2;
if (j < 0) {
ck = 1;
break;
}
if (i < 0) break;
}
if (ck) cout << "YES" << endl;
else cout << "NO" << endl;
}
return 0;
}
© Penalty(贪心或暴力dfs)
一道回溯的dfs,但是写了好久,搜索的功底太差了。
想法就是遇见一个?就分成两支往下搜索。
有很多个细节没有注意到:
1.dfs和循环嵌套,这样写大大增加了复杂度,应该是阶乘级别的了。
2.两个分支往下的时候,要判断step是奇数还是偶数,奇数才能给a+1,偶数才能给b+1。
3.遇到能停止发球的情况,不能马上return,因为可能还有更优解。
4.形成分支往下搜索的时候,这一次的状态相当于已经确定了,要立即进行判断是否能停止发球,不能等进入下一个状态再进行判断。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 5, mod = 998244353;
int ans;
string s;
void dfs(int step, int a, int b)
{
if (step >= 10) {
ans = min(ans, step);
return;
}
if (s[step] == '1') {
if (step & 1) a++;
else b++;
}
int sa = (10 - step) / 2, sb = (10 - step) / 2;
if (step & 1) sb++;
if (a + sa < b || b + sb < a) {
ans = min(ans, step);
// printf("st = %lld, a = %lld, b = %lld, ans = %lld\n", step, a, b, ans);
// return;
}
if (s[step] == '?') {
if (step & 1) {
int sa = (10 - step) / 2, sb = (10 - step) / 2;
if (step & 1) sb++;
if (a + sa + 1 < b || b + sb < a + 1) {
ans = min(ans, step);
}
dfs(step + 1, a + 1, b);
}
if ((step & 1) == 0) {
dfs(step + 1, a, b + 1);
int sa = (10 - step) / 2, sb = (10 - step) / 2;
if (step & 1) sb++;
if (a + sa < b + 1 || b + sb + 1 < a) {
ans = min(ans, step);
}
}
dfs(step + 1, a, b);
}
else dfs(step + 1, a, b);
}
signed main()
{
/* ios::sync_with_stdio(0);
cin.tie(0); */
int T;
cin >> T;
while(T--) {
cin >> s;
s = " " + s;
ans = 1e9 + 7;
dfs(1, 0, 0);
cout << ans << endl;
}
return 0;
}
其实还有复杂度更低的O(N)的写法,也很好理解,贪心的将问号单一转为其中一方得分就好了,试两次,取min。
#include<bits/stdc++.h>
using namespace std;
// 9 19 29 39
#define MAX 10000
int main(){
int T;
cin >> T;
while(T--){
string s;
cin >> s;
int A = 10;
int B = 10;
int scoreA = 0;
int scoreB = 0;
for(int i = 0; s[i] != '\0'; i++){
if(i % 2 == 0){
if(s[i] == '1' || s[i] == '?')
scoreA++;
}
if(i % 2 == 1){
if(s[i] == '1')
scoreB++;
}
if(i % 2 == 0){
if(scoreA + 5 - (i+2)/2 < scoreB || scoreB + 5 - i/2 < scoreA){
A = i+1;
break;
}
}
else{
if(scoreA + 5 - (i+1)/2 < scoreB || scoreB + 5 - (i+1)/2 < scoreA){
A = i+1;
break;
}
}
}
scoreA = 0;
scoreB = 0;
for(int i = 0; s[i] != '\0'; i++){
if(i % 2 == 0){
if(s[i] == '1')
scoreA++;
}
if(i % 2 == 1){
if(s[i] == '1' || s[i] == '?')
scoreB++;
}
if(i % 2 == 0){
if(scoreA + 5 - (i+2)/2 < scoreB || scoreB + 5 - i/2 < scoreA){
B = i+1;
break;
}
}
else{
if(scoreA + 5 - (i+1)/2 < scoreB || scoreB + 5 - (i+1)/2 < scoreA){
B = i+1;
break;
}
}
}
cout << min(A,B) << endl;
}
}
J. Jeopardy of Dropped Balls(并查集)
思路是正确的,用数据结构优化连续的2,来达到提高下落的速度。
但是为什么暴力都不TLE,而我不暴力却一直TLE,想不出来出了什么bug。
贴一下加了并查集的代码。
这题时限两秒,其实暴力模拟是能稳过的。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e3 + 5, mod = 1000000007;
int mp[N][N];
int f[N][N];
int getf(int x,int y) {
if (f[x][y] == x) return x;
return f[x][y] = getf(f[x][y], y);
}
signed main()
{
int n, m, k;
cin >> n >> m >> k;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
scanf("%lld", &mp[i][j]);
if (mp[i][j] == 2) f[i][j] = i + 1;
else f[i][j] = i;
}
}
while (k--) {
int i = 1, j;
scanf("%lld", &j);
while(mp[i][j]) {
if (mp[i][j] == 1) {
f[i][j] = f[i + 1][j];
mp[i][j] = 2;
j = j + 1;
}
else if (mp[i][j] == 3) {
f[i][j] = f[i + 1][j];
mp[i][j] = 2;
j = j - 1;
}
else i = getf(i, j);
}
printf("%lld ", j);
}
return 0;
}
E. Air Conditioners(dp)
每个格子的温度其实都是由相邻格子提供;
如果一个点不能更新它的下一个点,那么,它后面的点都不可能会被它更新。
首先把空调的值都放好在对应的pos里。
试想,先考虑空调只能影响其右边的格子,从左往右遍历一遍,不断地取min;
再考虑空调对左边的格子的影响,从右往左遍历一遍。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e5 + 5, INF = 0x3f3f3f3f;
int a[N], pos[N];
int f[N];
signed main()
{
int q;
cin >> q;
while (q--) {
int n, k;
scanf("%lld%lld", &n, &k);
for (int i = 1; i <= k; i++) scanf("%lld", &pos[i]);
for (int i = 0; i <= n + 1; i++) f[i] = INF;
for (int i = 1; i <= k; i++) {
int x;
scanf("%lld", &x);
f[pos[i]] = x;
}
for (int i = 1; i <= n; i++) f[i] = min(f[i], f[i - 1] + 1);
for (int i = n; i >= 1; i--) f[i] = min(f[i], f[i + 1] + 1);
for (int i = 1; i <= n; i++) printf("%lld ", f[i]);
printf("\n");
}
return 0;
}
© Moamen and XOR(分类讨论+位运算)
上上个月没做出的题,其实当时的思路是正确的,只是考虑的情况太多了,其实没那么多的。
题要求&的结果大于等于xor,要大于,只有1 > 0的情况,那么&运算结果是1,xor运算结果是0,所以要求该二进制位上,所有数字均为1,且一共的n个数是偶数。
那么这题就开始奇偶讨论。
偶数:
1 > 0,第 s 位上全为 1,s 位以前的位满足 1 == 1 或者是 0 == 0;s 位以后的数任意填,(重做的时候我忽略了后面全为0的情况,导致wa)。
奇数:
不可能存在 1 > 0 的情况,只有1 == 1和 0 == 0。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e5 + 5, mod = 1e9 + 7;
int a[N], pos[N];
int f[N], inv[N];
int qpow(int x, int n)
{
int ret = 1;
while(n) {
if (n & 1) ret = ret * x % mod;
x = x * x % mod;
n >>= 1;
}
return ret;
}
int C(int n, int m)
{
if (m == 0 || n == m) return 1;
return f[n] * inv[m] % mod * inv[n - m] % mod;
}
signed main()
{
int q;
cin >> q;
f[0] = 1;
for (int i = 1; i <= 200005; i++) f[i] = f[i - 1] * i % mod;
for (int i = 1; i <= 200005; i++) inv[i] = qpow(f[i], mod - 2);
while (q--) {
int n, k;
cin >> n >> k;
int ans = 0;
if (n & 1) {
for (int s = 0; s <= k; s++) {
ans = (ans + C(k, s) * qpow(qpow(2, n - 1), s) % mod) % mod;
}
}
else {
for (int p = k; p >= 1; p--) {
ans = (ans + qpow(qpow(2, p - 1), n) * qpow(qpow(2, n - 1) - 1, k - p) % mod) % mod;
}
ans = (ans + qpow(qpow(2, n - 1) - 1, k)) % mod;
}
cout << ans << endl;
}
return 0;
}
© Make Them Equal 分类讨论+细节
很容易考虑不全的一道题。
在字符串下标中选一个数x,不能被x整除的下标就可以改为目标字符,求一个最小的修改次数。
一开始想,如果是选择下标n,那么最多只需要修改两次就好了, 1 ~ n - 1 一次,如果第 n 位不是目标字符,那么就再修改一次。
结果就wa了。于是又想选择 n - 1 ?结果又是 wa 了好多回。
最后意识到,从 x 从 n / 2 + 1 开始,所有的下标就已经不能被 x 整除了,所以,从 n / 2 + 1 ~ n,只要有一个下标为目标字符,就可以直接输出这个下标,修改次数为1。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5e3 + 5, mod = 1e9 + 7;
signed main()
{
int T;
cin >> T;
while(T--) {
int n;
char x;
string s;
cin >> n >> x >> s;
if (n == 1) {
if (s[0] == x) cout << 0;
else {
cout << 1 << endl;
cout << 1;
}
cout << endl;
continue;
}
bool ck = 1;
for (int i = 0; i < n; i++) {
if (s[i] != x) {
ck = 0;
break;
}
}
if (ck) {
cout << 0 << endl;
continue;
}
ck = 0;
for (int i = n / 2; i < n; i++) {
if (s[i] == x) {
cout << 1 << endl;
cout << i + 1 << endl;
ck = 1;
break;
}
}
if (!ck) {
cout << 2 << endl;
cout << n - 1 << " " << n << endl;
}
}
return 0;
}
// ftp://192.168.101.1/
(F) Array Stabilization (AND version)分类讨论+思维+位运算
洛谷的题解质量真的很高,把思路讲的明明白白的。
一个 0 1 字符串,一次操作是循环右移 d 位后,与原串进行 & 操作,获得一个新串。求最小的操作次数。
每一个位置上的数字都有她自己的固定路线,她只会走到固定的那些个位子上去,不会被不相干的位子上的数字所影响。
所以分离出这些位子,分成 gcd(n , d) 组。如果分离出的一组数全为 1 ,那么答案就是-1。其他的情况,容易看出答案是最长的连续 1 的长度。
下面的代码之所以分离两次,是因为开头结尾要是可能会连续,不能漏解。
#include <bits/stdc++.h>
#define int long long
namespace mystd {
inline int read() {
int w = 1, q = 0;
char ch = ' ';
while (ch != '-' && (ch < '0' || ch > '9')) ch = getchar();
if (ch == '-') w = -1, ch = getchar();
while (ch >= '0' && ch <= '9') q = q * 10 + ch - '0', ch = getchar();
return w * q;
}
inline void write(int x) {
if (x < 0) {
x = ~(x - 1);
putchar('-');
}
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace std;
using namespace mystd;
const int maxn = 1e6 + 100;
int t, n, d, g, ans, s[maxn], tp[maxn << 1];
int gcd(int x, int y) {
if (y == 0) return x;
else return gcd(y, x % y);
}
signed main() {
t = read();
while (t--) {
ans = 0;
n = read();
d = read();
g = gcd(n, d);
for (int i = 1; i <= n; i++) {
s[i] = read();
}
for (int i = 1; i <= g; i++) {
int c = 0;
for (int j = 1; j <= n / g; j++) tp[++c] = s[(i + d * j - 1) % n + 1]/* , cout <<(i + d * j - 1) % n + 1 << " " */;
// cout << endl;
for (int j = 1; j <= n / g; j++) tp[++c] = s[(i + d * j - 1) % n + 1];
int tmp = 0, res = 0;
for (int i = 1; i <= c; i++) {
if (tp[i] == 1) tmp++;
else tmp = 0;
res = max(res, tmp);
}
ans = max(ans, res);
}
write(ans == n / g * 2 ? -1 : ans);
puts("");
}
return 0;
}
//tql
还有一种解法,其实核心思想是一样的,但是实现的形式更加简单,直接用队列存储了0的下标。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 5, mod = 998244353;
int a[N];
signed main()
{
int T;
cin >> T;
while (T--) {
int n, d;
cin >> n >> d;
queue<int> q;
for (int i = 0; i <= n - 1; i++) {
scanf("%lld", &a[i]);
if (!a[i]) {
q.push(i);
}
}
int ans = 0;
while(!q.empty()) {
int len = q.size();
for (int i = 1; i <= len; i++) {
int x = q.front();
q.pop();
int nxt = (x + n - d) % n;
if (a[nxt]) {
a[nxt] = 0;
q.push(nxt);
}
}
ans++;
}
bool ck = 0;
for (int i = 0; i <= n - 1; i++) {
if (a[i] == 1) {
ck = 1;
break;
}
}
if (ck) cout << -1 << endl;
else cout << ans -1 << endl;
}
return 0;
}
(E) Gardener and Tree(拓扑排序删点)
学会了用拓扑排序来达到删除叶子节点的目的。
题与普通的拓扑排序不同,普通的是单向边,这里是双向边。不过也大同小异,删除度为1的节点就好了。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6, INF = 0x3f3f3f3f;
vector<int> v[N];
int in[N];
struct node{
int val, e;
};
queue <node> q;
signed main()
{
/* freopen("data5.in","r",stdin);
freopen("data5.out","w",stdout); */
int T;
cin >> T;
while(T--) {
int n, k;
cin >> n >> k;
if (n == 1) {
cout << 0 << endl;
continue;
}
int a, b;
for (int i = 1; i <= n - 1; i++) {
cin >> a >> b;
in[a]++;
in[b]++;
v[a].push_back(b);
v[b].push_back(a);
}
for(int i=1; i<=n; i++) {
if(in[i] == 1) q.push(node{0, i});
}
vector<int> ans;
while(!q.empty()) {
int p = q.front().e, now = q.front().val;
if (now == k) break;
q.pop(); // 选一个入度为1的点,出队列
ans.push_back(p);
int len = v[p].size();
for(int i = 0; i < len; i++) {
int y = v[p][i];
in[y]--;
if(in[y]==1) q.push(node{now + 1, y});
}
}
cout << n - ans.size() << endl;
for (int i = 1; i <= n; i++) {
v[i].clear();
in[i] = 0;
}
while(!q.empty()) q.pop();
ans.clear();
}
return 0;
}
A. AquaMoon and Strange Sort(奇偶分类讨论)
冒泡排序,每个数字只能移动偶数次,判断最后是否可以有序。
只能移动偶数次,那奇数位置移动完还在奇数位置,偶数位置也是。
那么就用桶记录每个数字出现的奇数有几次,偶数位置有几次,再给数组排序后,一一比对就好了。
注意清空数组时候的范围。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6, INF = 0x3f3f3f3f;
int c[N], a[N];
int odd[N], even[N];
signed main()
{
int T;
cin >> T;
while(T--) {
int n;
cin >> n;
int ma = -1;
for (int i = 1; i <= n; i++) {
cin >> a[i];
c[i] = a[i];
ma = max(ma, a[i]);
}
sort(c + 1, c + 1 + n);
for (int i = 1; i <= n; i++) {
if (i & 1) odd[c[i]]++;
else even[c[i]]++;
}
bool ck = 0;
for (int i = 1; i <= n; i++) {
if ((i & 1) && odd[a[i]]) odd[a[i]]--;
else if (!(i & 1) && even[a[i]]) even[a[i]]--;
else {
ck = 1;
break;
}
}
if (ck) cout << "NO" << endl;
else cout << "YES" << endl;
for (int i = 1; i <= ma; i++) odd[i] = even[i] = 0;
}
return 0;
}
(D) Co-growing Sequence
代码不放了,一直wa3的原因是数组开小了。。。
但是有一个写法稍异的写法,小小的数组却Ac了就真的很奇怪。。
(B) Groups
给出了n个人的空闲时间,判断是否可以分配出两天,学生分成两拨,每波n/2个,安排一波去其中一天干活。
用二维的set来保存每天都有空的学生,只要有那么两天,集合内的学生数>=n/2,两天的并集==n,那么一定可以YES。接着就是暴力了。。能想出来感觉也挺不容易的,。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e3 + 5, INF = 0x3f3f3f3f;
int a[N][10];
set<int> s[6];
signed main()
{
int tt;
cin >> tt;
while(tt--) {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= 5; j++) {
scanf("%lld", &a[i][j]);
if (a[i][j]) s[j].insert(i);
}
}
bool ck = 0;
for (int i = 1; i <= 5; i++) {
for (int j = i + 1; j <= 5; j++) {
if (s[i].size() >= n / 2 && s[j].size() >= n / 2) {
set<int> t;
for (auto k : s[i]) t.insert(k);
for (auto k : s[j]) t.insert(k);
if (t.size() == n) {
ck = 1;
break;
}
}
}
if (ck) break;
}
if (ck) cout << "YES" << endl;
else cout << "NO" << endl;
for (int i = 1; i <= 5; i++) s[i].clear();
}
return 0;
}
这题看大佬用bitset的奇淫技巧。
#include<bits/stdc++.h>
using namespace std;
int t,n;
bitset<1005> a[6];
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i=1;i<=5;++i)a[i]=0;
for(int i=1,x;i<=n;++i)
for(int j=1;j<=5;++j)
{scanf("%d",&x);a[j][i]=x;}
bool flag=0;
for(int i=1;i<=5;++i)for(int j=i+1;j<=5;++j)
if(a[i].count()>=n/2&&a[j].count()>=n/2&&(a[i]|a[j]).count()>=n)flag=1;
printf(flag?"YES\n":"NO\n");
}
return 0;
}
F. Interesting Function(数位问题)
10对答案贡献多了1,100是2,1000是3…
所以n每次除以10就能统计了。相当于n在统计有几个10,几个100…
例如199,有19个10,1个100。这样子计算,其中的100对10有1的贡献,对100也有1的贡献,就相当于100有2的贡献了。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 5, INF = 0x3f3f3f3f;
int f(int n)
{
int ret = 0;
while(n) {
ret += n;
n /= 10;
}
return ret;
}
signed main()
{
int tt;
cin >> tt;
while(tt--) {
int l, r;
cin >> l >> r;
cout << f(r) - f(l) << endl;
}
return 0;
}
(D) Training Session(集合+思维)
给定n个x与y。选3个出来,使得其中的x全不相同或者是y全不相同,求有多少种选法。
用总的选法减去不合理的选法。
如果当前选了x与y,要选出另外两个来让这个选择不合理,那么其中一个一定含有x,另一个含有y;这个x的选法就是cnt[x]-1,y的选法就是cnt[y]-1。
因为不可能有x与y同时相等,所以这样子是不重不漏的。
x y
x
y 这样的组合一定不合理。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 5, INF = 0x3f3f3f3f;
struct node{
int x, y;
} a[N];
int t1[N], t2[N];
signed main()
{
int tt;
cin >> tt;
while(tt--) {
int n;
cin >> n;
int sum = n * (n - 1) * (n - 2) / 6;
for (int i = 1; i <= n; i++) {
scanf("%lld%lld", &a[i].x, &a[i].y);
t1[a[i].x]++;
t2[a[i].y]++;
}
for (int i = 1; i <= n; i++) {
sum -= (t1[a[i].x] - 1) * (t2[a[i].y] - 1);
}
cout << sum << endl;
for (int i = 1; i <= n; i++) t1[i] = t2[i] = 0;
}
return 0;
}
(A) Computer Game
只能往右,斜着右上,或者斜着右下走。能否走到终点。
上课写的,dfs写了好久。。我的dfs功底太差了。其实根本不用写搜索,只要有那么一堵墙(这一列全为1),那么就无法走通。
见识一下bitset的奇淫技巧。
#include <bits/stdc++.h>
using namespace std;
int main() {
string s, t;
int k, n;
cin >> k;
while (k--)
cin >> n >> s >> t,
cout << ((bitset<100>(s) & bitset<100>(t)).count() ? "NO\n" : "YES\n");
}
(B) AquaMoon and Stolen String(桶优化暴力)
有n(n为奇数)个字符串,选n-1个出来,配成(n-1)/2对,每一对中,两个字符串之前的任意字符可以随意互换,(同下标才能换)。多出来的那个字符串被删去了,现在请你找到他。
思路:开一个字符桶 f [ 26 ] [ N ],反正字符换来换去还是在那一列。
这题是交互题,好像都要加一个fflush(stdout);
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 5, INF = 0x3f3f3f3f;
int tong[30][N];
char s[N];
signed main()
{
/* freopen("data5.in","r",stdin);
freopen("data5.out","w",stdout); */
int tt;
cin >> tt;
while(tt--) {
int n, m;
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%s", s + 1);
for (int j = 1; j <= m; j++) tong[s[j] - 'a'][j]++;
}
for (int i = 1; i <= n - 1; i++) {
scanf("%s", s + 1);
for (int j = 1; j <= m; j++) tong[s[j] - 'a'][j]--;
}
string ans;
for (int i = 1; i <= m; i++) {
for (int j = 0; j <= 25; j++) {
if (tong[j][i]) {
ans = ans + char(j + 'a');
break;
}
}
}
cout << ans << endl;
fflush(stdout);
for (int i = 0; i <= 25; i++)
for (int j = 1; j <= m; j++) tong[i][j] = 0;
}
fflush(stdout);
return 0;
}
© Strange Function(最小公倍数)
f(x)是最小的不能被x整除的数。给定n,求前n项f的和。
因为n非常大,一开始以为像矩阵快速幂求斐波那契一样,但是不会。。然后想到阶乘,如果一个数x能整除1 * 2 * 3,那么f(x)就是4,但是这样子其实是不正确的。就比如f(12)=5。
看到题解说最小公倍数,然后灵机一动就会了~
我的想法是,如果n能够整除lcm(1 ~ k),那么n以内就有n/lcm(1 ~ k)个数是lcm(1 ~ k)的倍数,令t = lcm(1 ~ k + 1),n/lcm(1 ~ k)- t 个数的f值就是k + 1。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 5, mod = 1e9 + 7;
int f[N];
int lcm(int a, int b)
{
return a / __gcd(a, b) * b;
}
signed main()
{
/* int l = 1;
for (int i = 1; i <= 100; i++) {
l = lcm(l, i);
cout << l << " ";
if (l > 10000000000000000) {
cout << i << " ";
break;
}
} */
f[0] = 1;
for (int i = 1; i <= 41; i++) f[i] = lcm(f[i - 1], i);
int tt;
cin >> tt;
while(tt--) {
int n;
cin >> n;
int ans = 0;
int last = 0;
for (int i = 41; i >= 1; i--) {
int c = n / f[i];
if (c == 0) continue;
ans = (ans + (c - last) * (i + 1)) % mod;
last = c;
}
cout << ans << endl;
}
return 0;
}
把last写成next也可以,能少做几次循环,不过41算是个常数了,改不改无所谓了。
(B) Pleasant Pairs(枚举+调和级数)
给定n个不同的数,求a[i]*a[j] == i + j有多少对。
思想:由这个式子可以得知,i + j一定是a[i]的倍数,所以可以枚举 i + j 是a[i]的多少倍,而 i + j最大是2 * n,所以枚举的过程就像是调和级数,调和级数的复杂度是logn。所以总体复杂度是nlogn。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 5, mod = 1e9 + 7;
int a[N];
signed main()
{
int tt;
cin >> tt;
while(tt--) {
int n;
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
int ans = 0;
for (int i = 1; i <= n; i++) {
for (int j = a[i] - i; i + j <= 2 * n; j += a[i]) {
if (j <= 0 || j > n || i >= j) continue;
if (a[i] * a[j] == i + j) {
ans++;
}
}
}
cout << ans << endl;
}
return 0;
}
牛客小白月赛39:
D-绝望
很普通的一个势能线段树,wa了半个下午的原因,是修改线段树数组的时候,线段树更新了,而原序列a[i]没有及时更新,以前做的题目一直在树上改就好了,这回的题原序列也要改一次,因为修改线段树的时候的一些判断,是依赖于原序列的值的。
G-冷静
q 次询问。每次询问给定 n 和 K,问 1 ~ n 中有多少数可以表示为大于等于 K 的质数的乘积(一个数可以乘多次)。
原问题几乎相当于求1 ~ n的区间内,有多少个数是>=k的。 但是有一点不同的是,这些数是1 ~ n最小素因子。 直接上普通平衡树Treap。 有一点要注意的是,k在平衡树内不存在的情况,比赛的时候就因为这个我一直wawawa…呜呜呜了
H-终别
每次操作可以使3个连续的数-1。还有一次特殊操作,直接让两个相邻的数归零,但是这个操作只能用一次。求让所有数归零的最小操作次数。
一开始我以为是道dp,还整了个递推式出来,看起来还很有道理的一个递推式,实际上漏解了。
我整了个f[i] = min({f[i - 1] + a[i], f[i - 2] + max(a[i], a[i - 1]), f[i - 3] + max({a[i], a[i - 1], a[i - 2]})});意思是当前的a[i]单干,a[i]与a[i-1]一起干,a[i]与a[i-1]与a[i-2】一起干,这样是不对的,因为a[i]单干不如和a[i+1]与a[i+2]一起干。
正解是贪心,从左到右,如果当前的a[i]不为0,那就干到他变成0为止。
这样子,如果不考虑那个特殊操作,到这里已经做完这道题了。
考虑这个特殊操作,需要枚举哪两个相邻的数直接归零。打游戏的时候突发灵感,如果中间两个相邻的数直接归零,左边的操作次数可以贪心得到,右边的呢?也可以同样的贪心得到,所以两个数组l,r。最后ans不断取min。
ans一开始不能太小,这题数据范围太大了,ans开始要开1e18。。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 5, mod = 1e9 + 7;
int a[N], b[N];
int l[N], r[N];
signed main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
b[i] = a[i];
}
for (int i = 1; i + 2 <= n; i++){
l[i] = l[i - 1] + a[i];
a[i + 1] = max(0ll, a[i + 1] - a[i]);
a[i + 2] = max(0ll, a[i + 2] - a[i]);
}
for (int i = 1; i <= n; i++) a[i]=b[i];
for (int i = n; i -2 >= 1; i--){
r[i] = r[i + 1] + a[i];
a[i - 1] = max(0ll, a[i - 1] - a[i]);
a[i - 2] = max(0ll, a[i - 2] - a[i]);
}
int ans = 1e18;
for (int i = 1; i + 1 <= n; i++){
ans = min(l[i - 1] + r[i + 2], ans);
}
cout << ans;
return 0;
}
C. Great Graphs(前缀和+推式子)
题目相当于是给定一个数组,求ai-aj的和,其中j<i。
因为这题的题目背景是图论,思路就一直没有往数学推式子上靠,其实推个式子,答案很简单就出来了。ans=(i-1)*ai - sum(i-1)。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 5, mod = 1e9 + 7;
int a[N], sum[N];
signed main()
{
int tt;
cin >> tt;
while(tt--) {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
int ans = 0;
sort(a + 1, a + 1 + n);
for (int i = 1; i <= n; i++) {
sum[i] = sum[i - 1] + a[i];
ans += (i - 1) * a[i] - sum[i - 1] - (a[i] - a[i - 1]);
}
cout << -ans << endl;
}
return 0;
}
D. PriceFixed(双指针+贪心)
小红要购物,规则是,给定ai表示商品i要多少个,bi表示总购够买商品超过bi后,就可以半价买第i件商品。每件商品如果愿意,可以买无数件。
思路:如果当前可以用优惠买i,那就直接用优惠买;如果不能买,那就原价买bi最大的商品,直到可以用优惠买i。这样是最贪心的,每一分钱都花在了需要的商品上。一开始被可以买无数件误导了,推了三种情况的式子,感觉还挺对,其实是不够贪心的。
两种写法都可以,写法1更简单。两种的内涵是一样的。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 5, mod = 998244353;
struct node{
int a, b;
bool operator <(const node &k) const {
return b < k.b;
}
} s[N];
signed main()
{
int n;
cin >> n;
int sum = 0, ans = 0;
for (int i = 1; i <= n; i++){
scanf("%lld%lld", &s[i].a, &s[i].b);
sum += s[i].a;
}
sort(s + 1, s + 1 + n);
int l = 1, r = n, k = 0;
while(k<sum){
if(k >= s[l].b) {
ans += s[l].a;
k += s[l].a;
l++;
}
else {
/* int mi = min(s[r].a, s[l].b - k);
ans += mi * 2;
k += mi;
s[r].a -= mi;
if (s[r].a == 0) r--; */
int cou = s[l].b - k;
while(cou && l <= r && k < sum){
int mi = min(cou, s[r].a);
ans += mi * 2;
k += mi;
cou -= mi;
s[r].a -= mi;
if(s[r].a==0) r--;
}
}
}
cout << ans;
return 0;
}
C. Diluc and Kaeya(gcd+思维)
给定一个只由D和K组成的字符串,问如何分割这个字符串,使得每一个子串中D的数量:K的数量都相等。
既然所有的子串D:K都相等,设其比值为x,那么母串的比值一定也是x。然后这题就简单了。
用map存结构体计数就好啦。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2000 + 5, mod = 998244353;
struct node{
int x, y;
bool operator < (const node &k) const {
if(x != k.x) return x < k.x;
else return y < k.y;
}
};
map<node, int> mp;
node f(int d,int k)
{
if (d == 0) return node{0,1};
else if (k == 0)return node{1,0};
int gcd = __gcd(d, k);
d /= gcd, k /= gcd;
return node{d, k};
}
signed main()
{
int tt;
cin >> tt;
while(tt--){
int n;
cin >> n;
string s;
cin >> s;
s = " " + s;
int d = 0, k = 0;
for (int i = 1; i <= n; i++){
if (s[i]=='D'){
d++;
cout << ++mp[f(d, k)] << " ";
}
else {
k++;
cout << ++mp[f(d, k)] << " ";
}
}
cout << endl;
mp.clear();
}
return 0;
}
D. Secret Santa(拓扑排序+分类讨论)
n个人想送给喜欢的人礼物,给定每个人喜欢的人,但是有的人可以被多个人喜欢,而每个人只能收到一份礼物,也就是说,这多个人中只能有一个人能送礼物给被喜欢的人。所以请你设计一种方案,能让K个人都能送礼物给自己喜欢的人,使K最大化。
思路:如果u喜欢v,那么u与v之间就连接一条有向边,u指向v。类似于拓扑排序,从入度为0的节点t开始做起,设t连接的节点p还没收到礼物,那就分配p给t。因为入度为0,所以没人会主动送礼物给t,因此缓存一下t,最后给再来分配t给别人。
需要注意的是:
- 既然这是图,是会出现环的,所以最后入度为1的节点也要进行上面的操作。不过这里他们就不用进缓存了,因为是环,入度一定>=1,肯定都能被分配给别人的。
- 最后是缓存的t ,他们没有收到礼物,但是可能已经送出礼物了,所以t与还没送出礼物的人要互相分配。注意自己不能分配给自己。
代码写的太丑,不放了。只要能模拟这个思路就好啦。
C. Grandma Capa Knits a Scarf(双指针+枚举)
给定一个字符串,求最少删除多少个字符(必须是同一个字符),能够让字符串变成回文串。
对于一个字符,如果把他删除几个就能得到回文串,那么全部删除他一定也能得到回文串。枚举删除哪一个字符,双指针l,r判断。如果s[l] != s[r],说明l与r中必有一个要被删除。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 5, INF = 998244353;
char s[N], t[N];
bool ck(int n)
{
for (int i = 1; i <= n / 2; i++) {
if (t[i] != t[n - i + 1]) return 0;
}
return 1;
}
signed main()
{
int tt;
cin >> tt;
while(tt--){
int n;
cin >> n;
scanf("%s", s + 1);
int ans = INF;
for (char x = 'a'; x <= 'z'; x++) {
int cnt = 0;
int ret = 0;
for (int i = 1; i <= n; i++) {
if (s[i] != x) t[++cnt] = s[i];
}
if (!ck(cnt)) continue;
int l = 1, r = n;
while(l < r) {
if (s[l] == s[r]){
l++;
r--;
}
else if (s[l] == x) {
ret++;
l++;
}
else if (s[r] == x) {
ret++;
r--;
}
}
ans = min(ans, ret);
// cout << "ret" << ret << endl;
}
if (ans != INF) cout << ans << endl;
else cout << -1 << endl;
}
return 0;
}
B. Omkar and Heavenly Tree(思维)
要求构造一棵n个节点树。给定m个限制,(1<=m< n),要求a到c的路径不可以经过b。
边的情况实在是太多了,考虑点与点之间的关系吧。最多给N-1个点不能作为中间点,那么一定还剩一个点可以作为剩余所有点之间的中间点。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 5, INF = 998244353;
signed main()
{
int tt;
cin >> tt;
while(tt--){
int n, m;
cin >> n >> m;
set<int> s;
for (int i = 1; i <= n; i++) {
s.insert(i);
}
for (int i = 1; i <= m; i++) {
int x, y, z;
cin >> x >> y >> z;
s.erase(y);
}
int x = *(s.begin());
for (int i = 1; i <= n; i++) {
if (x != i) cout<<i << " "<<x <<endl;
}
}
return 0;
}
C. Omkar and Determination(数据结构+思维)
给定一个由.与X组成的地图。X不可以走通,.与.可以走通,但是只能往左或者往上走。这样子每个点就可以标记为E(能走通)或者N(不能走通),给定m个询问,问l列到r列的地图转化成EN图后,是否能根据EN图还原回地图。
要还原,E一定是.,但是N不一定是X。
EN .X .X
NN --> XX 或 X.
所以只要出现左边与上面都是X的情况,那么EN图就是不可还原的了。
只要记录每一列是否有这样的衰点,用前缀和判断就可以了。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 5, INF = 998244353;
string s[N];
int sum[N];
signed main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
s[i].resize(m + 1);
scanf("%s", &s[i][1]);
}
s[0].resize(m + 1);
for (int j = 1; j <= m; j++) {
for (int i = 1; i <= n; i++) {
if (s[i][j - 1] == 'X' && s[i - 1][j] == 'X') {
sum[j] = 1;
break;
}
}
sum[j] = sum[j - 1] + sum[j];
}
int q;
cin >> q;
while(q--) {
int l, r;
scanf("%lld%lld", &l, &r);
if (sum[r] - sum[l]) puts("NO");
else puts("YES");
}
for (int j = 1; j <= m; j++) sum[j] = 0;
return 0;
}
D. Another Problem About Dividing Numbers(数论)
给定a和b,每次对a操作或对b操作:对a,选择一个数能被a整除,a/=c;对b,选择一个数c能b整除,b/=c。问恰好k次操作能否让a=b。
将a,b分解质因数,每次的选c操作可看做是在质因数中选几个除去,最后要留下一样的质因数。
那么如果a,b互质,就都有一部分质因数不同,那至少操作2次,如果不互质且a,b不等,那么至少操作一次。
#include <bits/stdc++.h>
using namespace std;
// #define int long long
const int N = 1e6 + 5, INF = 998244353;
int f(int n)
{
int cnt = 0;
for (int i = 2; i * i <= n; i++) {
while(n % i == 0) {
n /= i;
cnt++;
}
}
if (n > 1) cnt++;
return cnt;
}
signed main()
{
int tt;
cin >> tt;
while(tt--){
int a, b, k;
scanf("%d%d%d", &a, &b, &k);
int fa = f(a), fb = f(b);
int mi = 2, ma = fa + fb;
if ((a % b == 0 || b % a == 0) && a != b) mi = 1;
if (mi <= k && k <= ma) printf("YES\n");
else printf("NO\n");
}
return 0;
}
A. Di-visible Confusion(最小公倍数)
一个数在数组中的下标是i,如果这个数不能被i+1整除,那么这个数就可以删除。
思路:如果2~i + 1中有存在一个数不是ai的因子,那么ai就可以被删除。
我们可以用归纳法来证明。 假设可以删除包含 n-1 个元素的前缀。 由于 an 可以在从 1 到 n 的某个位置被擦除(假设是 k),那么在擦除 n-1 个元素的前缀时,当前缀包含 k-1 个元素时,则 an 在第 k 个位置,所以我们可以在该位置擦除它并相应地擦除序列的其余部分。
所以结合lcm判断一下就好了。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 5, mod = 998244353;
int a[N];
int lc[N];
int lcm(int x, int y)
{
return x / __gcd(x, y) * y;
}
signed main()
{
int cnt = 0;
int l = 1;
for (int i = 2; i <= 100; i++) {
l = lcm(i, l);
if (l > 1e9) {
cnt = i - 1;
break;
}
lc[i] = l;
}
int tt;
cin >> tt;
while(tt--) {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
bool ck = 0;
for (int i = 1; i <= n; i++) {
for (int j = 2; j <= cnt; j++) {
if (a[i] % lc[j] == 0 && i <= j - 1) {
ck = 1;
break;
}
}
if (ck) break;
}
if (ck) puts("NO");
else puts("YES");
}
return 0;
}
D. Moderate Modular Mode(数论+推式子)
求一个整数n使得n%x=y%n。
分类讨论一下,如果x>y,那么直接输出x+y就好了。
这题难点是y>x的情况,首先很容易证明出这种情况下一定是x<=n<=y。接着有:
n = tx + d
y = sn + d
y + tx
消去d有n = -------,题目有保证x和y都是偶数,因此s直接取1就可以。
s + 1
结合上面的不等式n<=y,能得到t<=y/x,所以可以取t = y/x。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 5, mod = 998244353;
int a[N];
signed main()
{
int tt;
cin >> tt;
while(tt--) {
int x, y;
scanf("%lld%lld", &x, &y);
if (y % x == 0) printf("%lld\n", x);
else {
if (x > y) printf("%lld\n", x + y);
else printf("%lld\n", (y/x*x + y) / 2);
}
}
return 0;
}
盾与战锤(子列和+调和级数)
你有一个长度为 n 的攻击序列,每个攻击都会造成一定伤害,你可以选出它的一个 子序列 进行攻击,每一秒按照子序列中的顺序进行一次攻击。选出的子序列用完了就不能继续攻击了。敌人的盾每k秒会恢复。
先想想怎么选子序列,先思考怎么得到长度为k的最大子列和。这个只要排序数组后求前缀和就好了。(比赛的时候却想了好久)
接下来的就简单了,在盾恢复的前一秒一定是最贪心的,而n以内有很多个恢复时间,此时累加的伤害-盾的总抗伤,取max就好了。
整个复杂度就一调和级数。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 5, INF = 998244353;
int a[N], sum[N], ans[N];
signed main()
{
int n, s;
cin >> n >> s;
for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
sort(a + 1, a + 1 + n, greater<int>());
for (int i = 1; i <= n; i++) {
sum[i] = sum[i - 1] + a[i];
}
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j += i) {
ans[i] = max(ans[i], sum[j] - j / i * s);
}
if (n % i != 0) ans[i] = max(ans[i], sum[n] - (n / i + 1) * s);
}
for (int i = 1; i <= n; i++) {
printf("%lld\n", ans[i]);
}
return 0;
}
妄想集合(势能线段树)
有n个可重集合,每个集合一开始只有一个数。
操作是往l ~ r加一个数x。然后问 l ~ r之间的数是否可以选三个出来构成三角形。
因为所给的数都不超过1e9,所以说三角形的最大边也就是1e9,而斐波那契数列刚好完美的构不成三角形,这个数列超过46项就超1e9了。
所以一个集合的数超过46,那么这个一个集合就能选出数来构成三角形(根据鸽巢原理)。
一开始只要暴力的往区间加数就好了,然后判断也是不停的暴力。不过询问的区间长度一开始要是超过46,也不用暴力了,直接yes。
#include <bits/stdc++.h>
using namespace std;
// #define int long long
const int N = 1e5 + 5, mod = 998244353;
struct node{
int l, r;
int val;
int add;
#define l(x) tree[x].l
#define r(x) tree[x].r
} tree[N * 4];
vector<int> v[N];
char s[7];
void build(int p, int l, int r)
{
l(p) = l, r(p) = r;
if (l >= r) {
tree[p].val = 1;
return;
}
int mid = (l + r) >> 1;
int chl = p * 2, chr = p * 2 + 1;
build(chl, l, mid);
build(chr, mid + 1, r);
tree[p].val = tree[chl].val + tree[chr].val;
}
void up(int p, int l, int r, int x)
{
if (l <= l(p) && r(p) <= r && tree[p].val / (r(p) - l(p) + 1) >= 47) {
return;
}
if (l(p) >= r(p)) {
v[l(p)].push_back(x);
tree[p].val++;
return;
}
int mid = (l(p) + r(p)) >> 1;
int chl = p * 2, chr = p * 2 + 1;
if (l <= mid) up(chl, l, r, x);
if (r > mid) up(chr, l, r, x);
tree[p].val = tree[chl].val + tree[chr].val;
}
int que(int p, int l, int r)
{
if (l <= l(p) && r(p) <= r) return tree[p].val;
int mid = (l(p) + r(p)) >> 1;
int chl = p * 2, chr = p * 2 + 1;
int ret = 0;
if (l <= mid) ret += que(chl, l, r);
if (r > mid) ret += que(chr, l, r);
return ret;
}
bool ck(int l, int r)
{
vector<int> t;
for (int i = l; i <= r; i++) {
for (auto p : v[i]) t.push_back(p);
}
sort(t.begin(), t.end());
int len = t.size();
for (int i = 0; i + 2 <= len - 1; i++) {
if (t[i] + t[i + 1] > t[i + 2]) return true;
}
return false;
}
signed main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
int x;
scanf("%d", &x);
v[i].push_back(x);
}
build(1, 1, n);
while(m--){
int l, r;
scanf("%s%d%d", s + 1, &l, &r);
if (s[1] == 'A'){
int len = que(1, l, r);
if (len >= 47) puts("YES");
else {
if (ck(l, r) == true) puts("YES");
else puts("NO");
}
}
else {
int x;
scanf("%d", &x);
up(1, l, r, x);
}
}
return 0;
}