周三
M. Rock-Paper-Scissors Pyramid(思维)
斜着看,每次复制一遍加一个
用一个栈维护,如果当前可以打败栈顶的,就出栈,如果相同,入不入栈都可以,如果打不过也要入栈,因为它可以阻挡后面的。
最后栈底就是留下来的。
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e6 + 10;
char s[N];
int top;
int main()
{
map<char, int> mp;
mp['S'] = 0; mp['P'] = 1; mp['R'] = 2;
int T; scanf("%d", &T);
while(T--)
{
string str; cin >> str;
int len = str.size();
top = 0;
rep(i, 0, len)
{
while(top && (mp[str[i]] + 1) % 3 == mp[s[top]]) top--;
s[++top] = str[i];
}
printf("%c\n", s[1]);
}
return 0;
}
P3396 哈希冲突(根号分治)
用根号为分界线,一部分暴力,一部分预处理
对于这道题,模数小于等于根号n时,直接预处理
模数大于等于根号n时,直接暴力
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1.5e5 + 10;
const int M = 400;
int s[M][M], a[N], n, m;
int main()
{
scanf("%d%d", &n, &m);
_for(i, 1, n) scanf("%d", &a[i]);
int block = sqrt(n);
_for(i, 1, n)
_for(j, 1, block)
s[j][i % j] += a[i];
while(m--)
{
char op[10]; int x, y;
scanf("%s%d%d", op, &x, &y);
if(op[0] == 'A')
{
if(x <= block) printf("%d\n", s[x][y]);
else
{
int res = 0;
for(int i = y; i <= n; i += x) res += a[i];
printf("%d\n", res);
}
}
else
{
_for(j, 1, block) s[j][x % j] += y - a[x];
a[x] = y;
}
}
return 0;
}
Array Queries(根号分治)
1e5左右的数据范围要很敏感,很可能是n根号n的算法
这题而言,每次p最少会增加k
那么当k大于根号n时,最多跳根号n次
当k小于根号n时,可以用dp直接求出,倒着求一遍
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e5 + 10;
const int M = 350;
int dp[N][M], a[N], n, m;
int main()
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &a[i]);
int block = sqrt(n);
for(int i = n; i >= 1; i--)
_for(k, 1, block)
{
int j = i + a[i] + k;
if(j > n) dp[i][k] = 1;
else dp[i][k] = dp[j][k] + 1;
}
scanf("%d", &m);
while(m--)
{
int p, k;
scanf("%d%d", &p, &k);
if(k <= block) printf("%d\n", dp[p][k]);
else
{
int res = 0;
while(p <= n)
{
p = p + a[p] + k;
res++;
}
printf("%d\n", res);
}
}
return 0;
}
周四
A. Ban or Pick, What's the Trick(记忆化搜索)
注意要逆推,当前的选择肯定根据选择之后的结果是什么
逆推的话,记忆化搜索比写循环要方便
注意空间要开两倍
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int a[N], b[N], n, k;
ll dp[N << 1][15][15]; //交之前一定要检查一下空间开够了没有
ll dfs(int round, int numa, int numb)
{
if(round == 2 * n + 1) return 0;
if(dp[round][numa][numb] != -1) return dp[round][numa][numb];
int ta = round / 2 - numb + numa;
int tb = (round + 1) / 2 - numa + numb;
if(round % 2 == 1)
{
ll res = dfs(round + 1, numa, numb);
if(ta + 1 <= n && numa + 1 <= k) res = max(res, dfs(round + 1, numa + 1, numb) + a[ta + 1]);
return dp[round][numa][numb] = res;
}
else
{
ll res = dfs(round + 1, numa, numb);
if(tb + 1 <= n && numb + 1 <= k) res = min(res, dfs(round + 1, numa, numb + 1) - b[tb + 1]);
return dp[round][numa][numb] = res;
}
}
int main()
{
scanf("%d%d", &n, &k);
_for(i, 1, n) scanf("%d", &a[i]);
_for(i, 1, n) scanf("%d", &b[i]);
sort(a + 1, a + n + 1, greater<int>());
sort(b + 1, b + n + 1, greater<int>());
memset(dp, -1, sizeof dp);
printf("%lld\n", dfs(1, 0, 0));
return 0;
}
周五
G. Matryoshka Doll(dp+组合数学)
很容易想到dp[i][j]表示前i个物品分成j组的方案数
当前物品要不独立成一组,要不和之前的一组
设p为最大的下标使得ai - ap >= r
那么显然ai只能和1到p分为一组
那么关键是1到p有多少组可以让ai加进去呢
注意到p+1到i-1这一部分,两两是不可能为一组的,且ai不能和它们一组
那么只剩下j - (i - p - 1)这么多组了,ai可以加入其中一个
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 5000 + 10;
const int mod = 998244353;
int L[N], a[N], n, k, R;
ll dp[N][N];
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%d%d", &n, &k, &R);
_for(i, 1, n) scanf("%d", &a[i]);
int l = n;
for(int r = n; r >= 1; r--)
{
while(l - 1 >= 0 && a[r] - a[l] < R) l--;
L[r] = l;
}
dp[0][0] = 1;
_for(i, 1, n)
_for(j, 1, k)
{
dp[i][j] = dp[i - 1][j - 1];
int t = i - L[i] - 1;
if(j - t > 0) dp[i][j] = (dp[i][j] + dp[i - 1][j] * (j - t) % mod) % mod;
}
printf("%lld\n", dp[n][k]);
}
return 0;
}
J. Sum Plus Product(找规律)
那a,b,c三个数试一下,是abc+ac+bc+ab+a+b+c
每一项就是abc这三个取字母或者取1
可以写成(a + 1)(b + 1)(c + 1) - 1
那么答案就是加1乘起来然后减1
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int mod = 998244353;
int main()
{
int T; scanf("%d", &T);
while(T--)
{
int n; scanf("%d", &n);
ll ans = 1;
_for(i, 1, n)
{
int x; scanf("%d", &x);
ans = ans * (x + 1) % mod;
}
ans--;
if(ans < 0) ans += mod;
printf("%lld\n", ans);
}
return 0;
}
Shortest Path in GCD Graph(容斥)
找和u和v互质的数有多少个,那么将两个数质因数分解,然后容斥一下即可
主要要特判gcd=2的情况
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 1e7 + 10;
int vis[N], a[N], n, q, ans;
vector<int> p, ve;
void init()
{
vis[0] = vis[1] = 1;
_for(i, 2, 1e7)
{
if(!vis[i]) a[i] = i, p.push_back(i); //取最小质因子的时候不要忘了质数为质数本身
for(int x: p)
{
if(x * i > 1e7) break;
vis[x * i] = 1;
a[x * i] = x;
if(i % x == 0) break;
}
}
}
vector<int> deal(int x)
{
vector<int> res;
while(x > 1)
{
int t = a[x];
res.push_back(t);
while(x % t == 0) x /= t;
}
return res;
}
void dfs(int i, int cnt, ll cur)
{
if(i == ve.size())
{
if(cur > 1)
{
if(cnt % 2 == 1) ans += n / cur;
else ans -= n / cur;
}
return;
}
dfs(i + 1, cnt + 1, cur * ve[i]);
dfs(i + 1, cnt, cur);
}
int gcd(int a, int b) { return !b ? a: gcd(b, a % b); }
int main()
{
init();
scanf("%d%d", &n, &q);
while(q--)
{
int u, v;
scanf("%d%d", &u, &v);
vector<int> pu = deal(u), pv = deal(v);
ve = pu;
for(int x: pv) ve.push_back(x);
sort(ve.begin(), ve.end());
ve.erase(unique(ve.begin(), ve.end()), ve.end());
if(ve.size() == pu.size() + pv.size())
{
puts("1 1");
continue;
}
ans = 0;
dfs(1, 1, ve[0]);
dfs(1, 0, 1);
printf("%d %d\n", 2, n - ans + (gcd(u, v) == 2));
}
return 0;
}
Arithmetic Subsequence(奇偶分治)
这题妙啊,奇数偶数是突破口
奇数全部放左边,偶数全部放右边
那么三元组一定全在左边或权在右边
然后考虑递归处理,比如现在全是奇数,那么相减后最低位都为0,那么就没用了,所有数直接右移一位,这时又产生了奇数和偶数。
考虑二进制位,从最低位开始,为1的放左边为0放右边,然后再看倒数第二位……
那么就按照这个策略排序一遍,就一定不会有等差数列
特判一下一个数出现三次以上
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 5e3 + 10;
unordered_map<int, int> mp;
int a[N], n;
bool cmp(int x, int y)
{
_for(j, 0, 30)
{
int tx = (x >> j) & 1, ty = (y >> j) & 1;
if(tx != ty) return tx > ty;
}
return true; //注意可能有相等的情况 要return true
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
mp.clear();
scanf("%d", &n);
int flag = 1;
_for(i, 1, n)
{
scanf("%d", &a[i]);
if(++mp[a[i]] >= 3) flag = 0;
}
if(!flag)
{
puts("NO");
continue;
}
puts("YES");
sort(a + 1, a + n + 1, cmp);
_for(i, 1, n) printf("%d ", a[i]);
puts("");
}
return 0;
}