恢复疯狂训练模式
周一
C. Watto and Mechanism(字典树+dfs)
首先如果给了一堆串,然后问你一个串在不在里面,就直接用字典树就可以了
这道题不同的是要有一个位置不相同
那就修改一下,dfs就可以了
在dfs的过程中,每次都判断能否改变一个字母继续走就可以了
#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 = 6e5 + 10;
int t[N][3], End[N], n, m, cnt;
string str;
void add(string s)
{
int p = 0, len = s.size();
rep(i, 0, len)
{
if(!t[p][s[i] - 'a']) t[p][s[i] - 'a'] = ++cnt;
p = t[p][s[i] - 'a'];
}
End[p] = 1;
}
bool dfs(int pos, int p, int flag)
{
if(pos == str.size()) return flag && End[p];
if(t[p][str[pos] - 'a'] && dfs(pos + 1, t[p][str[pos] - 'a'], flag)) return true;
if(!flag)
{
rep(j, 0, 3)
if(str[pos] - 'a' != j && t[p][j] && dfs(pos + 1, t[p][j], 1))
return true;
}
return false;
}
int main()
{
scanf("%d%d", &n, &m);
_for(i, 1, n)
{
string s;
cin >> s;
add(s);
}
_for(i, 1, m)
{
cin >> str;
puts(dfs(0, 0, 0) ? "YES" : "NO");
}
return 0;
}
C. Watto and Mechanism(哈希)
这道题有个很关键的信息,就是字符从长度不超过6e5
对于看以前的字符是否存在,可以用哈希来解决
所以我们就可以枚举每一个字母,O(1)计算出新的哈希值,然后判断即可
这看起来很慢,但是题目告诉我们不超过6e5 是可以做的
这道题卡哈希卡到我怀疑人生…… 双哈希也WA
一直过不去……
最后看了一个AC的代码,在取模上和我不太一样,过了
不纠结了……
#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 unsigned long long ull;
typedef pair<ull, ull> pa;
const ull mod1 = 1e9 + 7, mod2 = 1e9 + 9;
const int N = 6e5 + 10, base = 131;
map<pa, bool> mp;
ull p1[N], p2[N];
int n, m;
pa get_hash(string s)
{
int len = s.size();
ull res1 = 0, res2 = 0;
rep(i, 0, len)
{
res1 = (res1 * base + s[i]) % mod1;
res2 = (res2 * base + s[i]) % mod2;
}
return make_pair(res1, res2);
}
int main()
{
p1[0] = 1; rep(i, 1, N) p1[i] = p1[i - 1] * base % mod1;
p2[0] = 1; rep(i, 1, N) p2[i] = p2[i - 1] * base % mod2;
scanf("%d%d", &n, &m);
_for(i, 1, n)
{
string s;
cin >> s;
mp[get_hash(s)] = 1;
}
_for(t, 1, m)
{
string s;
cin >> s;
pa val = get_hash(s);
int len = s.size(), find = 0;
rep(i, 0, len)
{
for(int k = 'a'; k <= 'c'; k++)
if(k != s[i])
{
ull cur1 = (val.first + (k - s[i]) * p1[len - i - 1] + 3 * mod1) % mod1;
ull cur2 = (val.second + (k - s[i]) * p2[len - i - 1] + 3 * mod2) % mod2;
if(mp[make_pair(cur1, cur2)])
{
find = 1;
break;
}
}
if(find) break;
}
puts(find ? "YES" : "NO");
}
return 0;
}
E. XOR on Segment(异或+线段树)
首先这种位运算常见的思路就是拆成一位一位来考虑
所以开20个线段树就可以了
有两个地方注意一下
一个是修改的时候懒标记也要下传
一个标记会叠加,要考虑进去
#include <bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#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 t[N << 4][20], lazy[N << 4][20], n, m;
void up(int k, int id)
{
t[k][id] = t[l(k)][id] + t[r(k)][id];
}
void add(int k, int l, int r, int x, int id)
{
if(l == r)
{
t[k][id]++;
return;
}
int m = l + r >> 1;
if(x <= m) add(l(k), l, m, x, id);
else add(r(k), m + 1, r, x, id);
up(k, id);
}
void update(int k, int l, int r, int id)
{
t[k][id] = (r - l + 1) - t[k][id];
lazy[k][id] ^= 1;
}
void down(int k, int l, int r, int id)
{
if(lazy[k][id])
{
int m = l + r >> 1;
update(l(k), l, m, id);
update(r(k), m + 1, r, id);
lazy[k][id] = 0;
}
}
void change(int k, int l, int r, int L, int R, int id)
{
if(L <= l && r <= R)
{
update(k, l, r, id);
return;
}
down(k, l, r, id);
int m = l + r >> 1;
if(L <= m) change(l(k), l, m, L, R, id);
if(R > m) change(r(k), m + 1, r, L, R, id);
up(k, id);
}
int ask(int k, int l, int r, int L, int R, int id)
{
if(L <= l && r <= R) return t[k][id];
down(k, l, r, id);
int m = l + r >> 1, res = 0;
if(L <= m) res += ask(l(k), l, m, L, R, id);
if(R > m) res += ask(r(k), m + 1, r, L, R, id);
return res;
}
int main()
{
scanf("%d", &n);
_for(i, 1, n)
{
int x; scanf("%d", &x);
_for(j, 0, 19)
if(x & (1 << j))
add(1, 1, n, i, j);
}
scanf("%d", &m);
while(m--)
{
int t, l, r, x;
scanf("%d%d%d", &t, &l, &r);
if(t == 1)
{
ll ans = 0;
_for(j, 0, 19)
ans += ask(1, 1, n, l, r, j) * (1LL << j);
printf("%lld\n", ans);
}
else
{
scanf("%d", &x);
_for(j, 0, 19)
if(x & (1 << j))
change(1, 1, n, l, r, j);
}
}
return 0;
}
A. Windblume Ode(质数)
这道题稍微想复杂了一点
涉及到质数合数,可以从奇偶来考虑
大于2的偶数一定是合数
由于n >= 3所以和一定大于2
如果和是质数,那么其一定是奇数
这时只要再减去一个奇数就是偶数,也就是合数了,这个数不可能为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;
const int N = 100 + 10;
int a[N], n;
bool check(int x)
{
if(x == 0 || x == 1) return false;
for(int i = 2; i * i <= x; i++)
if(x % i == 0)
return false;
return true;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
int sum = 0;
_for(i, 1, n) scanf("%d", &a[i]), sum += a[i];
if(!check(sum))
{
printf("%d\n", n);
_for(i, 1, n) printf("%d ", i);
puts("");
}
else
{
int t;
_for(i, 1, n)
if(!check(sum - a[i]))
{
t = i;
break;
}
printf("%d\n", n - 1);
_for(i, 1, n)
if(i != t)
printf("%d ", i);
puts("");
}
}
return 0;
}
E1. Weights Division (easy version)(简化问题)
遇到树的问题,一个常见的思路是简化成一条链来思考,然后拓展到树中
如果是一条链,显然贪心,每次都能减少的最大即可
减少的量是n/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 = 1e5 + 10;
struct node{ int v, w; };
struct Edge
{
int w, cnt;
bool operator < (const Edge& rhs) const
{
return (w + 1) / 2LL * cnt < (rhs.w + 1) / 2LL * rhs.cnt;
}
};
vector<node> g[N];
ll S, sum;
int dp[N], f[N], n;
void dfs(int u, int fa, int w, ll weight)
{
f[u] = w;
dp[u] = 0;
if(g[u].size() == 1)
{
dp[u] = 1;
sum += weight;
}
for(auto x: g[u])
{
int v = x.v; w = x.w;
if(v == fa) continue;
dfs(v, u, w, weight + w);
dp[u] += dp[v];
}
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%lld", &n, &S);
_for(i, 1, n) g[i].clear();
_for(i, 1, n - 1)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
g[u].push_back(node{v, w});
g[v].push_back(node{u, w});
}
sum = 0;
dfs(1, 0, 0, 0);
priority_queue<Edge> q;
_for(i, 1, n) q.push(Edge{f[i], dp[i]});
int ans = 0;
while(sum > S)
{
Edge x = q.top(); q.pop();
sum -= (x.w + 1) / 2LL * x.cnt;
q.push(Edge{x.w / 2, x.cnt});
ans++;
}
printf("%d\n", ans);
}
return 0;
}
B. Make Them Equal(构造)
这题差一点一点
已经想到了i=1时非常特殊
所以尽可能一移a1上,然后再用a1给其他的数加
问题是我移的时候是对i取模移的,导致a1不够大,可能出现负数的情况
正解是移到极致,直接2到n全部变成0
不是i的倍数的时候就用a1加到i的倍数
这样a1是一定不会为负数的
因为a >= 1
所以每次需要的数是i - ai % i < i
而1 ~ i - 1最少的和都为i - 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;
const int N = 1e4 + 10;
int a[N], n, sum;
struct node{ int i, j, x; };
vector<node> ans;
int main()
{
int T; scanf("%d", &T);
while(T--)
{
sum = 0;
ans.clear();
scanf("%d", &n);
_for(i, 1, n)
{
scanf("%d", &a[i]);
sum += a[i];
}
if(sum % n != 0)
{
puts("-1");
continue;
}
int k = sum / n, ok = 1;
_for(i, 2, n)
{
if(a[i] % i != 0)
{
int t = i - a[i] % i;
a[i] += t;
a[1] -= t;
ans.push_back(node{1, i, t});
}
ans.push_back(node{i, 1, a[i] / i});
a[1] += a[i];
a[i] = 0;
}
_for(i, 2, n) ans.push_back(node{1, i, k});
printf("%d\n", ans.size());
for(auto t: ans) printf("%d %d %d\n", t.i, t.j, t.x);
}
return 0;
}
E. Modular Stability(数学)
不要畏惧数论,其实也没有多难
我首先发现模数里面有1就可以了
于是我看那个7 3的16是怎么算的
1一定选的话有15种,还差一种什么呢
灵光一现貌似成倍数的都可以 这差的一组是2 4 6
2 4 6显然可以看成2 乘以 1 2 3
于是可以枚举选择的末尾位置,在中间选一定个数的方案用组合数,这一串可以乘一些数
只要满足最大的数不超过n就行了,所以可以算出可以乘的数是1到n/i
组合数的话预处理出阶乘就行了
#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 = 5e5 + 10;
const int mod = 998244353;
int fac[N], n, k, ans;
int add(int a, int b) { return (a + b) % mod; }
int mul(int a, int b) { return 1LL * a * b % mod; }
int binpow(int a, int b)
{
int res = 1;
for(; b; b >>= 1)
{
if(b & 1) res = mul(res, a);
a = mul(a, a);
}
return res;
}
int inv(int x) { return binpow(x, mod - 2); }
int C(int m, int n)
{
return mul(fac[n], mul(inv(fac[m]), inv(fac[n - m])));
}
int main()
{
fac[0] = 1;
rep(i, 1, N) fac[i] = mul(fac[i - 1], i);
scanf("%d%d", &n, &k);
if(k == 1) printf("%d\n", n);
else
{
_for(i, k, n) ans = add(ans, mul(C(k - 2, i - 2), n / i));
printf("%d\n", ans);
}
return 0;
}
周二
B. Domino for Young(二分图匹配 / 黑白染色)
这道题可太妙了
用到了棋盘的黑白染色,之前做过几道cf题也用到了黑白染色
黑白染色后,每次都是一个白与一个黑相连
这时最多的个数就是黑白个数的最小值
证明可以用二分图匹配的思想
比如对于一个白子,它可以和周围四个黑子匹配
如果都已经匹配,那么一定可以通过找增广路找到匹配
#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;
ll black, white;
int n;
int main()
{
scanf("%d", &n);
_for(i, 1, n)
{
int x; scanf("%d", &x);
if(i % 2 == 1)
{
black += (x + 1) / 2;
white += x / 2;
}
else
{
white += (x + 1) / 2;
black += x / 2;
}
}
printf("%lld\n", min(white, black));
return 0;
}
E. Reachability from the Capital(缩点+度数)
思考一段时间发现,把起点可以到达的点排除之后,寻找有多少个入度为0的点
但是我发现有环,这时入度就不为0了
所以我就用tarjan缩点,这样就没有环了
这就是题解里说的O(n + m)的做法
#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;
int f[N], dfn[N], low[N], st[N], co[N], vis[N], in[N];
int top, cnt, n, m, s, id;
vector<int> g[N], G[N];
vector<pair<int, int>> Edge;
void dfs(int u)
{
dfn[u] = low[u] = ++cnt;
st[++top] = u;
for(int v: g[u])
{
if(!dfn[v])
{
dfs(v);
low[u] = min(low[u], low[v]);
}
else if(!co[v]) low[u] = min(low[u], dfn[v]);
}
if(low[u] == dfn[u])
{
id++;
while(1)
{
co[st[top--]] = id;
if(st[top + 1] == u) break;
}
}
}
void build()
{
_for(i, 1, n)
if(!dfn[i])
dfs(i);
s = co[s];
_for(u, 1, n)
for(int v: g[u])
if(co[u] != co[v])
Edge.push_back(make_pair(co[u], co[v]));
sort(Edge.begin(), Edge.end());
int t = unique(Edge.begin(), Edge.end()) - Edge.begin();
rep(i, 0, t) G[Edge[i].first].push_back(Edge[i].second);
}
void dfs2(int u)
{
vis[u] = 1;
for(int v: G[u])
if(!vis[v])
dfs2(v);
}
int main()
{
scanf("%d%d%d", &n, &m, &s);
while(m--)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
}
build();
dfs2(s);
_for(u, 1, id)
if(!vis[u])
for(int v: G[u])
if(!vis[v])
in[v]++;
int ans = 0;
_for(i, 1, id)
if(!vis[i] && !in[i])
ans++;
printf("%d\n", ans);
return 0;
}
但其实搞复杂了
我没注意到这道题的数据范围比较小
n是5000而不是以往的1e5级别的
这个数据范围可以暴力
于是可以暴力算出每个点能到达的节点个数,排序
然后从大到小dfs,这样一定是最优的
#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;
int vis[N], k[N], cnt[N], n, m, s, sum;
vector<pair<int, int>> ve;
vector<int> g[N];
void dfs(int u)
{
vis[u] = 1;
for(int v: g[u])
if(!vis[v])
dfs(v);
}
void dfs2(int u)
{
sum++;
k[u] = 1;
for(int v: g[u])
if(!k[v] && !vis[v])
dfs2(v);
}
int main()
{
scanf("%d%d%d", &n, &m, &s);
while(m--)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
}
dfs(s);
_for(i, 1, n)
if(!vis[i])
{
sum = 0;
memset(k, 0, sizeof k);
dfs2(i);
ve.push_back(make_pair(-sum, i));
}
int ans = 0;
sort(ve.begin(), ve.end());
for(auto x: ve)
{
int u = x.second;
if(vis[u]) continue;
dfs(u);
ans++;
}
printf("%d\n", ans);
return 0;
}
周五
最近在打往年区域赛
每一场都好好补题,区域赛的题目质量还是挺高的
昨天打了2020年ICPC南京区域赛
A. Ah, It’s Yesterday Once More(构造)
队友想到了蛇形的构造,但是是从上到下的
正解是斜的蛇形构造
思路是尽可能的利用格子,构造长的绕的道路
代码不放了,构造斜着蛇形就好了
K. K Co-prime Permutation(互质)
套路是相邻的数质因数为1
首先1和任何数gcd都为1,所以至少有一个,k=0就输出-1
其他情况,前k个移个位置使得gcd为1,后面的数与自己gcd不为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;
int main()
{
int n, k;
scanf("%d%d", &n, &k);
if(k == 0) puts("-1");
else
{
vector<int> ans;
_for(i, 1, k)
{
int t = i - 1;
if(t == 0) t = k;
ans.push_back(t);
}
_for(i, k + 1, n) ans.push_back(i);
rep(i, 0, ans.size())
{
printf("%d", ans[i]);
if(i != ans.size() - 1) putchar(' ');
}
}
return 0;
}
L. Let's Play Curling(思维)
离散化一下,然后找最长的连续的红球就行了
写的时候注意红球位置会重复
我开始两发离散化的数组要开了两倍,但我的另外两个数组忘记开两倍了……
吸取教训,以后最好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 = 2e5 + 10;
int a[N], b[N], lsh[N], k[N], val[N], n, m, cnt;
int main()
{
int T; scanf("%d", &T);
while(T--)
{
cnt = 0;
scanf("%d%d", &n, &m);
_for(i, 1, n) scanf("%d", &a[i]), lsh[++cnt] = a[i];
_for(i, 1, m) scanf("%d", &b[i]), lsh[++cnt] = b[i];
sort(lsh + 1, lsh + cnt + 1);
cnt = unique(lsh + 1, lsh + cnt + 1) - lsh - 1;
_for(i, 1, cnt) k[i] = 0, val[i] = 0;
_for(i, 1, n)
{
int cur = lower_bound(lsh + 1, lsh + cnt + 1, a[i]) - lsh;
val[cur]++;
}
_for(i, 1, m)
{
int cur = lower_bound(lsh + 1, lsh + cnt + 1, b[i]) - lsh;
k[cur] = 1;
}
int ans = 0, s = 0;
_for(i, 1, cnt)
{
if(!k[i]) s += val[i];
else
{
ans = max(ans, s);
s = 0;
}
}
ans = max(ans, s);
if(!ans) puts("Impossible");
else printf("%d\n", ans);
}
return 0;
}
E. Evil Coordinate(构造)
考试的时候是分类讨论,写了150行,然后WA了
弄得很复杂,我也不知道哪些地方漏了
之后队友自己分类讨论了一波过了
看了题解,挺妙的
如果有解,那么一定可以通过同一种方向排列到一起实现
所以就枚举所有情况 4!= 24
#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;
int d[10], p[10], xx, yy;
int dir[4][2] = {0, 1, 0, -1, -1, 0, 1, 0};
char str[] = {'U', 'D', 'L', 'R'};
bool check()
{
int x = 0, y = 0;
rep(i, 0, 4)
{
int id = p[i];
_for(j, 1, d[id])
{
x += dir[id][0], y += dir[id][1];
if(x == xx && y == yy) return false;
}
}
return true;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
string s;
cin >> xx >> yy >> s;
int len = s.size();
if(!xx && !yy)
{
puts("Impossible");
continue;
}
memset(d, 0, sizeof d);
rep(i, 0, len)
rep(j, 0, 4)
if(s[i] == str[j])
d[j]++;
int ok = 0;
rep(i, 0, 4) p[i] = i;
while(1)
{
if(check()) { ok = 1; break; }
if(!next_permutation(p, p + 4)) break;
}
if(!ok) puts("Impossible");
else
{
rep(i, 0, 4)
_for(j, 1, d[p[i]])
putchar(str[p[i]]);
puts("");
}
}
return 0;
}
H. Harmonious Rectangle(抽屉原理)
这题挺可惜的
当时我想的时候感觉特别容易满足,但没有深入去想
其实超过一定情况是一定满足的
相等可以看作一个二元组相等
而二元组是9种情况
也就是说两行里面有超过10列,那么一定有一对二元组相等,一定满足条件
所以数据大的时候是一定满足的
数据小的时候直接暴力打表
方式是dfs,找到一个答案就剩下随便填
打表代码
#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 = 1e9 + 7;
int a[15][15], n, m;
ll binpow(ll a, ll b)
{
ll res = 1;
for(; b; b >>= 1)
{
if(b & 1) res = res * a % mod;
a = a * a % mod;
}
return res;
}
bool check(int x2, int y2)
{
for(int x1 = 1; x1 < x2; x1++)
for(int y1 = 1; y1 < y2; y1++)
if(a[x1][y1] == a[x1][y2] && a[x2][y1] == a[x2][y2] ||
a[x1][y1] == a[x2][y1] && a[x1][y2] == a[x2][y2] )
return true;
return false;
}
ll dfs(int x2, int y2, int cnt)
{
if(y2 == m + 1) y2 = 1, x2++;
if(x2 > n) return 0;
ll res = 0;
_for(c, 1, 3)
{
a[x2][y2] = c;
if(check(x2, y2)) res = (res + binpow(3, cnt)) % mod;
else res = (res + dfs(x2, y2 + 1, cnt - 1)) % mod;
}
return res;
}
int main()
{
for(n = 1; n <= 9; n++)
for(m = 1; m <= 9; m++)
{
memset(a, 0, sizeof a);
if(n == 1 || m == 1) printf("%d, ", 0);
else printf("%lld, ", dfs(1, 1, n * m - 1));
}
return 0;
}
AC代码
#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 = 1e9 + 7;
int ans[9][9] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 339, 4761, 52929, 517761, 4767849, 43046721, 387420489,
0, 339, 16485, 518265, 14321907, 387406809, 460338013, 429534507, 597431612, 0, 4761, 518265, 43022385, 486780060,
429534507, 792294829, 175880701, 246336683, 0, 52929, 14321907, 486780060, 288599194, 130653412, 748778899, 953271190,
644897553, 0, 517761, 387406809, 429534507, 130653412, 246336683, 579440654, 412233812, 518446848, 0, 4767849, 460338013,
792294829, 748778899, 579440654, 236701429, 666021604, 589237756, 0, 43046721, 429534507, 175880701, 953271190, 412233812,
666021604, 767713261, 966670169, 0, 387420489, 597431612, 246336683, 644897553, 518446848, 589237756, 966670169, 968803245};
ll binpow(ll a, ll b)
{
ll res = 1;
for(; b; b >>= 1)
{
if(b & 1) res = res * a % mod;
a = a * a % mod;
}
return res;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
int n, m;
scanf("%d%d", &n, &m);
if(n == 1 || m == 1) puts("0");
else if(n <= 9 && m <= 9) printf("%d\n", ans[n - 1][m - 1]);
else printf("%d\n", binpow(3, n * m));
}
return 0;
}
M. Monster Hunter(树上背包)
好久没写树上背包了
这题要简化一下
一个点的权值是自己点加上儿子中活着的点
儿子对当前有贡献当且仅当儿子和自己都活着的时候
可以用dp[0/1][i][j]
第一维表示是否活着
第二维表示节点
第三维表示当前子树中有几个活着的点
这个活着点就有点背包中重量的味道了,于是可以写树上背包
背包九讲里面讲到了泛化背包,看作一个函数的思想很有帮助,就涉及到背包的本质了
枚举的时候有小技巧,不然会T,具体看代码
这样枚举可以做到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;
typedef long long ll;
const int N = 2e3 + 10;
ll dp[2][N][N];
vector<int> g[N];
int siz[N], n;
void dfs(int u)
{
siz[u] = 1;
for(int v: g[u])
{
dfs(v);
for(int j = siz[u]; j >= 0; j--) //注意枚举重量是逆序枚举
_for(k, 0, siz[v])
{
dp[0][u][j + k] = min(dp[0][u][j + k], dp[0][u][j] + min(dp[0][v][k], dp[1][v][k])); //为了加速,这里用j + k
dp[1][u][j + k] = min(dp[1][u][j + k], dp[1][u][j] + min(dp[0][v][k], dp[1][v][k] + dp[1][v][1]));
}
siz[u] += siz[v];
}
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
_for(i, 0, n)
_for(j, 0, n)
dp[0][i][j] = dp[1][i][j] = 1e18; //long long的最大值设为1e18 int为1e9
_for(i, 1, n) g[i].clear();
_for(i, 2, n)
{
int x; scanf("%d", &x);
g[x].push_back(i);
}
_for(i, 1, n)
{
scanf("%lld", &dp[1][i][1]); //初始化
dp[0][i][0] = 0;
}
dfs(1);
for(int i = n; i >= 0; i--) printf("%lld ", min(dp[1][1][i], dp[0][1][i])); puts(""); //最后要取min
}
return 0;
}
周六
F. Clear the String(区间dp)
读完题就知道是区间dp
但是不知道怎么利用这个条件
答案是当s[i] == s[j]时可以减一次
这样可以包括所有的情况
我开始的时候只考虑了k和k+1
这样漏了相隔的情况
而s[i] == s[j]也包括相邻的情况
总是差一点点,多练
#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 = 500 + 10;
int dp[N][N], a[N][N], n;
char s[N];
int main()
{
scanf("%d%s", &n, s + 1);
memset(dp, 0x3f, sizeof dp);
_for(i, 1, n) dp[i][i] = 1;
_for(len, 2, n)
for(int i = 1; i + len - 1 <= n; i++)
{
int j = i + len - 1;
_for(k, i, j - 1)
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] - (s[i] == s[j]));
}
printf("%d\n", dp[1][n]);
return 0;
}
C. The Hard Work of Paparazzi(dp+优化)
这个看起来很长,其实题意很简单
很容易想到一个n方的dp
关键是怎么优化
我想了一会儿,突然发现r非常小
比较异常的数据范围是突破口
r最大只有500,意味着只要时间差大于1000就一定可以达到
所以时间差比较大的部分可以直接取区间最值,用一个变量存一下就行了
#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;
int dp[N], x[N], y[N], t[N], n, r;
int main()
{
scanf("%d%d", &r, &n);
_for(i, 1, n) scanf("%d%d%d", &t[i], &x[i], &y[i]);
x[0] = y[0] = 1;
memset(dp, -127, sizeof dp);
dp[0] = 0;
int mx = 0, r = 0;
_for(i, 1, n)
for(int j = i - 1; j >= 0; j--)
{
if(abs(t[i] - t[j]) <= 1000)
{
if(abs(t[i] - t[j]) >= abs(x[i] - x[j]) + abs(y[i] - y[j]))
dp[i] = max(dp[i], dp[j] + 1);
}
else
{
while(r < j) mx = max(mx, dp[++r]);
dp[i] = max(dp[i], mx + 1);
break;
}
}
int ans = 0;
_for(i, 1, n) ans = max(ans, dp[i]);
printf("%d\n", ans);
return 0;
}
C. Match Points(二分答案+尺取法)
开始直接尺取法,发现不对
因为r的起点不好判断
于是想到二分答案,左端点就前key个,r从key+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;
const int N = 2e5 + 10;
int a[N], n, z;
bool check(int key)
{
int ans = 0, l = 1, r = key + 1;
for(l = 1; l <= n; l++)
{
while(r <= n && a[r] - a[l] < z) r++;
if(r > n) break;
ans++; r++;
}
return ans >= key;
}
int main()
{
scanf("%d%d", &n, &z);
_for(i, 1, n) scanf("%d", &a[i]);
sort(a + 1, a + n + 1);
int l = 0, r = n / 2 + 1;
while(l + 1 < r)
{
int m = l + r >> 1;
if(check(m)) l = m;
else r = m;
}
printf("%d\n", l);
return 0;
}
C. Propagating tree(简化问题+dfs序+树状数组)
这题一开始理解错题意了……
没看样例解释……自作孽
首先简化问题,先考虑没有正负正负的情况
每次操作一个子树加一个值,询问一个点的值
其实很套路了,dfs序转化成区间,成了区间修改单点查询
差分+树状数组就可以实现
那么开始拓展
这道题有一个正负正负的关系,怎么处理呢?
可以发现,如果深度都为偶数或都为奇数,不用取负,否则取负
所以可以开两个树状数组,一个记录偶数深度的区间修改情况,一个记录奇数的
当前询问的是偶数深度,那就加上偶数的减去奇数的就可以了
#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 = 2e5 + 10;
int f[N][2], a[N], L[N], R[N], k[N], cnt, n, m;
vector<int> g[N];
int lowbit(int x) { return x & -x; }
void add(int x, int p, int id)
{
for(; x <= n; x += lowbit(x))
f[x][id] += p;
}
int sum(int x, int id)
{
int res = 0;
for(; x; x -= lowbit(x))
res += f[x][id];
return res;
}
void dfs(int u, int fa, int d)
{
L[u] = ++cnt;
k[u] = d % 2;
for(int v: g[u])
{
if(v == fa) continue;
dfs(v, u, d + 1);
}
R[u] = cnt;
}
int main()
{
scanf("%d%d", &n, &m);
_for(i, 1, n) scanf("%d", &a[i]);
_for(i, 1, n - 1)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1, 0, 1);
while(m--)
{
int op, x, val;
scanf("%d%d", &op, &x);
if(op == 1)
{
scanf("%d", &val);
add(R[x] + 1, -val, k[x]);
add(L[x], val, k[x]);
}
else printf("%d\n", a[x] + sum(L[x], k[x]) - sum(L[x], k[x] ^ 1));
}
return 0;
}
周日
Cook Pancakes!
自己智障没有特判直接交,WA了一发
要特判一下n <= k的情况
#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;
int main()
{
int n, k;
scanf("%d%d", &n, &k);
if(n <= k) puts("2");
else printf("%d\n", (2 * n + k - 1) / k);
return 0;
}
Xor Transformation(异或)
x ^ T = y
T = x ^ y
所以直接异或x ^ y就行了
但是要使得x ^ y < x
我们看y的最高位 最高位前面的0是没有影响
如果x的对应位为1,那么x ^ y < x
否则x ^ y > x
如果对应位为0,就转化成前一种情况,异或上这一位使得其为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++)
typedef long long ll;
ll x, y, ans1, ans2;
int main()
{
scanf("%lld%lld", &x, &y);
int maxy;
for(int i = 60; i >= 0; i--)
if(y & (1LL << i))
{
maxy = i;
break;
}
if(x & (1LL << maxy))
{
puts("1");
printf("%lld\n", x ^ y);
}
else
{
x ^= 1LL << maxy;
puts("2");
printf("%lld %lld\n", 1LL << maxy, x ^ y);
}
return 0;
}
Matrix Equation(高斯消元)
首先对应每一位相等可以列一个方程组
固定一列后,可以列含有n个未知数的n个方程
然后高斯消元求自由变元的数量就可以了
找了个模板直接抄
注意有无解的情况
这样一算是n的四次方的
用bitset优化
#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 = 200 + 10;
const int mod = 998244353;
bitset<N> a[N];
int A[N][N], B[N][N], n;
int binpow(int a, int b)
{
int res = 1;
for(; b; b >>= 1)
{
if(b & 1) res = 1LL * res * a % mod;
a = 1LL * a * a % mod;
}
return res;
}
int Guess()
{
int r = 0, c = 0;
while(r < n && c < n)
{
int m_r = r;
_for(i, r + 1, n)
if(a[i][c] > a[m_r][c]) m_r = i;
if(m_r != r) swap(a[r], a[m_r]);
if(!a[r][c])
{
a[r][c] = 0;
c++;
continue;
}
_for(i, r + 1, n)
if(a[i][c])
a[i] ^= a[r];
r++; c++;
}
_for(i, r, n)
if(a[i][n])
return -1;
return n - r;
}
int main()
{
scanf("%d", &n);
rep(i, 0, n)
rep(j, 0, n)
scanf("%d", &A[i][j]);
rep(i, 0, n)
rep(j, 0, n)
scanf("%d", &B[i][j]);
int ans = 0;
rep(j, 0, n)
{
rep(i, 0, n) a[i].reset();
rep(i, 0, n)
rep(k, 0, n)
{
if(k != i) a[i][k] = A[i][k];
else a[i][k] = abs(B[i][j] - A[i][i]);
}
int t = Guess();
if(t == -1)
{
puts("0");
return 0;
}
ans += t;
}
printf("%d\n", binpow(2, ans));
return 0;
}
Bit Sequence(数位dp)
算是写过的比较复杂的数位dp了
[0, L]符合某个条件的x的个数,八成数位dp(比赛时竟然没反应过来)
那数位dp该记录什么呢,注意到进位很不好处理
注意到m只有100,所以只要记录前7位,这样最多进位一次
因为2^7 = 128 > 100 开始时我弄成6位搞错了
那么如果判断符不符合条件呢?
只要后七位是什么数,剩余位有多少个1 以及7位之后有多少个连续的1(进位)
注意数位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;
typedef long long ll;
const int N = 110;
int a[N], b[N], len, m;
ll dp[70][2][2][2][150], L;
int cal(int x)
{
int res = 0;
for(; x; x >>= 1)
res += x & 1;
return res;
}
bool check(int k, int con, int num)
{
num--;
_for(i, 1, m)
{
num++;
if(num == 128)
{
num = 0;
k -= con - 1;
k = (k % 2 + 2) % 2;
}
if((k + cal(num)) % 2 != b[i]) return false;
}
return true;
}
ll dfs(int pos, int pre, int con, int k, int num, int limit)
{
if(pos > len) return check(k, con, num);
if(dp[pos][pre][con][k][num] != -1 && !limit) return dp[pos][pre][con][k][num];
ll res = 0, mx = limit ? a[len - pos + 1] : 1;
_for(i, 0, mx)
{
if(len - pos + 1 <= 7) res += dfs(pos + 1, i, con, k, num * 2 + i, i == mx && limit);
else
{
if(pre && i) res += dfs(pos + 1, i, (con + 1) % 2, (k + i) % 2, num, i == mx && limit);
else res += dfs(pos + 1, i, i, (k + i) % 2, num, i == mx && limit);
}
}
if(!limit) dp[pos][pre][con][k][num] = res;
return res;
}
ll solve(ll x)
{
len = 0;
while(x) a[++len] = x % 2, x /= 2;
memset(dp, -1, sizeof dp);
return dfs(1, 0, 0, 0, 0, 1);
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d%lld", &m, &L);
_for(i, 1, m) scanf("%d", &b[i]);
printf("%lld\n", solve(L));
}
return 0;
}