UESTC2022暑假二轮集训补题日志
UESTC2022暑假二轮集训补题日志
day1
数据删除
day2
Another Coin Weighing Puzzle(莫比乌斯反演,思维)
题意:
你有n包硬币,每包硬币里面都恰好有k个硬币。有一包硬币是假的,且假的硬币比真硬币重,且同种硬币重量相同(是个实数)。你可以使用天平称硬币,使用天平的条件是两边有数量相等的硬币。使用天平会返回两边重量之差(右减左),测量完之后硬币任你处置。你必须在第一次测量之前确定整个测量的行动策略(但是你可以通过观察每次的结果,把你预设的操作做完后回答假币是哪包),一包硬币中的k个硬币外观一样且和别的包的不一样(因此你可以辨认某个硬币是从哪包来的),在你最多使用天平m次的前提下,你最多可以在n等于多少包中,还能根据你的行动策略确定硬币中假的那一包。答案对998244353取模。
题解:
考虑次称量过后,最多有多少种可能的结果?
有这么多种:考虑对满足以下条件的整数序列计数:
- (再加上全为0的情况)
每一个数的含义是“右边比左边重多少个假币和真币的质量差”。
要求是因为我们并不知道真假硬币质量差的数值,例如对于$\left {0,1,2,3\right } \left {0,2,4,6 \right } m$次称量中我们只能辨识出它们的比值是。
设以上计数的结果为,也就是说我们经过次称量有种预期结果。
如果将个袋子分别按照其中一种序列进行摆放(第次称重时,这个袋子右边比左边多个),则可以根据次测量的实际的结果,确定出哪一个袋子装的假币。
如果此时增加一个袋子,那么一定存在一个已有的袋子对应的序列是本质相同的,无法区分。
补充证明:一定可以做到每次测量天平左右两边的硬币数量相同。
这是因为得到的个序列具有对称性,每次测量所有的和必然为0。
然后考虑
(考虑的倍数在中出现次,但要排除所有数都选)
答案就是
注意可能为负值,要加模数再取模。
//
// Created by vv123 on 2022/7/27.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e6 + 10;
const int mod = 998244353;
int vis[N], mu[N], prime[N];
void Shai(int n = N - 1) {
vis[1] = mu[1] = 1;
int cnt = 0;
for (int i = 2; i <= n; i++) {
if (!vis[i]) {
prime[++cnt] = i;
mu[i] = -1;
}
for (int j = 1; j <= cnt && i * prime[j] <= n; j++) {
vis[i * prime[j]] = 1;
if (i % prime[j] == 0) {
mu[i * prime[j]] = 0;
break;
} else {
mu[i * prime[j]] = mu[prime[j]] * mu[i];
}
}
}
}
int pow(int a, int b, int p) {
int res = 1;
for (; b; b >>= 1) {
if (b & 1) res = res * a % p;
a = a * a % p;
}
return res;
}
int m, k;
void solve() {
Shai();
cin >> m >> k;
int ans = 1;
for (int d = 1; d <= k; d++) {
ans = (ans + (pow(1 + 2 * (k / d), m, mod) - 1) * mu[d] % mod + mod) % mod;
}
cout << ans << "\n";
}
signed main() {
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
Editing Explosion (编辑距离,计数)
题意:
给定一个仅包含大写字母的字符串,求与它编辑距离为的字符串的数量
题解:
考虑编辑距离的经典dp.设dp[i][j]
表示t的前i位与s的前j位的编辑距离,则
dp[0][j] = j; dp[i][0] = i;
dp[i][j] = min(dp[i-1][j]+1,dp[i][j-1]+1,
dp[i-1][j-1]+(t[i]==s[j] ? 0 : 1)
我们可以发现,不同字符串t产生的不同dp[i]数组其实并不多。
Row i depends only on row i-1, the string a, and (for i>0) the character b[i-1].
Adjacent elements in a row differ by a maximum value of 1.
The length a is short; less than or equal to 10.
The maximum allowed distance is at most 10. (We can treat any values more than 10 as 11).
Thus, the number of distinct possible rows in the dp matrix, across all strings, is small.
我们按长度i从0到|s|+d依次生成字符串t。
对于每一个阶段i,考虑用一个map<vector,int>存下所有可能的dp[i]数组对应的字符串数量。
然后我们用每一个dp[i]数组推导出下一阶段得到的编辑距离数组,并将字符串数量累加上去。
dpv[j]=min(dpu[j]+1, dpv[j-1]+1, dpu[j-1] + (t[i]==s[j] ? 0 : 1))
对于所有产生的dp[i]数组array,如果array[|s|]=d,将数组array对应的字符串数量加到答案中即可。
//
// Created by vv123 on 2022/7/27.
//
#include <bits/stdc++.h>
#define int long long
#define vi vector<int>
using namespace std;
const int mod = 998244353;
char s[21]; int d;
map<vi, int> f[21];
void solve() {
cin >> s + 1 >> d;
int n = strlen(s + 1);
vi dp0(n + 1);
for (int i = 0; i <= n; i++) dp0[i] = i;
f[0][dp0] = 1;//dp[0]是唯一的
for (int i = 0; i <= n + d - 1; i++) {
for (auto u : f[i]) {
vi dpu = u.first; int cnt = u.second;
vi dpv(n + 1);
for (char c = 'A'; c <= 'Z'; c++) {
for (int j = 0; j <= n; j++) dpv[j] = dpu[j] + 1;
for (int j = 1; j <= n; j++) {
dpv[j] = min(dpv[j], min(dpv[j - 1] + 1, dpu[j - 1] + (c != s[j])));
}
f[i + 1][dpv] = (f[i + 1][dpv] + cnt) % mod;
}
}
}
int ans = 0;
for (int i = 0; i <= n + d; i++) {
for (auto u : f[i]) {
vi dpi = u.first; int cnt = u.second;
if (dpi[n] == d) ans = (ans + cnt) % mod;
}
}
cout << ans << "\n";
}
signed main() {
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
day3
Animal Companion in Maze (图论)
题意:
给定一个图,有双向边和单向边两种边,双向边不能立刻返回,问从任意一点开始的最长路径长度,或判定为Infinite
。
题解:
首先考虑如何判断图中是否存在有向环。
就,dfs维护一个栈,如果访问到栈中的结点说明有环
排除了inf的情况,考虑怎么求最长路
大概可以这样
但是TLE on test5 QAQ
//
// Created by vv123 on 2022/7/28.
//
#pragma GCC optimize(3,"Ofast","inline")
#include <bits/stdc++.h>
using namespace std;
const int N = 4e5 + 10;
int n, m;
int cnt = 0;//边的数量
int rev[N];//记录无向边中的一条边对应的另一条边
struct edge {
int v, id;
};
vector<edge> g[N];
struct dp {
int val, id;
bool operator<(const dp& x) const {
return val != x.val ? val > x.val : id < x.id;
}
};
bool vis[N];//某条边是否访问过
bool ins[N];//某条边是否在栈中
bool hascycle = false;
int f[N];//以某条边开始的最长路
set<dp> s[N];//保存结点所有出边的f值(似乎优先队列也可以)
void dfs(int u, int pre) { //入边编号
if (hascycle) return;
vis[pre] = true; ins[pre] = true;
for (auto [v, id] : g[u]) {
if (id == rev[pre]) continue;
if (!vis[id]) {
dfs(v, id);
s[u].insert({f[id], id});
} else if (ins[id]) {
hascycle = true;
return;
}
}
if (!s[u].empty()) { //从大到小排序
if (s[u].begin()->id == rev[pre]) {
//cout << "!!!" << u << " " << pre << "\n";
//考虑s[u]不一定是这次dfs(u,pre)更新的
//set中可能存在至多一个不合法的状态
auto it = ++s[u].begin();
if (it != s[u].end()) f[pre] = max(f[pre], it->val + 1);
}
else f[pre] = max(f[pre], s[u].begin()->val + 1);
}
ins[pre] = false;
}
void solve() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int u, v, w;
cin >> u >> v >> w;
if (w == 1) {
g[u].push_back({v, ++cnt});
} else {
g[u].push_back({v, ++cnt});
g[v].push_back({u, ++cnt});
rev[cnt] = cnt - 1;
rev[cnt - 1] = cnt;
}
}
for (int i = 1; i <= n; i++) {
dfs(i, 0);
}
if (hascycle) cout << "Infinite\n";
else cout << f[0] << "\n";
}
signed main() {
int T = 1;
//cin >> T;
ios::sync_with_stdio(false);
cin.tie(0);
while (T--) {
solve();
}
return 0;
}
Skinny Polygon(拓展欧几里得,构造)
题意
给你两个数A和B,要你构造一个三角形或一个四边形(凹凸任意),满足以下条件:
1.至少一个点的x坐标为0
2.至少一个点的x坐标为A
3.至少一个点的y坐标为0
4.至少一个点的y坐标为B
5.图形面积<=25000
6.任意点坐标(x,y),x和y必须是整数,且范围:0<=x<=A,0<=y<=B
数据范围:n个case,1<=n<=1e5,2<=X<=1e9,2<=Y<=1e9
题解
首先考虑(0,0)、(A,B)加上与直线Bx - Ay = 0最近的点构成的三角形。
不妨设这个点(x,y)在直线上方,由点到直线的距离公式和三角形面积公式,有S=(Bx-Ay)/2
注意到使Bx-Ay=c有解的最小正整数c=gcd(A,B)
故当gcd(A,B)不大于50000时,第三个点的坐标取Bx-Ay=gcd(A,B)在要求范围内的整数解即可。
如果gcd(A,B)大于50000,考虑凹四边形(0,0) (A-1,B) (A,B-1) (A/gcd(A,B), B/gcd(A,B)),它的面积一定不超过10^9/50000 = 20000。
补充:
1.若 ,扩展欧几里得算法求出的可行解必有 。
下面给出这一性质的证明。(OI-Wiki)
- 时,,必在下一层终止递归。
得到 ,显然 。 - 时,设 。
因为
所以
因此 成立。
考虑的解是的解的"共轭",然后显然的解是一正一负的,本题一定是有解的,只需要将负值取相反数即可。
顺便贴一下任意解和x最小正整数解的板子
//ax+by=c,gcd(a,b)|c
int x,y,kx,ky;
int gcd=extgcd(a,b,x,y);
x*=c/gcd;
y*=c/gcd;
kx=b/gcd;
ky=-a/gcd;
//通解就是:x+kx*n,y+ky*n
int x,y;
int g=extgcd(a,b,x,y);
x*=c/g;
b/=g;
if(b<0)b=-b;
int ans=x%b;
if(ans<0)ans+=b;
//x的最小正整数解
//
// Created by vv123 on 2022/7/28.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 4e5 + 10;
int exgcd(int a, int b, int &x, int &y) {
if (b == 0) {
x = 1; y = 0;
return a;
}
int g = exgcd(b, a % b, y, x);
y -= (a / b) * x;
return g;
}
int A, B;
void solve() {
cin >> A >> B;
int x, y, g = exgcd(B, A, x, y);
x = abs(x), y = abs(y);
if (g <= 50000) {
printf("3\n0 0\n%lld %lld\n%lld %lld\n", A, B, x, y);
} else {
printf("4\n0 0\n%lld %lld\n%lld %lld\n%lld %lld\n", A, B - 1, A / g, B / g, A - 1, B);
}
}
signed main() {
int T = 1;
cin >> T;
while (T--) {
solve();
}
return 0;
}
day4
Fractions (计数)
题意:
已知。求有多少对满足
题解:
满足条件的很少,枚举,统计对应的数量即可。
设
就做完了
//
// Created by vv123 on 2022/7/29.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 2e5 + 10;
int ceil(int x, int y) {
if (!x) return 0;
return x / y + (x % y != 0);
}
void solve() {
int A, B, C, D;
cin >> A >> B >> C >> D;
int ans = 0;
for (int p = 1; p < 1000; p++) {
for (int q = 1; p + q < 1000; q++) {
if (__gcd(p, q) != 1) continue;
int tmin = max(ceil(A, p), ceil(C, q));
int tmax = min(B / p, D / q);
ans += max(0ll, tmax - tmin + 1);
}
}
cout << ans << "\n";
}
signed main() {
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
Game on Plane (博弈论,SG函数)
题意:
两个人玩游戏,依次进行操作。
圆上有个点。每次操作选两个点连线,不能与已有的线相交,但可以有一个公共端点。
最后连出多边形的一方获胜。问先手胜还是后手胜。
题解:
我们可以将题意转化为“无法选出两个未选择的点”的一方必败,因为下一方马上就能连出三角形。
这样。
当时,先手进行第一次操作会将局面划分为两部分,设其中一部分的大小为,则得到的局面是和两个局面的和,即
根据相关结论,的函数值为
于是
从小到大递推即可。
//
// Created by vv123 on 2022/7/29.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 5010;
int sg[N];
void init(int n = N - 1) {
sg[1] = 0; sg[2] = 1;
for (int i = 1; i <= n; i++) {
unordered_set<int> s;
for (int j = 0; j <= i - 2; j++) {
s.insert(sg[j] ^ sg[i - 2 - j]);
}
for (int j = 0; ; j++) {
if (!s.count(j)) {
sg[i] = j;
break;
}
}
}
}
inline void solve() {
int n;
cin >> n;
sg[n] ? puts("First") : puts("Second");
}
signed main() {
init();
int T = 1;
cin >> T;
while (T--) {
solve();
}
return 0;
}
重链剖分+线段树 模板题
//
// Created by vv123 on 2022/7/29.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 2e6 + 10;
int mod, w[N], wt[N];
struct segmentTree {
#define mid (l+r>>1)
#define len (r-l+1)
#define llen (mid-l+1)
#define rlen (r-mid)
#define ls (u<<1)
#define rs (u<<1|1)
#define ain (s<=l&&r<=t)
#define lin (s<=mid)
#define rin (t>=mid+1)
int sum[N], laz[N];
void pushup(int u) {
sum[u] = (sum[ls] + sum[rs]) % mod;
}
void pushdown(int l, int r, int u) {
if (laz[u]) {
(sum[ls] += laz[u] * llen) %= mod; (sum[rs] += laz[u] * rlen) %= mod;
(laz[ls] += laz[u]) %= mod; (laz[rs] += laz[u]) %= mod;
laz[u] = 0;
}
}
void build(int l, int r, int u) {
if (l == r) { sum[u] = wt[l]; return; }
build(l, mid, ls); build(mid + 1, r, rs);
pushup(u);
}
void upd(int s, int t, int l, int r, int u, int k) {
k %= mod;
if (ain) {
(sum[u] += k * len) %= mod;
(laz[u] += k) %= mod;
return;
}
pushdown(l, r, u);
if (lin) upd(s, t, l, mid, ls, k);
if (rin) upd(s, t, mid + 1, r, rs, k);
pushup(u);
}
int ask(int s, int t, int l, int r, int u) {
int res = 0;
if (ain) return sum[u];
pushdown(l, r, u);
if (lin) (res += ask(s, t, l, mid, ls)) %= mod;
if (rin) (res += ask(s, t, mid + 1, r, rs)) %= mod;
return res;
}
} tr;
vector<int> g[N];
int n, q, rt;//wt以dfn为下标
int fa[N], dep[N], siz[N], son[N];
int cnt, top[N], dfn[N], rnk[N];
//fa dep siz son
void dfs1(int u) {
son[u] = -1;
siz[u] = 1;
for (auto v : g[u]) {
if (!dep[v]) {
dep[v] = dep[u] + 1;
fa[v] = u;
dfs1(v);
siz[u] += siz[v];
if (son[u] == -1 || siz[v] > siz[son[u]])
son[u] = v;
}
}
}
//top dfn rnk
void dfs2(int u, int t) {
top[u] = t;
dfn[u] = ++cnt;
rnk[cnt] = u;
wt[cnt] = w[u];
if (son[u] == -1) return;
dfs2(son[u], t);//优先遍历重儿子
for (auto v : g[u]) {
if (v != son[u] && v != fa[u]) {
dfs2(v, v);
}
}
}
int askPath(int x, int y) {
int res = 0;
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
(res += tr.ask(dfn[top[x]], dfn[x], 1, n, 1)) %= mod;
x = fa[top[x]];
}
//top[x] == top[y] in the same line
if (dep[x] > dep[y]) swap(x, y);
(res += tr.ask(dfn[x], dfn[y], 1, n, 1)) %= mod;
return res;
}
void updPath(int x, int y, int k) {
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
tr.upd(dfn[top[x]], dfn[x], 1, n, 1, k);
x = fa[top[x]];
}
if (dep[x] > dep[y]) swap(x, y);
tr.upd(dfn[x], dfn[y], 1, n, 1, k);
}
int askSon(int x) {
return tr.ask(dfn[x], dfn[x] + siz[x] - 1, 1, n, 1);
}
void updSon(int x, int k) {
tr.upd(dfn[x], dfn[x] + siz[x] - 1, 1, n, 1, k);
}
void print(int a[]) {
for (int i = 1; i <= n; i++)
cout << a[i] << " ";
cout << "\n";
}
void solve() {
cin >> n >> q >> rt >> mod;
for (int i = 1; i <= n; i++) {
cin >> w[i];
}
for (int i = 1; i <= n - 1; i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
dep[rt] = 1;
dfs1(rt);
dfs2(rt, rt);
tr.build(1, n, 1);
//print(fa);print(dep);print(siz);print(son);print(top);print(dfn);print(rnk);
while (q--) {
int op, x, y, k;
cin >> op;
if (op == 1) {
cin >> x >> y >> k;
updPath(x, y, k);
} else if (op == 2) {
cin >> x >> y;
cout << askPath(x, y) << "\n";
} else if (op == 3) {
cin >> x >> k;
updSon(x, k);
} else {
cin >> x;
cout << askSon(x) << "\n";
}
}
}
signed main() {
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
Coloring Roads(树链剖分+单调栈)(待填)
题意
给出一棵树。
询问u, c, m: 将结点u到根节点路径上的边都染色成c,询问染色边数为m的颜色有多少种。
题解
每次都是从一个结点到根,所以可以对这颗树进行树链剖分
维护
has[col]代表颜色col有多少条边。
cnt[m]边数为m的颜色有多少个。
dfn[v]:结点v的dfs编号。
对于每条链的头维护一个dfn的单调递增栈。
栈中的内容为(dfn,c),从dfn处用颜色c染色到链头。
对于一个染色操作,模拟树链跑到根结点的过程。
如果当前的结点dfn大于栈头的dfn,那就要将栈头的这个颜色的影响去掉,并弹出。
如果当前的结点的dfn小于栈头的dfn,那就将当前dfn到链头的之前颜色的影响去掉。
将dfn到链头染色为c,然后加入单调栈中。
————————————————
版权声明:本文为CSDN博主「Link_Ray」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Link_Ray/article/details/90679602
还没调完,明天再说
//
// Created by vv123 on 2022/7/30.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 2e6 + 10;
int mod, w[N], wt[N];
int c, ans[N], num[N];//ans[x] 结点数量为x的颜色数 num[x] 颜色x的结点数量
struct segmentTree {
#define mid (l+r>>1)
#define len (r-l+1)
#define llen (mid-l+1)
#define rlen (r-mid)
#define ls (u<<1)
#define rs (u<<1|1)
#define ain (s<=l&&r<=t)
#define lin (s<=mid)
#define rin (t>=mid+1)
int sum[N], laz[N];
void pushup(int u) {
sum[u] = (sum[ls] + sum[rs]) % mod;
}
void pushdown(int l, int r, int u) {
if (laz[u]) {
(sum[ls] += laz[u] * llen) %= mod; (sum[rs] += laz[u] * rlen) %= mod;
(laz[ls] += laz[u]) %= mod; (laz[rs] += laz[u]) %= mod;
laz[u] = 0;
}
}
void build(int l, int r, int u) {
if (l == r) { sum[u] = wt[l]; return; }
build(l, mid, ls); build(mid + 1, r, rs);
pushup(u);
}
void upd(int s, int t, int l, int r, int u, int k) {
k %= mod;
if (ain) {
(sum[u] += k * len) %= mod;
(laz[u] += k) %= mod;
return;
}
pushdown(l, r, u);
if (lin) upd(s, t, l, mid, ls, k);
if (rin) upd(s, t, mid + 1, r, rs, k);
pushup(u);
}
int ask(int s, int t, int l, int r, int u) {
int res = 0;
if (ain) return sum[u];
pushdown(l, r, u);
if (lin) (res += ask(s, t, l, mid, ls)) %= mod;
if (rin) (res += ask(s, t, mid + 1, r, rs)) %= mod;
return res;
}
} tr;
vector<int> g[N];
int n, q, rt;//wt以dfn为下标
int fa[N], dep[N], siz[N], son[N];
int cnt, top[N], dfn[N], rnk[N];
//fa dep siz son
void dfs1(int u) {
son[u] = -1;
siz[u] = 1;
for (auto v : g[u]) {
if (!dep[v]) {
dep[v] = dep[u] + 1;
fa[v] = u;
dfs1(v);
siz[u] += siz[v];
if (son[u] == -1 || siz[v] > siz[son[u]])
son[u] = v;
}
}
}
//top dfn rnk
void dfs2(int u, int t) {
top[u] = t;
dfn[u] = ++cnt;
rnk[cnt] = u;
wt[cnt] = w[u];
if (son[u] == -1) return;
dfs2(son[u], t);//优先遍历重儿子
for (auto v : g[u]) {
if (v != son[u] && v != fa[u]) {
dfs2(v, v);
}
}
}
int askPath(int x, int y) {
int res = 0;
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
(res += tr.ask(dfn[top[x]], dfn[x], 1, n, 1)) %= mod;
x = fa[top[x]];
}
//top[x] == top[y] in the same line
if (dep[x] > dep[y]) swap(x, y);
(res += tr.ask(dfn[x], dfn[y], 1, n, 1)) %= mod;
return res;
}
void updPath(int x, int y, int k) {
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
tr.upd(dfn[top[x]], dfn[x], 1, n, 1, k);
x = fa[top[x]];
}
if (dep[x] > dep[y]) swap(x, y);
tr.upd(dfn[x], dfn[y], 1, n, 1, k);
}
int askSon(int x) {
return tr.ask(dfn[x], dfn[x] + siz[x] - 1, 1, n, 1);
}
void updSon(int x, int k) {
tr.upd(dfn[x], dfn[x] + siz[x] - 1, 1, n, 1, k);
}
void print(int a[]) {
for (int i = 1; i <= n; i++)
cout << a[i] << " ";
cout << "\n";
}
void updColor(int c, int k) {
ans[num[c]]--;
num[c] += k;
ans[num[c]]++;
}
vector<pii> st[N];
void solve() {
cin >> n >> c >> q;
ans[0] = c;
rt = 1;
for (int i = 1; i <= n - 1; i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
dep[rt] = 1;
dfs1(rt);
dfs2(rt, rt);
//tr.build(1, n, 1);
//print(fa);print(dep);print(siz);print(son);print(top);print(dfn);print(rnk);
while (q--) {
int u, c, m;
cin >> u >> c >> m;
while (u) {
int tp = top[u];
int pre = dfn[tp] - 1;
//你考虑链上现有的颜色
//添加的会把dfn小于它的都覆盖掉
//维护一个单调减的栈即可
//一段一段地减去pos - pre的影响
while (!st[tp].empty() && st[tp].back().first <= dfn[u]) {
auto [pos, color] = st[tp].back();
st[tp].pop_back();
updColor(color, -(pos - pre));
pre = pos;
}
if (!st[tp].empty()) {
auto [pos, color] = st[tp].back();
updColor(color, -(dfn[u] - pre));
}
updColor(c, dfn[u] - dfn[tp] + 1);
u = fa[u];
}
//注意到求的是边数
//如果num[c]==m
cout << ans[m] - (num[c] == m) + (num[c] == m + 1) << "\n";
}
}
signed main() {
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
day5
Hacked (树论,思维)
题意:
树上有个被选择的点,你需要为他们两两配对,并选出个居住点,使所有点对的路径上至少有一个居住点,且是最小的。
题解:
首先证明一个结论:一定存在一个结点,我们以它为根,那么以它的子节点为根的所有子树中,被选择的点数不大于。
考虑以为根节点,设为以为根的树中被选择的结点数。如果除外所有节点的值都不大于,我们就找到了符合要求的根节点,否则,一定存在某些子节点,使得。
此时可以证明:树上至少存在一个节点满足:,但对的所有子节点,有。考虑反证,如果对于所有的都有,则我们令递归下去,会得到,这与矛盾。
考虑将满足上述条件的结点设为根,因为以为根时,所以以为根时必有,又因为的其他子节点都满足,所以就是满足要求的根节点。
我们可以通过dfs找到这样的根节点。具体来说,以随便一个点(如1)为根dfs,设表示以为根时它的所有子节点的最大值,遍历的子树时用更新,最后用更新。的点就是我们要的根节点。
然后结论是:存在一种配对方案,只需要以该结点作为唯一的居住点。
我们把拆分成了若干个不大于的数的和。似乎有一种方案是,每次选择最大的和任意一个其他的数配对。但是正确性暂时不会证明。
题解给出的方法是,将来自同一个子树的选择点放在一起,排成一列,其实就是按dfs序排列一下,由每个子树的值不大于可知一定不在一个子树中。
//
// Created by vv123 on 2022/7/30.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 2e6 + 10;
int n, k, chosen[N], f[N], rt, a[N];
vector<int> g[N];
void dfs1(int u, int fa) {
f[u] = chosen[u];
int maxf = 0;
for (auto v : g[u]) {
if (v == fa) continue;
dfs1(v, u);
f[u] += f[v];
maxf = max(maxf, f[v]);
}
maxf = max(maxf, k * 2 - f[u]);
if (maxf <= k) rt = u;
}
int cnt = 0;
void dfs2(int u, int fa) {
if (chosen[u]) a[++cnt] = u;
for (auto v : g[u]) {
if (v == fa) continue;
dfs2(v, u);
}
}
void solve() {
cin >> n >> k;
for (int i = 1; i <= n - 1; i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
for (int i = 1; i <= k * 2; i++) {
int t; cin >> t;
chosen[t] = 1;
}
dfs1(1, 0);
dfs2(rt, 0);
cout << "1\n" << rt << "\n";
for (int i = 1; i <= k; i++) {
cout << a[i] << " " << a[i + k] << " " << rt << "\n";
}
}
signed main() {
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
C - Split and Maximize (计数,Catalan)
题意:
求满足下列条件的1至2N的排列个数:它可以被划分成两个长度为N的子序列A,B,使得最大
题解:
显然要尽量让大的和大的相乘。例如
这样对于和会产生种划分,每种划分又可以构成种顺序
考虑每种定序划分对应了多少种可能的排列,考虑按照已有的顺序对每一个位置依次安排元素,这个长度为的序列需要满足每一个位置前缀的中元素数量小于等于中元素,因此符合要求的安排数等于长度为的合法括号序列数。综上,答案为
//
// Created by vv123 on 2022/7/30.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 4e5 + 10;
const int mod = 998244353;
int f[N], p[N];
void init(int n = N - 1) {
f[0] = 1; p[0] = 1;
for (int i = 1; i <= n; i++) {
f[i] = f[i - 1] * i % mod;
p[i] = p[i - 1] * 2ll % mod;
}
}
int pow(int a, int b, int p) {
int res = 1;
for (; b; b >>= 1) {
if (b & 1) res = res * a % p;
a = a * a % p;
}
return res;
}
int n;
string s;
int a[N];
void solve() {
cin >> n;
int ans = f[n * 2] % mod * p[n] % mod * pow(f[n + 1], mod - 2, mod) % mod;
cout << ans << "\n";
}
void test() {
int a[] = {0, 1, 2, 3, 4, 5, 6};
int cnt = 0;
do {
bool flag = false;
for (int i = 1; i <= 6; i++) {
for (int j = i + 1; j <= 6; j++) {
for (int k = j + 1; k <= 6; k++) {
vector<int> v;
for (int p = 1; p <= 6; p++) {
if (p != i && p != j && p != k) v.push_back(p);
}
if (a[v[0]] * a[i] + a[v[1]] * a[j] + a[v[2]] * a[k] == 44)
flag = true;
}
}
}
if (flag) cnt++;
} while (next_permutation(a + 1, a + 1 + 6));
cout << cnt << "\n";
}
signed main() {
init();
//test();
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
day6
D - Magical Array(脑洞)
题意:
略
题解:
注意到操作1不改变前缀和的和,操作2使前缀和的和+1,计算一下即可。
//
// Created by vv123 on 2022/7/15.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 5e5 + 10;
const int inf = 0x3f3f3f3f;
int n, m;
void print(int arr[]) {
for (int i = 1; i <= n; i++)
cout << arr[i] << " ";
cout << "\n";
}
int a[N], s[N];
int work() {
int res = 0;
for (int i = 1; i <= m; i++) {
cin >> a[i];
s[i] = s[i - 1] + a[i];
res = res + s[i];
}
return res;
}
int ss[N];
void solve() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
ss[i] = work();
}
if (ss[1] != ss[2] && ss[2] == ss[3]) {
cout << 1 << " " << abs(ss[1] - ss[2]) << "\n";
return;
}
for (int i = 1; i <= n; i++) {
if (ss[i] != ss[1]) {
cout << i << " " << abs(ss[1] - ss[i]) << "\n";
return;
}
}
}
signed main() {
int T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
day7
Prime Ring Plus(网络流,构造)
题意
构造若干个由[1,n]组成的环,环中相邻两个数之和为质数。若没有就输出-1。
题解
我们考虑如果有解的话,得到的是一些奇偶相间的环,环的长度都是偶数,所以只有当n为偶数时有解。
这样最后得到的图,有n个点n条边,每个点的度数都为2,具体来说每个奇数点与两个偶数点相连,每个偶数点与两个奇数点相连,相连的两个点和为质数。
从原点对所有奇数建立容量为2的边,再由奇数点向和为质数的偶数点建边,最后从所有偶数再建一条容量的2的边到汇点。 如果恰好能跑满流,则找到了一个符合上述特征的图。
找到后从1号点开始DFS即可。
//
// Created by vv123 on 2022/8/1.
//
#include <bits/stdc++.h>
using namespace std;
const int N = 2e4 + 10;
const int INF = 0x3f3f3f3f;
bool isprime[N];
void init() {
for (int x = 2; x <= 20000; x++) {
bool isp = true;
for (int i = 2; i * i <= x; i++) {
if (x % i == 0) {
isp = false;
break;
}
}
isprime[x] = isp;
}
}
struct Edge {
int to, cap, flow;
};
vector<int> vec[N];
struct Dinic {
int n, m, s, t;//结点数,边数(包括反向弧),源点编号,汇点编号
vector<Edge> edges;//边表,dges[e]和dges[e^1]互为反向弧
vector<int> G[N];//邻接表,G[i][j]表示结点i的第j条边在e数组中的编号
bool vis[N]; //BFS的使用
int d[N]; //从起点到i的距离
int cur[N]; //当前弧下标
void AddEdge(int from, int to, int cap) {
edges.push_back((Edge) {to, cap, 0});
edges.push_back((Edge) {from, 0, 0});
int m = edges.size();
G[from].push_back(m - 2);
G[to].push_back(m - 1);
}
bool bfs() {
memset(vis, 0, sizeof(vis));
queue<int> Q;
Q.push(s);
d[s] = 0;
vis[s] = 1;
while (!Q.empty()) {
int x = Q.front();
Q.pop();
for (int i = 0; i < G[x].size(); i++) {
Edge &e = edges[G[x][i]];
if (!vis[e.to] && e.cap > e.flow)//只考虑残量网络中的弧
{
vis[e.to] = 1;
d[e.to] = d[x] + 1;
Q.push(e.to);
}
}
}
return vis[t];
}
int dfs(int x, int a)//x表示当前结点,a表示目前为止的最小残量
{
if (x == t || a == 0)return a;//a等于0时及时退出,此时相当于断路了
int flow = 0, f;
for (int &i = cur[x]; i < G[x].size(); i++)//从上次考虑的弧开始,注意要使用引用,同时修改cur[x]
{
Edge &e = edges[G[x][i]];//e是一条边
if (d[x] + 1 == d[e.to] && (f = dfs(e.to, min(a, e.cap - e.flow))) > 0) {
e.flow += f;
edges[G[x][i] ^ 1].flow -= f;
vec[x].push_back(e.to);
vec[e.to].push_back(x);
flow += f;
a -= f;
if (!a)break;//a等于0及时退出
}
}
return flow;
}
int Maxflow(int s, int t)//主过程
{
this->s = s, this->t = t;
int flow = 0;
while (bfs())//不停地用bfs构造分层网络,然后用dfs沿着阻塞流增广
{
memset(cur, 0, sizeof(cur));
flow += dfs(s, INF);
}
return flow;
}
}dc;
void print(vector<int> vec) {
for (auto u:vec) {
cout << u << " ";
}
cout << "\n";
}
int n;
bool vis[N];
void dfs(int u, vector<int> &vi) {
vi.push_back(u);
vis[u] = 1;
for (auto v : vec[u]) {
if (v == 0 || v == n + 1 || vis[v]) continue;
dfs(v, vi);
}
}
int main() {
init();
cin >> n;
if (n % 2 != 0) {
puts("-1");
return 0;
}
for (int i = 1; i <= n - 1; i += 2) {
for (int j = 2; j <= n; j += 2) {
if (isprime[i + j]) dc.AddEdge(i, j, 1);
}
}
for (int i = 1; i <= n - 1; i += 2) dc.AddEdge(0, i, 2);
for (int i = 2; i <= n; i += 2) dc.AddEdge(i, n + 1, 2);
int flow = dc.Maxflow(0, n + 1);
for (int i = 1; i <= n; i++) {
if (!vis[i]) {
vector<int> v;
dfs(i, v);
cout << v.size() << " " << v[0];
for (int i = 1; i < v.size(); i++)
cout << " " << v[i];
cout << "\n";
}
}
return 0;
}
day8
Life is a Game (Kruskal重构树,树上倍增)(待填)
题意:
给定一个图。每次询问给出出生点x和初始能力值。出生或者走到一个点会将能力值加上该点点权,当能力值不小于边权时才能通过一条边。问最值能力值的最大值。
题解:
如tag所示
wa2待调
//
// Created by vv123 on 2022/8/2.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 4e5 + 10;
const int inf = 1e18;
int n, m, q, fa[N], d[N], f[N][31], g[N][31], mink[N];
int find(int x) { return x == fa[x] ? fa[x] : fa[x] = find(fa[x]); }
struct edge {
int u, v, w;
bool operator<(const edge& x) const {
return w < x.w;
}
} e[N];
vector<int> G[N];
int val[N], cnt, siz[N];
void kruskal() {
sort(e + 1, e + 1 + m);
for (int i = 1; i <= n; i++) fa[i] = i;
cnt = n;
for (int i = 1; i <= m; i++) {
auto[u, v, w] = e[i];
int fu = find(u), fv = find(v);
if (fu != fv) {
val[++cnt] = w;
fa[cnt] = fa[fu] = fa[fv] = cnt;
//printf("%d,%d --%d--> %d\n", fu, fv, w, cnt);
G[cnt].push_back(fu); G[fu].push_back(cnt);
G[cnt].push_back(fv); G[fv].push_back(cnt);
}
}
}
//mink[u]从叶子节点到u路径上val - siz的最大值
//g[u][i] 第2^i级祖先的mink值
void dfs(int u, int fa) {
siz[u] = u <= n ? val[u] : 0;
d[u] = d[fa] + 1; f[u][0] = fa;
for (int i = 1; (1 << i) <= d[u]; i++)
f[u][i] = f[f[u][i - 1]][i - 1];
for (auto v : G[u]) {
if (v == fa) continue;
dfs(v, u);
siz[u] += siz[v];
}
mink[u] = u <= n ? -inf : val[u] - siz[u];
}
void dfs2(int u, int fa) {
g[u][0] = mink[fa];
for (int i = 1; (1 << i) <= d[u]; i++)
g[u][i] = max(g[u][i - 1], g[f[u][i - 1]][i - 1]);
for (auto v : G[u]) {
if (v != fa) dfs2(v, u);
}
}
signed main() {
cin >> n >> m >> q;
for (int i = 1; i <= n; i++) cin >> val[i];
for (int i = 1; i <= m; i++) {
cin >> e[i].u >> e[i].v >> e[i].w;
}
kruskal();
dfs(cnt, 0); dfs2(cnt, 0);
//for (int i = 1; i <= cnt; i++) {
// printf("%d: val=%d, siz=%d, mink=%d\n", i, val[i], siz[i], mink[i]);
//}
while (q--) {
int x, k;
cin >> x >> k;
if (val[f[x][0]] > k + val[x]) {
cout << k + val[x] << "\n";
continue;
}
for (int i = 30; i >= 0; i--) {
if (((1 << i) <= d[x]) && g[x][i] <= k) x = f[x][i];
}
cout << k + siz[x] << "\n";
}
return 0;
}
Color with Occurrences (贪心,模拟)
昨天赛时过了,不过有些小题大作。
简单来说就是你可以用一些模式串在匹配的地方为主串染色,问最少染几次能把主串全染红。
然后转化成了用最少的区间覆盖某个区间的问题
之前在acwing写过一个类似的题,但不一样的是cf的区间是整数意义上的,[1,2]和[3,4]也被认为覆盖了[3,4]
acwing题
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
const int inf = 0x3f3f3f3f;
int n, s, t;
struct Seg {
int l, r;
} seg[N];
bool cmp(Seg a, Seg b) {
return a.l < b.l;
}
int main() {
cin >> s >> t >> n;
for (int i = 0; i < n; i++) {
cin >> seg[i].l >> seg[i].r;
}
sort(seg, seg + n, cmp);
int pos = s, maxr = -inf, ans = 0;
for (int i = 0; i < n; i++) {
if (seg[i].l > pos) pos = maxr, ans++;
if (seg[i].l <= pos && seg[i].r >= pos && seg[i].r > maxr) {
maxr = seg[i].r;
}
if (pos >= t) break;
}
if (pos < t && maxr >= t) pos = maxr, ans++;
if (pos >= t) cout << ans;
else cout << -1;
return 0;
}
cf题
//
// Created by vv123 on 2022/8/1.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 5e5 + 10;
const int inf = 0x3f3f3f3f;
void print(int arr[], int n) {
for (int i = 1; i <= n; i++)
cout << arr[i] << " ";
cout << "\n";
}
struct Seg {
int l, r, id;
} seg[N];
bool cmp(Seg a, Seg b) {
return a.l < b.l;
}
int n, a[N];
char s[N], t[N];
void solve() {
cin >> t + 1 >> n;
int lent = strlen(t + 1);
int cnt = 0;
for (int i = 1; i <= n; i++) {
cin >> s + 1;
int len = strlen(s + 1);
for (int l = 1; l + len - 1 <= lent; l++) {
int r = l + len - 1;
bool ok = true;
for (int p = l; p <= r; p++)
if (s[p - l + 1] != t[p]) ok = false;
if (ok) seg[++cnt] = {l, r, i};
}
}
sort(seg + 1, seg + cnt + 1, cmp);
for (int i = 1; i <= cnt; i++) {
//printf("%d,%d,%d\n", seg[i].l, seg[i].r, seg[i].id);
}
int pos = 1, maxr = -inf, id = -1, idl;
vector<pii> ans;
for (int i = 1; i <= cnt; i++) {
if (seg[i].l > pos) ans.push_back({id, idl}), pos = maxr + 1;
if (seg[i].l <= pos && seg[i].r >= pos && seg[i].r > maxr) {
maxr = seg[i].r, id = seg[i].id, idl = seg[i].l;
}
if (pos > lent) break;
}
if (pos <= lent && maxr >= lent) pos = maxr + 1, ans.push_back({id, idl});
if (pos > lent) {
cout << ans.size() << "\n";
for (auto u : ans)
cout << u.first << " " << u.second << "\n";
}
else cout << -1 << "\n";
}
signed main() {
int T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
day9
Poor Turkeys(逆推,bitset优化)
题意:
有N只火鸡,M个人每个人有两个数x和y,依次操作,如果当前火鸡x和y都活着就随便吃一只,如果剩一个就吃它,如果都死了就什么也不做。问有多少对(i,j)满足i和j最后可能都活着。
N<=400,M<=1e5
题解:
学自洛谷题解。首先考虑一个的做法,我们枚举每一对,可以倒推需要保护的火鸡有哪些。如果往前推到某一个时刻发现谁都无法保护就无法保护.
有了这个思路可以写出暴力代码。
//
// Created by vv123 on 2022/8/1.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 5e5 + 10;
const int inf = 0x3f3f3f3f;
long double eps = 1e-8;
int n, m;
int x[N], y[N], protect[N];
void solve() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> x[i] >> y[i];
}
int cnt = 0;
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
memset(protect, 0, sizeof protect);
protect[i] = protect[j] = 1;
bool ok = true;
for (int k = m; k >= 1; k--) {
if (protect[x[k]] && protect[y[k]]) {
ok = false;
break;
} else if (protect[x[k]] || protect[y[k]]) {
protect[x[k]] = protect[y[k]] = true;
}
}
cnt += ok;
}
}
cout << cnt << "\n";
}
signed main() {
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
然后我们发现对于每一个j的处理都是相似的,可以对每一个i用bitset更新出每一个j的答案,这样就可以卡过了。
//
// Created by vv123 on 2022/8/1.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 5e5 + 10;
const int inf = 0x3f3f3f3f;
long double eps = 1e-8;
int n, m;
int x[N], y[N];
bitset<405> protect[405], fail;
void solve() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> x[i] >> y[i];
}
int cnt = 0;
for (int i = 1; i <= n; i++) {
fail.reset();
for (int j = 1; j <= n; j++) protect[j].reset();
//protect[x][y] x在(i,y)中是否被保护
for (int j = i + 1; j <= n; j++) protect[i][j] = protect[j][j] = 1;
for (int k = m; k >= 1; k--) {
fail |= protect[x[k]] & protect[y[k]];
protect[x[k]] = protect[y[k]] = protect[x[k]] ^ protect[y[k]];
}
cnt += n - i - fail.count();
}
cout << cnt << "\n";
}
signed main() {
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
Integers on a Tree(构造,树形dp,(贪心?))
题意:
你有一棵树,树上有些点有点权。你要为没有点权的点分配点权,使得相邻点的点权相差1。
题解:
首先考虑一个性质,我们把某个已有权值的点设为根dfs,求出每个点的深度,则随着深度的增加,点权一定是奇偶交替的。如果已有的权值的点不满足此性质,则无解。
然后设结点u的值域为[l[u], r[u]]
,已定结点的值域初始化为点权,未定点初始化为[-inf,inf]
考虑子节点v对父节点的影响,有
l[u] = max(l[u], l[v] - 1);
r[u] = min(r[u], r[v] + 1);
如果我们自下而上求出所有点的值域都非空,则一定有解。因为值域体现了来自子节点的限制,且考虑一个已定点被所有子节点更新后值域仍非空,说明它的子节点可以满足来自父节点的条件。对于未定点选值域内第一个符合条件的点即可。
还有一种优先队列贪心,略...
//
// Created by vv123 on 2022/8/1.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 5e5 + 10;
const int inf = 0x3f3f3f3f;
long double eps = 1e-8;
int n, k, rt, w[N], l[N], r[N], d[N];
vector<int> g[N];
void dfs(int u, int fa) {
d[u] = d[fa] + 1;
if (w[u] != -1 && (w[u] % 2 ^ d[u] % 2) != (w[rt] % 2 ^ d[rt] % 2)) {
puts("No");
exit(0);
}
for (auto v : g[u]) {
if (v == fa) continue;
dfs(v, u);
l[u] = max(l[u], l[v] - 1);
r[u] = min(r[u], r[v] + 1);
}
if (l[u] > r[u]) {
puts("No");
exit(0);
}
}
void work(int u, int fa) {
if (w[u] == -1) {
if (l[u] == -inf) l[u] = w[fa] - 1;
for (int i = l[u]; i <= r[u]; i++) {
if ((i % 2 ^ d[u] % 2) == (w[rt] % 2 ^ d[rt] % 2) && abs(i - w[fa]) == 1) {
w[u] = i;
break;
}
}
}
for (auto v : g[u]) {
if (v == fa) continue;
work(v, u);
}
}
void solve() {
cin >> n;
for (int i = 1; i <= n - 1; i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
for (int i = 1; i <= n; i++) {
l[i] = -inf; r[i] = inf; w[i] = -1;
}
cin >> k;
for (int i = 1; i <= k; i++) {
int x, y;
cin >> x >> y;
l[x] = r[x] = w[x] = y;
if (i == 1) rt = x;
}
dfs(rt, 0);
work(rt, 0);
puts("Yes");
for (int i = 1; i <= n; i++) {
cout << w[i] << "\n";
}
}
signed main() {
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
Forest(图论,贪心)
题意:
给你一个森林,点有点权,每次操作可以选两个点连边,花费是它们的点权和,每个点只能用一次。问把图联通的最小花费。
题解:
如果有cnt个连通块,其实我们只需要选出(cnt - 1) * 2个点就可以了(只要它们覆盖了所有的连通块)...
首先求出每个已有联通块中的最小点权,然后在剩下的点里面选最小的,只要选够(cnt - 1) * 2个点即可...然后连法就是每个点贪心地向后面最近的未连的连通块连边...
代码略
day10
Dasha and cyclic table(bitset优化暴力)
题意:
有两个矩阵A和B,B有一些通配符,输出一个01矩阵表示B在哪些位置能匹配A,超出边界的地方可以延拓。
矩阵的边长<=400。时限6s。
题解:
我们考虑B矩阵的每一个字符可以让答案矩阵的哪些位置变成0,通配符跳过。
可以把A拆成26个01矩阵A[n][m][26]=0/1
,暴力地做是的。
ch = B[r][c];
if (ch == '?') continue;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
ans[(i - r % n + n) % n][((j - c % m + m) % m] &= A[i][j][ch];
}
}
如果借助bitset利用位运算一次处理,就可以除掉一个64通过本题。
有了这个想法,定义ans[i]
为答案矩阵的第i行,A[i][ch]
为第i行对于字符ch的01矩阵
对于
for (int j = 0; j < m; j++) {
int tj = (j - c % m + m) % m;
ans[((i - r + n) % n][tj] &= A[i][ch][j];
}
只需要根据tj = (j - c % m + m) % m
对A进行移位
这可能有些抽象,但是结合j=c%m -> tj=0
考虑这种循环移位,可以验证最后符合这样的规律
ans[(i - r % n + n) % n] &= A[i][ch] >> c % m | A[i][ch] << m - c % m;
注意bitset和二进制数一样使用小端法
//
// Created by vv123 on 2022/8/4.
//
#include <bits/stdc++.h>
using namespace std;
const int N = 400 + 10;
int n, m, R, C;
bitset<N> ans[N], A[N][26];
char s[N];
int main() {
cin >> n >> m;
for (int i = 0; i < n; i++) {
cin >> s;
for (int j = 0; j < m; j++) {
int ch = s[j] - 'a';
A[i][ch][j] = ans[i][j] = 1;
}
}
cin >> R >> C;
for (int r = 0; r < R; r++) {
cin >> s;
for (int c = 0; c < C; c++) {
if (s[c] == '?') continue;
int ch = s[c] - 'a';
for (int i = 0; i < n; i++) {
ans[(i - r % n + n) % n] &= A[i][ch] >> c % m | A[i][ch] << m - c % m;
}
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++)
cout << ans[i][j];
cout << "\n";
}
return 0;
}
Robot in a Hallway (模拟,dp?,线段树?)
题意:
有2*m的网格,一开始在(1,1),每个格子有一个解锁时间,每次操作可以花费1秒钟走向一个已经解锁的格子,不能两次进入同一个格子。问走完所有格子的最短时间
题解:
如果第一步往右走,只能逆时针走一遍。
否则路径一定可以表示为蛇形+一个顺时针或逆时针的环。我们可以预处理出蛇形的路径,每次走两步然后尝试走环。
将下面的路径展开到第一行后面,容易发现环形路径上的瓶颈由点权以及位置共同决定,而且顺时针方向位置的贡献为负,逆时针为正。
我们可以用线段树分别维护每个结点val ± pos的最大值,每次走两步蛇形步然后尝试剩下的走大环更新答案。
时间复杂度
其实直接递推一遍就能出来了...
//
// Created by vv123 on 2022/8/1.
//
#include <iostream>
#include <cstdio>
#include <cmath>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 4e5 + 10;
const int inf = 0x3f3f3f3f;
const long double eps = 1e-8;
int n, a[N];
struct nodel {
int mx, pos;
bool operator<(const nodel& x) const {
return mx - pos < x.mx - x.pos;
}
} tl[N << 2];
struct noder {
int mx, pos;
bool operator<(const noder& x) const {
return mx + pos < x.mx + x.pos;
}
} tr[N << 2];
void pushup(int rt) {
tl[rt] = max(tl[rt << 1], tl[rt << 1 | 1]);
tr[rt] = max(tr[rt << 1], tr[rt << 1 | 1]);
}
void build(int l, int r, int rt) {
if (l == r) {
tl[rt] = {a[l], l};
tr[rt] = {a[l], l};
return;
}
int mid = l + r >> 1;
build(l, mid, rt << 1);
build(mid + 1, r, rt << 1 | 1);
pushup(rt);
}
nodel queryl(int a, int b, int l = 1, int r = n, int rt = 1) {
if (a <= l && r <= b) return tl[rt];
if (l > b || r < a) return {-inf, 0};
int mid = l + r >> 1;
nodel ls = queryl(a, b, l, mid, rt << 1);
nodel rs = queryl(a, b, mid + 1, r, rt << 1 | 1);
return max(ls, rs);
}
noder queryr(int a, int b, int l = 1, int r = n, int rt = 1) {
if (a <= l && r <= b) return tr[rt];
if (l > b || r < a) return {-inf, -inf};
int mid = l + r >> 1;
noder ls = queryr(a, b, l, mid, rt << 1);
noder rs = queryr(a, b, mid + 1, r, rt << 1 | 1);
return max(ls, rs);
}
int path[N], ans;
void init() {
int cnt = 0;
for (int k = 1; k <= n / 2; k++) {
if (k % 2 == 1) {
path[++cnt] = k;
path[++cnt] = n + 1 - k;
} else {
path[++cnt] = n + 1 - k;
path[++cnt] = k;
}
}
}
void print(int arr[], int len = n) {
for (int i = 1; i <= len; i++) cout << arr[i] << " ";
cout << "\n";
}
void solve() {
cin >> n;
for (int i = 1; i <= n * 8; i++) {
tl[i] = {-1, -1};
tr[i] = {-1, -1};
}
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) cin >> a[2 * n + 1 - i];
n *= 2;
build(1, n, 1);
init();
nodel t = queryl(2, n);
int ans = t.mx + abs(n - t.pos) + 1;
int tm = 0, pos = 0;
for (int k = 1; k <= n / 2; k++) {
if (k != 1) tm = max(tm + 1, a[path[++pos]] + 1);
else pos++;
tm = max(tm + 1, a[path[++pos]] + 1);
if (k == n / 2) {
ans = min(ans, tm);
continue;
}
if (k % 2 == 1) {
noder t = queryr(k + 1, n - k);
//cout << k << " " << t.mx << " " << t.pos << "\n";
ans = min(ans, max(tm + n - 2 * k, t.mx + abs(k + 1 - t.pos) + 1));
} else {
nodel t = queryl(k + 1, n - k);
//cout << k << " " << t.mx << " " << t.pos << "\n";
ans = min(ans, max(tm + n - 2 * k, t.mx + abs(n - k - t.pos) + 1));
}
}
cout << ans << "\n";
}
signed main() {
int T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
Chip Move (背包,滚动数组)
题意:
你有一个数x,一开始是0,第i次操作可以把x加上(i-1+k)的正倍数,问x从0变成1~n各有多少种路径
题解:
注意理解题意,然后滚动数组优化完全背包就行了
//
// Created by vv123 on 2022/8/5.
//
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
const int mod = 998244353;
int n, k, dp[N][2], ans[N];
void solve() {
cin >> n >> k;
dp[0][0] = 1;
int u = 1, minpos = 0;
for (int i = k; (minpos += i) <= n; i++, u ^= 1) {
for (int j = minpos; j <= n; j++) {
dp[j][u] = (dp[j - i][u ^ 1] + dp[j - i][u]) % mod;
}
for (int j = 1; j <= n; j++) {
ans[j] = (ans[j] + dp[j][u]) % mod;
dp[j][u ^ 1] = 0;
}
}
for (int i = 1; i <= n; i++) {
cout << ans[i] << "\n";
}
}
int main() {
int T = 1;
//cin >> T;
while (T--) solve();
return 0;
}
day11
H - Rikka with A Long Colour Palette (贪心)
题意
有n个区间,k种颜色,依次为每个区间涂色,使得被k种颜色覆盖的区间长度最长。
题解
将2n个点按坐标排序,坐标相同者右端点优先,依次处理。
维护一个队列表示待使用的颜色。
对于一个左端点,如果队列非空就染染上队首的颜色并弹出队首。如果队列为空,将该线段加入一个待染线段的队列。
对于一个右端点,如果它所在的线段还没被染色,说明该线段是多余的,染成1即可,否则将线段颜色加入待染色队列。
每次操作后尝试用待染线段队列中的线段为待染颜色队列染色(有一些左端点可能已经被右端点更新掉了,是无效的)。当待染颜色为空时,每次将该点与上一点的坐标之差统计到答案中。
至于为什么是对的....因为在每一个位置,我们都染上了尽量多的颜色。
//
// Created by vv123 on 2022/8/5.
//
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
const int mod = 998244353;
struct point {
int pos, segid, type;
bool operator<(const point& x) const {
if (pos != x.pos) return pos < x.pos;
return type < x.type;
}
} p[N];
struct Seg {
int l, r, c;
} seg[N];
int n, k;
inline void solve() {
cin >> n >> k;
for (int i = 1; i <= n; i++) {
int l, r;
cin >> l >> r;
p[i * 2 - 1] = {l, i, 1};
p[i * 2] = {r, i, -1};
seg[i] = {l, r, 0};
}
queue<int> need, waiting;
for (int i = 1; i <= k; i++) need.push(i);
sort(p + 1, p + 1 + n * 2);
int len = 0;
for (int i = 1; i <= n * 2; i++) {
if (need.empty()) len += p[i].pos - p[i - 1].pos;
int pos = p[i].pos, id = p[i].segid;
if (p[i].type == 1) {
if (need.empty()) waiting.push(id);
else {
seg[id].c = need.front();
need.pop();
}
} else {
if (seg[id].c) need.push(seg[id].c);
else seg[id].c = 1;
}
while (!need.empty() && !waiting.empty()) {
int u = waiting.front(); waiting.pop();
if (seg[u].c) continue;
seg[u].c = need.front(); need.pop();
}
}
cout << len << "\n";
for (int i = 1; i <= n - 1; i++)
cout << seg[i].c << " ";
cout << seg[n].c << "\n";
}
int main() {
ios::sync_with_stdio(false);cin.tie(0);
int T = 1;
cin >> T;
while (T--) solve();
return 0;
}
Rikka with Illuminations (凸多边形切线,区间贪心)
题意:
凸n边形的外部有m个灯塔,选最少的灯塔使得所有边都被照亮
题解:
求凸多边形的切线,然后特判一下与边重合的情况,然后转化成一个最少区间覆盖一个大区间的问题。对于每一个灯,可以处理出两个相差为n的区间。枚举大区间的起点依次处理,显然一个灯塔不会被算两次,然后就是之前提到过的经典问题了。
赛时因为行末空格wa了,呜呜呜呜呜
//
// Created by vv123 on 2022/7/13.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
using _T=long long; // 全局数据类型,可修改为 long long 等
constexpr _T eps=1e-8;
constexpr long double PI=3.1415926535897932384l;
// 点与向量
template<typename T> struct point
{
T x,y;
bool operator==(const point &a) const {return (abs(x-a.x)<=eps && abs(y-a.y)<=eps);}
bool operator<(const point &a) const {if (abs(x-a.x)<=eps) return y<a.y-eps; return x<a.x-eps;}
bool operator>(const point &a) const {return !(*this<a || *this==a);}
point operator+(const point &a) const {return {x+a.x,y+a.y};}
point operator-(const point &a) const {return {x-a.x,y-a.y};}
point operator-() const {return {-x,-y};}
point operator*(const T k) const {return {k*x,k*y};}
point operator/(const T k) const {return {x/k,y/k};}
T operator*(const point &a) const {return x*a.x+y*a.y;} // 点积
T operator^(const point &a) const {return x*a.y-y*a.x;} // 叉积,注意优先级
int toleft(const point &a) const {const auto t=(*this)^a; return (t>eps)-(t<-eps);} // to-left 测试
T len2() const {return (*this)*(*this);} // 向量长度的平方
T dis2(const point &a) const {return (a-(*this)).len2();} // 两点距离的平方
// 涉及浮点数
long double len() const {return sqrtl(len2());} // 向量长度
long double dis(const point &a) const {return sqrtl(dis2(a));} // 两点距离
long double ang(const point &a) const {return acosl(max(-1.0,min(1.0,((*this)*a)/(len()*a.len()))));} // 向量夹角
point rot(const long double rad) const {return {x*cos(rad)-y*sin(rad),x*sin(rad)+y*cos(rad)};} // 逆时针旋转(给定角度)
point rot(const long double cosr,const long double sinr) const {return {x*cosr-y*sinr,x*sinr+y*cosr};} // 逆时针旋转(给定角度的正弦与余弦)
};
using Point=point<_T>;
// 极角排序
struct argcmp
{
bool operator()(const Point &a,const Point &b) const
{
const auto quad=[](const Point &a)
{
if (a.y<-eps) return 1;
if (a.y>eps) return 4;
if (a.x<-eps) return 5;
if (a.x>eps) return 3;
return 2;
};
const int qa=quad(a),qb=quad(b);
if (qa!=qb) return qa<qb;
const auto t=a^b;
// if (abs(t)<=eps) return a*a<b*b-eps; // 不同长度的向量需要分开
return t>eps;
}
};
// 直线
template<typename T> struct line
{
point<T> p,v; // p 为直线上一点,v 为方向向量
bool operator==(const line &a) const {return v.toleft(a.v)==0 && v.toleft(p-a.p)==0;}
int toleft(const point<T> &a) const {return v.toleft(a-p);} // to-left 测试
bool operator<(const line &a) const // 半平面交算法定义的排序
{
if (abs(v^a.v)<=eps && v*a.v>=-eps) return toleft(a.p)==-1;
return argcmp()(v,a.v);
}
// 涉及浮点数
point<T> inter(const line &a) const {return p+v*((a.v^(p-a.p))/(v^a.v));} // 直线交点
long double dis(const point<T> &a) const {return abs(v^(a-p))/v.len();} // 点到直线距离
point<T> proj(const point<T> &a) const {return p+v*((v*(a-p))/(v*v));} // 点在直线上的投影
};
using Line=line<_T>;
// 线段
template<typename T> struct segment
{
point<T> a,b;
// 判定性函数建议在整数域使用
// 判断点是否在线段上
// -1 点在线段端点 | 0 点不在线段上 | 1 点严格在线段上
int is_on(const point<T> &p) const
{
if (p==a || p==b) return -1;
return (p-a).toleft(p-b)==0 && (p-a)*(p-b)<-eps;
}
// 判断线段直线是否相交
// -1 直线经过线段端点 | 0 线段和直线不相交 | 1 线段和直线严格相交
int is_inter(const line<T> &l) const
{
if (l.toleft(a)==0 || l.toleft(b)==0) return -1;
return l.toleft(a)!=l.toleft(b);
}
// 判断两线段是否相交
// -1 在某一线段端点处相交 | 0 两线段不相交 | 1 两线段严格相交
int is_inter(const segment<T> &s) const
{
if (is_on(s.a) || is_on(s.b) || s.is_on(a) || s.is_on(b)) return -1;
const line<T> l{a,b-a},ls{s.a,s.b-s.a};
return l.toleft(s.a)*l.toleft(s.b)==-1 && ls.toleft(a)*ls.toleft(b)==-1;
}
// 点到线段距离
long double dis(const point<T> &p) const
{
if ((p-a)*(b-a)<-eps || (p-b)*(a-b)<-eps) return min(p.dis(a),p.dis(b));
const line<T> l{a,b-a};
return l.dis(p);
}
// 两线段间距离
long double dis(const segment<T> &s) const
{
if (is_inter(s)) return 0;
return min({dis(s.a),dis(s.b),s.dis(a),s.dis(b)});
}
};
using Segment=segment<_T>;
// 多边形
template<typename T> struct polygon
{
vector<point<T>> p; // 以逆时针顺序存储
size_t nxt(const size_t i) const {return i==p.size()-1?0:i+1;}
size_t pre(const size_t i) const {return i==0?p.size()-1:i-1;}
// 回转数
// 返回值第一项表示点是否在多边形边上
// 对于狭义多边形,回转数为 0 表示点在多边形外,否则点在多边形内
pair<bool,int> winding(const point<T> &a) const
{
int cnt=0;
for (size_t i=0;i<p.size();i++)
{
const point<T> u=p[i],v=p[nxt(i)];
if (abs((a-u)^(a-v))<=eps && (a-u)*(a-v)<=eps) return {true,0};
if (abs(u.y-v.y)<=eps) continue;
const Line uv={u,v-u};
if (u.y<v.y-eps && uv.toleft(a)<=0) continue;
if (u.y>v.y+eps && uv.toleft(a)>=0) continue;
if (u.y<a.y-eps && v.y>=a.y-eps) cnt++;
if (u.y>=a.y-eps && v.y<a.y-eps) cnt--;
}
return {false,cnt};
}
// 多边形面积的两倍
// 可用于判断点的存储顺序是顺时针或逆时针
T area() const
{
T sum=0;
for (size_t i=0;i<p.size();i++) sum+=p[i]^p[nxt(i)];
return sum;
}
// 多边形的周长
long double circ() const
{
long double sum=0;
for (size_t i=0;i<p.size();i++) sum+=p[i].dis(p[nxt(i)]);
return sum;
}
};
using Polygon=polygon<_T>;
//凸多边形
template<typename T> struct convex: polygon<T>
{
// 凸多边形关于某一方向的极点
// 复杂度 O(logn)
// 参考资料:https://codeforces.com/blog/entry/48868
template<typename F> size_t extreme(const F &dir) const
{
const auto &p=this->p;
const auto check=[&](const size_t i){return dir(p[i]).toleft(p[this->nxt(i)]-p[i])>=0;};
const auto dir0=dir(p[0]); const auto check0=check(0);
if (!check0 && check(p.size()-1)) return 0;
const auto cmp=[&](const Point &v)
{
const size_t vi=&v-p.data();
const auto checkv=check(vi);
const auto t=dir0.toleft(v-p[0]);
return checkv^(checkv==check0 && ((!check0 && t<=0) || (check0 && t<0)));
};
return partition_point(p.begin(),p.end(),cmp)-p.begin();
}
// 过凸多边形外一点求凸多边形的切线,返回切点下标
// 复杂度 O(logn)
// 必须保证点在多边形外
pair<size_t,size_t> tangent(const point<T> &a) const
{
const size_t i=extreme([&](const point<T> &u){return u-a;});
const size_t j=extreme([&](const point<T> &u){return a-u;});
return {i,j};
}
// 求平行于给定直线的凸多边形的切线,返回切点下标
// 复杂度 O(logn)
pair<size_t,size_t> tangent(const line<T> &a) const
{
const size_t i=extreme([&](...){return a.v;});
const size_t j=extreme([&](...){return -a.v;});
return {i,j};
}
};
using Convex=convex<_T>;
const int N = 1e4 + 10;
const int inf = 0x3f3f3f3f;
struct seg {
int l, r, id;
bool operator<(const seg& x) const {
return l < x.l;
}
} seg[N];
int n, m;
bool sameline(point<int> a, point<int> b, point<int> c) {
return ((a - b) ^ (c - b)) == 0;
}
inline void solve() {
convex<int> poly;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
int x, y;
cin >> x >> y;
poly.p.push_back({x, y});
}
int cnt = 0;
for (int i = 1; i <= m; i++) {
int x, y;
cin >> x >> y;
point<int> p = {x, y};
auto [l, r] = poly.tangent(p);
point<int> pll = poly.p[poly.pre(l)];
point<int> plr = poly.p[poly.nxt(l)];
point<int> pl = poly.p[l];
point<int> pr = poly.p[r];
point<int> prl = poly.p[poly.pre(r)];
point<int> prr = poly.p[poly.nxt(r)];
if (sameline(p, pl, pll)) {
if (p.dis2(pl) > p.dis2(pll)) l = poly.pre(l);
}
if (sameline(p, pl, plr)) {
if (p.dis2(pl) > p.dis2(plr)) l = poly.nxt(l);
}
if (sameline(p, pr, prl)) {
if (p.dis2(pr) > p.dis2(prl)) r = poly.pre(r);
}
if (sameline(p, pr, prr)) {
if (p.dis2(pr) > p.dis2(prr)) r = poly.nxt(r);
}
//cout << l << " " << r << "\n";
if (l > r) {//[3,0]
r += n;
seg[++cnt].l = l + 1, seg[cnt].r = r, seg[cnt].id = i;
seg[++cnt].l = l + 1 + n, seg[cnt].r = r + n, seg[cnt].id = i;
} else {
seg[++cnt].l = l + 1, seg[cnt].r = r, seg[cnt].id = i;
seg[++cnt].l = l + 1 + n, seg[cnt].r = r + n, seg[cnt].id = i;
}
}
vector<int> ans;
int mincnt = inf;
sort(seg + 1, seg + 1 + cnt);
for (int s = 1; s <= n; s++) {
int t = s + n - 1;
int pos = s, maxr = -inf, id = -1;
vector<int> tans;
for (int i = 1; i <= cnt; i++) {
if (seg[i].l > pos) {
if (id == -1) {
goto loop;
}
tans.push_back(id), pos = maxr + 1;
}
if (seg[i].l <= pos && seg[i].r >= pos && seg[i].r > maxr) {
maxr = seg[i].r, id = seg[i].id;
}
if (pos > t) break;
}
if (pos <= t && maxr >= t) pos = maxr + 1, tans.push_back(id);
if (pos > t && tans.size() < mincnt) {
ans = tans;
mincnt = ans.size();
}
loop:;
}
if (mincnt == inf) {
cout << "-1\n";
return;
} else {
cout << ans.size() << "\n";
for (int i = 0; i < ans.size() - 1; i++) cout << ans[i] << " "; cout << ans.back() << "\n";
}
}
signed main() {
int T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
day12
(数据删除)(博弈论)
Tournament Countdown (交互,思维)
题意:
轮数不超过17的锦标赛,每次可以询问两个人谁的场次多(或一样多),通过不超过次询问确定冠军。
题解:
注意到相邻的四个人可以通过两次询问得到四个人的最大值。
例如ask(1,4),若1=2则答案就是2、3的最大值。
若不相等,询问较大者和另一边没选的那个,二者中的较大者就是四人的最大值。
如果一开始不是,分成两个子问题即可。
代码略。
Sugoroku 3 (期望dp)
题意:
有个点,一开始在号点。
给定一个序列,你在号点掷一个色子,会等概率得到一个的整数,然后移动到号点。
问到达号点掷色子次数的期望。
题解:
设为号点走到号点的期望步数,显然
考虑倒推,易得
解出
使用后缀和优化,时间复杂度为
//
// Created by vv123 on 2022/8/8.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 10;
const int mod = 998244353;
int n, a[N], dp[N], suf[N];
int pow(int a, int b, int p) {
int res = 1;
for (; b; b >>= 1) {
if (b & 1) res = res * a % p;
a = a * a % p;
}
return res;
}
inline void solve() {
cin >> n;
for (int i = 1; i <= n - 1; i++) {
cin >> a[i];
}
suf[n] = dp[n] = 0;
for (int i = n - 1; i >= 1; i--) {
dp[i] = (suf[i + 1] - suf[i + a[i] + 1] + 1 + mod) % mod * pow(a[i], mod - 2, mod) % mod;
dp[i] = (dp[i] + 1) % mod;
suf[i] = (suf[i + 1] + dp[i]) % mod;
}
cout << dp[1] << "\n";
}
signed main() {
int T = 1;
//ios::sync_with_stdio(false);cin.tie(0);
//cin >> T;
while (T--) {
solve();
}
return 0;
}
Tournament (完全二叉树,记忆化搜索)
题意:
题解:
比赛的流程像是一个倒着的完全二叉树。(或者说,正着的现实中的树)
我们从根开始枚举左子树还是右子树赢。
设表示当前访问到结点u,u的胜场数为win的得分。
转移方程为
最后只需要考虑:访问到叶子节点时访问到了哪个状态。
当访问到叶子节点,即时,对应的最终胜者为
考虑u的后n位,如果全0则为1号胜,如果全1则为号胜。容易发现如果将编号整体减一,则最终后n位构成的数字就是胜者的编号。这么说可能有些玄学,考虑那个倒着的完全二叉树就很显然了。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 10;
const int inf = 1e18;
int c[N][20], dp[N][20], n;
int dfs(int u, int win) {
if (u >= (1 << n)) return c[(u ^ (1 << n)) + 1][win];
if (~dp[u][win]) return dp[u][win];
return dp[u][win] = max(dfs(u << 1, 0) + dfs(u << 1 | 1, win + 1), dfs(u << 1, win + 1) + dfs(u << 1 | 1, 0));
}
signed main() {
cin >> n;
for (int i = 1; i <= (1 << n); i++)
for (int j = 1; j <= n; j++)
cin >> c[i][j];
memset(dp, -1, sizeof dp);
cout << dfs(1, 0) << "\n";
return 0;
}
day13
Shinobu Loves Segment Tree (讨论,乱搞)
题意:
void build(int id, int l, int r) {
value[id] + = r − l + 1;
if(l == r) return;
int mid = (r + l)/2;
build(id ∗ 2, l, mid);
build(id ∗ 2 + 1, mid + 1, r);
return;
}
给出n,x,求依次执行build(1,1,1),build(1,1,2),...build(1,1,n)
后value[x]
的值
题解:
设表示第build(1,1,n)对x二进制位的贡献。
观察到结论:当bit[p-1]=1时,否则,定义
终止条件:p=1,return n
设表示考虑对x取二进制位的影响
答案为
显然
当第位为1时,有
因为
只需要讨论一下边上的两个数是否被重复计算
当第位为0时,同理有
以上两种情况当l=1时直接暴力计算1的答案然后l++即可
时间复杂度
//
// Created by vv123 on 2022/8/8.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
void print(int arr[], int n) {
for (int i = 1; i <= n; i++) cout << arr[i] << " ";
cout << "\n";
}
const int N = 2e6 + 10;
int bit[N], maxp;
inline void tobit(int x) {
maxp = 0;
for (; x; x >>= 1) {
bit[++maxp] = x & 1;
}
}
inline int ceil(int x, int y = 2) {
if (x <= 1) return 0;
return x / y + (x % y != 0);
}
inline int f(int n, int p) {
int res = n;
for (int i = p - 1; i >= 1; i--) {
res = bit[i] ? res / 2 : ceil(res, 2);
}
//printf("f(%d,%d)=%d\n", n, p, res);
return res;
}
inline int g(int l, int r, int p) {
//printf("%d %d %d\n", l, r, p);
if (l > r) return 0;
if (p == 1) return (l + r) * (r - l + 1) / 2;
/*
int res = 0;
for (int i = l; i <= r; i++) {
res += f(i, p);
}
return res;
*/
if (l == r) return f(l, p);
if (bit[p - 1]) {
int res = 0;
if (l == 1) res += f(l, p), l++;
res += g(l / 2, r / 2, p - 1) * 2;
if (l % 2 == 1) res -= f(l / 2, p - 1);
if (r % 2 == 0) res -= f(r / 2, p - 1);
return res;
} else {
int res = 0;
if (l == 1) res += f(l, p), l++;
res += g((l + 1) / 2, (r + 1) / 2, p - 1) * 2;
if (l % 2 == 0) res -= f((l + 1) / 2, p - 1);
if (r % 2 == 1) res -= f((r + 1) / 2, p - 1);
return res;
}
}
inline void solve() {
int n, x;
cin >> n >> x;
tobit(x);
cout << g(1, n, maxp) << "\n";
}
signed main() {
int T = 1;
ios::sync_with_stdio(false);cin.tie(0);
cin >> T;
while (T--) {
solve();
}
return 0;
}
Shinobu lovest trip (数论)
题意:
题解:
//
// Created by vv123 on 2022/8/8.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
void print(int arr[], int n) {
for (int i = 1; i <= n; i++) cout << arr[i] << " ";
cout << "\n";
}
int pow(int a, int b, int p) {
int res = 1;
for (; b; b >>= 1) {
if (b & 1) res = res * a % p;
a = a * a % p;
}
return res;
}
const int N = 2e6 + 10;
int p, n, q, a[N], s[N], d[N];
unordered_map<int, int> ax;
inline void solve() {
cin >> p >> a[1] >> n >> q;
ax.clear(); ax[a[1]] = 1;
for (int i = 2; i <= 200000; i++) {
a[i] = a[i - 1] * a[1] % p;
if (!ax[a[i]]) ax[a[i]] = i;
}
for (int i = 1; i <= n; i++) {
cin >> s[i] >> d[i];
}
while (q--) {
int x, res = 0, ak;
cin >> x;
for (int i = 1; i <= n; i++) {
if (x == s[i]) { res++; continue; }
ak = x * pow(s[i], p - 2, p) % p;
if (ax[ak] > 0 && ax[ak] <= d[i]) res++;
}
cout << res << "\n";
}
}
signed main() {
int T = 1;
//ios::sync_with_stdio(false);cin.tie(0);
cin >> T;
while (T--) {
solve();
}
return 0;
}
day14
Good Coloring(图论,染色,无向图转DAG)
题意:
n个点m条边,每个点有一个颜色,颜色有k种。
你要为这个图重新染色,颜色种数x不超过k,且存在一条长度为x的路径包含每一种颜色,输出染色方案和那条路径。
题解:
来自HappyCodingLife's solution
我们把给出的m条边改成单向边:从颜色小的向颜色大的连边。这样就得到了一个有向无环图。显然图中的最长链长度不会超过k,然后求一下每个点开始的最长路len,以len作为该点的颜色即可。
//
// Created by vv123 on 2022/8/9.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
void print(int arr[], int n) {
for (int i = 1; i <= n; i++) cout << arr[i] << " ";
cout << "\n";
}
const int N = 6e5 + 10;
int n, m, k, c[N], f[N], vis[N], ans[N];
vector<int> g[N];
void dfs1(int u) {
vis[u] = f[u] = 1;
for (auto v : g[u]) {
if (!vis[v]) dfs1(v);
f[u] = max(f[u], f[v] + 1);
}
}
int cnt;
void dfs2(int u) {
ans[++cnt] = u;
for (auto v : g[u]) {
if (f[u] == f[v] + 1) {
dfs2(v);
break;
}
}
}
inline void solve() {
cin >> n >> m >> k;
memset(f, 0, sizeof(int) * (n + 1));
memset(vis, 0, sizeof(int) * (n + 1));
memset(ans, 0, sizeof(int) * (n + 1));
for (int i = 1; i <= n; i++) {
cin >> c[i];
g[i].clear();
}
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
if (c[u] > c[v]) swap(u, v);
g[u].push_back(v);
}
int x = 0, st = 0;
for (int i = 1; i <= n; i++) {
if (!vis[i]) dfs1(i);
if (f[i] > x) {
x = f[i];
st = i;
}
}
cnt = 0; dfs2(st);
cout << x << " "; print(f, n); print(ans, x);
}
signed main() {
int T = 1;
ios::sync_with_stdio(false);cin.tie(0);
cin >> T;
while (T--) {
solve();
}
return 0;
}
day15
Count(数论,欧拉函数)
题意:
求
题解:
赛时猜想,但是发现不满足便没有继续想下去
事实上,记
当为偶数时显然,因为对应的数本来就都是奇数
当为奇数时,因为对于任意的,有,构成的每一个奇数都有一个偶数与它唯一对应。
答案即为函数的前项和。
//
// Created by vv123 on 2022/7/8.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e7 + 10;
int n, cnt;
int vis[N], prime[N], phi[N], sum[N];
inline void Shai(int n = N - 1) {
vis[1] = phi[1] = 1;
cnt = 0;
for (int i = 2; i <= n; i++) {
if (!vis[i]) {
prime[++cnt] = i;
phi[i] = i - 1;
}
for (int j = 1; j <= cnt && i * prime[j] <= n; j++) {
vis[i * prime[j]] = 1;
if (i % prime[j] == 0) {
phi[i * prime[j]] = prime[j] * phi[i];
break;
} else {
phi[i * prime[j]] = phi[prime[j]] * phi[i];
}
}
}
for (int i = 1; i <= n; i++) {
if (i % 2 == 0) sum[i] = sum[i - 1] + phi[i];
else sum[i] = sum[i - 1] + phi[i] / 2;
}
}
inline void solve() {
cin >> n;
cout << sum[n] << "\n";
}
signed main() {
ios::sync_with_stdio(false); cin.tie(0);
Shai();
int T = 1;
cin >> T;
while (T--) {
solve();
}
return 0;
}
CSGO(枚举,曼哈顿距离的最大值)
题意:
有两类武器(主武器和副武器),每类有若干把,每把武器都有一个基础属性S,以及k个附加属性,让你选一把主武器MW和一把副武器SW,使得 最大,输出该最大值
T<=100, n<=100000, m<=100000, K<=5, 0<=S<=1e9, |x[i]|<=1e9, sum of (n+m)<=300000
题解:
考虑去掉绝对值后每个的系数,每一个维度可能有主+副-和主-副+两种情况,总共有种可能,对于每一种可能,都可以可以求出一个最佳组合。
最佳组合一定包含在上述情况中,因为最佳组合每个的系数一定是那种的其中一个。
相似题目:2022暑假前集训动态规划 B-下棋
使用到曼哈顿距离变换:
本题其实就是考虑最多五个维度的情况下暴力枚举系数求得曼哈顿距离的最大值。
//
// Created by vv123 on 2022/7/8.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e6 + 10;
const int inf = 1e18;
int n, m, k, a[N][6], b[N][6];
int cofa[6], cofb[6];
inline void setcof(int st) {
for (int i = 0; i < k; i++) {
if (st >> i & 1) cofa[i + 1] = 1, cofb[i + 1] = -1;
else cofa[i + 1] = -1, cofb[i + 1] = 1;
}
}
inline void solve() {
cin >> n >> m >> k;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= k; j++)
cin >> a[i][j];
}
for (int i = 1; i <= m; i++) {
for (int j = 0; j <= k; j++)
cin >> b[i][j];
}
cofa[0] = cofb[0] = 1;
int ans = -inf;
for (int st = 0; st <= (1ll << k) - 1; st++) {
setcof(st);
int maxA = -inf, maxB = -inf;
for (int i = 1; i <= n; i++) {
int res = 0;
for (int j = 0; j <= k; j++) res += cofa[j] * a[i][j];
maxA = max(maxA, res);
}
for (int i = 1; i <= m; i++) {
int res = 0;
for (int j = 0; j <= k; j++) res += cofb[j] * b[i][j];
maxB = max(maxB, res);
}
ans = max(ans, maxA + maxB);
}
cout << ans << "\n";
}
signed main() {
ios::sync_with_stdio(false); cin.tie(0);
int T = 1;
cin >> T;
while (T--) {
solve();
}
return 0;
}
Videos(最小费用最大流)
gugugu
day16
Crystalfly(树形DP)
题意:
0时刻你位于树根(1号点),每个时刻可以选择不动或者前往相邻的点。每个点两个属性,如果一个点的相邻结点被访问,则秒后变为0。首次访问一个点可以获得的分值,问最大得分。
题解:
设以u为根的子树的答案为
走进u后立刻返回,最终以u为根的子树的答案”为
那么显然有
只考虑的子结点,显然
如果,则可以先去别的子结点再回头访问
假设先去了结点,则对的贡献不再是里面的,而应修改为
因此需要用来更新
考虑记录所有的最大值和次大值,如果当前结点的这个值等于最大值就用次大值来更新。注意最大值和次大值可能是相等的。
特别的,叶子节点的
时间复杂度
//
// Created by vv123 on 2022/8/9.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N= 5e5 + 10;
const int inf = 0x3f3f3f3f;
int n, a[N], t[N], f[N], g[N];
vector<int> G[N];
void dfs(int u, int fa) {
g[u] = a[u];
int max1 = 0, max2 = 0;
for (auto v : G[u]) {
if (v == fa) continue;
dfs(v, u);
g[u] += f[v] - a[v];
int val = g[v] - (f[v] - a[v]);
if (val > max1) {
max2 = max1;
max1 = val;
} else if (val > max2) {
max2 = val;
}
}
f[u] = g[u];
for (auto v : G[u]) {
if (v == fa) continue;
f[u] = max(f[u], g[u] + a[v]);
if (t[v] == 3) {
if (g[v] - (f[v] - a[v]) == max1) f[u] = max(f[u], g[u] + a[v] + max2);
else f[u] = max(f[u], g[u] + a[v] + max1);
}
}
}
inline void solve() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i], G[i].clear();
for (int i = 1; i <= n; i++) cin >> t[i];
if (n == 1) {
cout << a[1] << "\n";
return;
}
for (int i = 1; i <= n - 1; i++) {
int u, v;
cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
dfs(1, 0);
cout << f[1] << "\n";
}
signed main() {
int T = 1;
ios::sync_with_stdio(false);cin.tie(0);
cin >> T;
while (T--) {
solve();
}
return 0;
}
Klee in Solitary Confinement(前缀和)
题意:
有一个长度为n序列a,你可以选择一个区间把所有数加k,或者什么也不做。求众数出现次数的最大值。
题解:
通过加上一个offset使得所有数都是正数。
考虑将插入到第和第个vector中
最终每个vector种只有两个数x和x+k。记录它们的前缀出现次数cnt。对于这个vector,需要找到一个区间,使得
最大。前面关于的部分可以预处理出最大值,枚举的过程中更新答案即可。
时间复杂度是的。
//
// Created by vv123 on 2022/8/9.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 4e6 + 10;
const int inf = 0x3f3f3f3f;
const int offset = 2e6 + 2;
int n, k, a[N], cnt[N];
vector<int> v[N];
inline void solve() {
cin >> n >> k;
int ans = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i]; a[i] += offset;
cnt[a[i]]++;
ans = max(ans, cnt[a[i]]);
v[a[i]].push_back(a[i]); v[a[i] + k].push_back(a[i]);
}
if (k == 0) {
cout << ans << "\n";
return;
}
for (int i = k; i <= N - 1; i++) {
if (!cnt[i]) continue;
int y = i, prey = 0, maxpre = 1, tot = cnt[y];
int res = 0;
for (int j = 0; j < v[i].size(); j++) {
if (v[i][j] == y) prey++;
res = max(res, -2 * prey + j + tot + maxpre);
maxpre = max(maxpre, 2 * prey - j);
}
ans = max(ans, res);
}
cout << ans << "\n";
}
signed main() {
int T = 1;
ios::sync_with_stdio(false);cin.tie(0);
//cin >> T;
while (T--) {
solve();
}
return 0;
}
Paimon Sorting(找规律,树状数组)
题意:
定义派蒙排序
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (a[i] < a[j]) swap(a[i], a[j]);
执行后会得到一个单调增的序列,不信你试试
给定序列A,问它的每个前缀排序时的swap次数。
题解:
考虑末尾插入一个数x对swap次数的影响。
设前边i-1个数的最大值为m
若x=m,swap次数不变
若x<m,只在最后一轮产生新的swap,次数为前边比x大的数的个数(去重),可以使用树状数组维护
若x>m,
由找规律可知,如果之前最大的数只出现了一次,则x的贡献为2。
否则设最大数第二次出现的位置为pos,则贡献为i-pos+2。
//
// Created by vv123 on 2022/8/9.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 4e6 + 10;
const int inf = 0x3f3f3f3f;
const int offset = 2e6 + 2;
int n, k, a[N], ans[N];
vector<int> v[N];
int t[N];
inline void add(int i, int x) {
for (; i <= n; i += i & -i)
t[i] += x;
}
inline int sum(int i) {
int res = 0;
for(; i; i -= i & -i) {
res += t[i];
}
return res;
}
inline void solve() {
cin >> n;
memset(t, 0, sizeof(int) * (n + 1));
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
ans[1] = 0;
int mx = a[1], mxpos = -1;
add(a[1], 1);
for (int i = 2; i <= n; i++) {
if (a[i] == mx) {
if (mxpos == -1) mxpos = i;
ans[i] = ans[i - 1];
} else if (a[i] < mx) {
ans[i] = ans[i - 1] + sum(n) - sum(a[i]);
} else {
if (mxpos == -1) ans[i] = ans[i - 1] + 2;
else ans[i] = ans[i - 1] + i - mxpos + 2;
mx = a[i]; mxpos = -1;
}
if (sum(a[i]) - sum(a[i] - 1) == 0) add(a[i], 1);
}
for (int i = 1; i <= n - 1; i++) {
cout << ans[i] << " ";
}
cout << ans[n] << "\n";
}
signed main() {
int T = 1;
ios::sync_with_stdio(false);cin.tie(0);
cin >> T;
while (T--) {
solve();
}
return 0;
}
day17
这天去帮室友搬显示器了,然后去看了永雏塔菲的生日会。
然后失眠去看英仙座流星雨,然后不知道何时睡着的。
Eva and Euro coin (字符串,思维)
题意:
给出两个只包含01的字符串,每次可以选择连续k个相同的数字进行翻转,问能否通过若干次操作把两个字符串变为相同。
题解:
观察一下样例:000000 101101 k=2
我们尝试把t变成s
101101
100001
111001
001001
001111
000000
你会发现其实相等于把11消掉,然后把00消掉,然后把11消掉
我们将一个字符串的本质定义为消到最后的状态
只要它们本质相同就一定是可以转化的
用栈的思想模拟一下就好了
赛时好像理解错题意了,不然看懂样例一之后应该是可以做出来的。
//
// Created by vv123 on 2022/8/14.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 10;
int n, k;
string s, t;
vector<pair<char,int> > work(string s) {
vector<pair<char,int> > v;
v.emplace_back(s[0], 1);
if (v.back().second == k) v.pop_back();
for (int i = 1; i < s.length(); i++) {
if (!v.empty() && s[i] == v.back().first) v.back().second++;
else v.emplace_back(s[i], 1);
if (v.back().second == k) v.pop_back();
}
return v;
}
inline void solve() {
cin >> n >> k >> s >> t;
work(s) == work(t) ? puts("Yes") : puts("No");
}
signed main() {
int T = 1;
//ios::sync_with_stdio(false);cin.tie(0);
//cin >> T;
while (T--) {
solve();
}
return 0;
}
day18
Colorful Tree(树上启发式合并)
咕咕咕
day19
Rikka with Number (高精度,数位dp,康托展开)
题意:
一个数如果在d进制下的数字是0~d-1的排列,则称它为d进制下的好数。
一个数是好数,如果它是任意一个进制下的好数。
给出以内的十进制正数L,R,问有多少好数。
题解:
考虑通过前缀和相减求出区间内的答案。
如果一个数是好数,它只会是一种进制下的好数。因为d进制好数的取值范围是,显然不同d进制好数的值域互不相交。
这样我们可以通过对数估计得出每个进制在十进制下的位数范围。如果n大于d的最大位数,显然[1,n]可以包含所有d进制好数(d!-(d-1)!)个
设上述最大的d为dmax,只需再往后枚举几个数统计好数个数。
求从1023...(d)到n之间有多少个好数,可以把n转化成d+1进制数,然后在数位dp的过程中利用康托展开计算排列数即可。
//
// Created by vv123 on 2022/8/15.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e4 + 10;
const int MAXN = 9999;
const int DLEN = 4;
const int mod = 998244353;
char L[N], R[N];
int f[N], mn[N], mx[N];
void init() {
f[0] = 1;
for (int i = 1; i < N; i++) f[i] = f[i - 1] * i % mod;
}
class Big {
public:
int a[N], len;
Big(const int b = 0) {
int c, d = b;
len = 0;
memset(a, 0, sizeof(a));
while (d > MAXN) {
c = d - (d / (MAXN + 1)) * (MAXN + 1);
d = d / (MAXN + 1);
a[len++] = c;
}
a[len++] = d;
}
Big(const char *s) {
int t, k, index, L, i;
memset(a, 0, sizeof(a));
L = strlen(s);
len = L / DLEN;
if (L % DLEN) len++;
index = 0;
for (i = L - 1; i >= 0; i -= DLEN) {
t = 0;
k = i - DLEN + 1;
if (k < 0) k = 0;
for (int j = k; j <= i; j++) t = t * 10 + s[j] - '0';
a[index++] = t;
}
}
bool operator>(const Big &T)const {
int ln;
if (len > T.len) return true;
else if (len == T.len) {
ln = len - 1;
while (a[ln] == T.a[ln] && ln >= 0) ln--;
if (ln >= 0 && a[ln] > T.a[ln]) return true;
else return false;
} else return false;
}
bool operator==(const Big &T)const {
int ln;
if (len == T.len) {
ln = len - 1;
while (a[ln] == T.a[ln] && ln >= 0) ln--;
return ln < 0;
} else return false;
}
Big operator-(const Big &T)const {
int i, j, big;
bool flag;
Big t1, t2;
if (*this > T) {
t1 = *this;
t2 = T;
flag = 0;
} else {
t1 = T; t2 = *this; flag = 1;
}
big = t1.len;
for (i = 0; i < big; i++) {
if (t1.a[i] < t2.a[i]) {
j = i + 1;
while (t1.a[j] == 0) j++;
t1.a[j--]--;
while (j > i) t1.a[j--] += MAXN;
t1.a[i] += MAXN + 1 - t2.a[i];
} else t1.a[i] -= t2.a[i];
}
t1.len = big;
while (t1.a[t1.len - 1] == 0 && t1.len > 1) {
t1.len--;
big--;
}
if (flag) t1.a[big - 1] = 0 - t1.a[big - 1];
return t1;
}
int operator%(const int &b)const {
int i, d = 0;
for (int i = len - 1; i >= 0; i--) d = ((d * (MAXN + 1)) % b + a[i]) % b;
return d;
}
Big operator/(const int &b)const {
Big ret;
int i, down = 0;
for (int i = len - 1; i >= 0; i--) {
ret.a[i] = (a[i] + down * (MAXN + 1)) / b;
down = a[i] + down * (MAXN + 1) - ret.a[i] * b;
}
ret.len = len;
while (ret.a[ret.len - 1] == 0 && ret.len > 1) ret.len--;
return ret;
}
void print() {
printf("%d", a[len - 1]);
for (int i = len - 2; i >= 0; i--) printf("%d", a[i]);
}
int change(int m,int *s){ ///将大数转化成m进制,保存在字符串s 范围从1到len,并返回长度
int B[N];
int Le=0; memcpy(B,a,sizeof a);
int tmp = len - 1;
while (tmp >= 0){
int x=0;
for (int i = tmp;i >= 0;i--){
int pre=x; x=(x*(MAXN+1)+B[i])%m; B[i]=(pre*(MAXN+1)+B[i])/m;
}
s[++Le]=x; while(tmp >= 0&&B[tmp]==0) tmp--;
}
return Le;
}
};
int digit[N];
int vis[N];
int dfs(int pos, int rbound, int leading) {
if (pos == 0) return 1;
if (!rbound) return f[pos];
int res = 0, l = leading, r = digit[pos];
for (int i = l; i <= r; i++) {
if (!vis[i]) {
vis[i] = 1;
res = (res + dfs(pos - 1, i == r && rbound, 0));
vis[i] = 0;
}
}
return res;
}
int solve(Big n) {
if (n == 0ll) return 0;
int len = n.change(10, digit), res = 0;
int d;
for (d = 2; ; d++) {
if ((int) ((d + 1) * log10(d + 1)) + 1 > len) break;
res = (res + (d - 1) * f[d - 1]) % mod;
}
while (1) {
len = n.change(d, digit);
if (len < d) break;
if (len > d) res = (res + (d - 1) * f[d - 1]) % mod;
else {
memset(vis, 0, sizeof vis);
res = (res + dfs(len, 1, 1)) % mod;
}
d++;
}
return res;
}
signed main() {
int T = 1;
//ios::sync_with_stdio(false);cin.tie(0);
cin >> T;
init();
while (T--) {
scanf("%s%s", L, R);
Big l = L, r = R;
printf("%lld\n", (solve(r) - solve(l - 1) + mod) % mod);
}
return 0;
}
day20
Less Time, More profit(网络流,最大权闭合图)
题意:
比赛的时候,这题提问的人还是蛮多的,所以我好好讲一下该题的题意
有n个工厂,m个商店,建设第i个工厂需要花费payi的钱和ti的时间,多个工厂可以同时建设,即花费时间取最大的ti
第i个商店在指定的k个工厂都建完之后可以一次性获得proi的利润
也就是说k个工厂任何一个没有建完的情况下,该商店都不会获得利润
问最少需要多少时间,获得的总利润不少于L
在满足最少时间的情况下,求最大化的利润
题解:
二分时间t,确定可选工厂的集合,然后只需求出最大权闭合子图。
注意:工厂是负权点,它向T的连边边权是点权的相反数,即正数。故上图有误。
这是一个经典问题,以下内容来自OI-WIKI
最大权值闭合图,即给定一张有向图,每个点都有一个权值(可以为正或负或 ),你需要选择一个权值和最大的子图,使得子图中每个点的后继都在子图中。
做法:建立超级源点 和超级汇点 ,若节点 权值为正,则 向 连一条有向边,边权即为该点点权;若节点 权值为负,则由 向 连一条有向边,边权即为该点点权的相反数。原图上所有边权改为 。跑网络最大流,将所有正权值之和减去最大流,即为答案。
几个小结论来证明:
- 每一个符合条件的子图都对应流量网络中的一个割。因为每一个割将网络分为两部分,与 相连的那部分满足没有边指向另一部分,于是满足上述条件。这个命题是充要的。
- 最小割所去除的边必须与 和 其中一者相连。因为否则边权是 ,不可能成为最小割。
- 我们所选择的那部分子图,权值和 所有正权值之和 我们割去的正权值点的权值之和 我们割去的负权值点的权值绝对值之和。当我们不选择一个正权值点时,其与 的连边会被断开;当我们选择一个负权值点时,其与 的连边会被断开。断开的边的边权之和即为割的容量。于是上述式子转化为:权值和 所有正权值之和 割的容量。
- 于是得出结论,最大权值和 所有正权值之和 最小割 所有正权值之和 最大流。
简单地说,我们需要选出若干商店和工厂,点权商店为正工厂为负,满足它们构成一个闭合图,且点权和=正权和-(未选的正权和-选中的负权绝对值和)最大。因为每种闭合子图都与一个割相对应,而括号内的内容正是割的容量,因此最大点权和=所有正权和-最小割。
记录一下调试的坑点:如果在结构体里放vector,容易忘了清空。。
//
// Created by vv123 on 2022/8/16.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 4e4 + 10;
const int INF = 1e18;
struct Edge {
int from, to, cap, flow;
Edge(int u, int v, int c, int f) : from(u), to(v), cap(c), flow(f) {}
};
struct Dinic {
int n, m, s, t;
vector<Edge> edges;
vector<int> G[N];
int d[N], cur[N];
bool vis[N];
void init(int n = N - 1) {
for (int i = 0; i <= n; i++) G[i].clear();
edges.clear();
}
void AddEdge(int from, int to, int cap) {
edges.push_back(Edge(from, to, cap, 0));
edges.push_back(Edge(to, from, 0, 0));
m = edges.size();
G[from].push_back(m - 2);
G[to].push_back(m - 1);
}
bool BFS() {
memset(vis, 0, sizeof(vis));
queue<int> Q;
Q.push(s);
d[s] = 0;
vis[s] = 1;
while (!Q.empty()) {
int x = Q.front();
Q.pop();
for (int i = 0; i < G[x].size(); i++) {
Edge& e = edges[G[x][i]];
if (!vis[e.to] && e.cap > e.flow) {
vis[e.to] = 1;
d[e.to] = d[x] + 1;
Q.push(e.to);
}
}
}
return vis[t];
}
int DFS(int x, int a) {
if (x == t || a == 0) return a;
int flow = 0, f;
for (int& i = cur[x]; i < G[x].size(); i++) {
Edge& e = edges[G[x][i]];
if (d[x] + 1 == d[e.to] && (f = DFS(e.to, min(a, e.cap - e.flow))) > 0) {
e.flow += f;
edges[G[x][i] ^ 1].flow -= f;
flow += f;
a -= f;
if (a == 0) break;
}
}
return flow;
}
int Maxflow(int s, int t) {
this->s = s;
this->t = t;
int flow = 0;
while (BFS()) {
memset(cur, 0, sizeof(cur));
flow += DFS(s, INF);
}
return flow;
}
} dc;
int n, m, L;
struct node {
int t, val;
vector<int> to;
bool operator<(const node& x) const {
return t < x.t;
}
} a[N];
int vis[N];
int check(int maxi) {
memset(vis, 0, sizeof vis);
dc.init();
int S = n + m + 1, T = n + m + 2;
int sum = 0;
for (int i = n + 1; i <= maxi; i++) {
//if (a[i].t > maxt) continue;
sum += a[i].val;
dc.AddEdge(S, i, a[i].val);
for (auto v : a[i].to) {
dc.AddEdge(i, v, INF);
if (!vis[v]) vis[v] = 1, dc.AddEdge(v, T, a[v].val);
}
}
int maxflow = dc.Maxflow(S, T);
//cout << maxflow << "\n";
return sum - maxflow;
}
inline void solve() {
cin >> n >> m >> L;
for (int i = 1; i <= n; i++) {
cin >> a[i].val >> a[i].t;
}
for (int i = n + 1; i <= n + m; i++) {
cin >> a[i].val; a[i].t = 0;
int k, v; cin >> k;
a[i].to.clear();
for (int j = 1; j <= k; j++) {
cin >> v;
a[i].t = max(a[i].t, a[v].t);
a[i].to.push_back(v);
}
}
sort(a + n + 1, a + n + m + 1);
int ans = -1, i, anst = -1;
for (i = n + 1; i <= n + m; i++) {
if (anst != -1 && a[i].t > anst) break;
ans = check(i);
if (ans >= L) anst = a[i].t;
}
if (anst == -1) printf("impossible\n");
else printf("%lld %lld\n", anst, ans);
}
signed main() {
int T = 1;
cin >> T;
for (int Case = 1; Case <= T; Case++) {
printf("Case #%d: ", Case);
solve();
}
return 0;
}
Intersection is not allowed! (容斥原理,行列式的重排定义)
题意:
给出n∗n网格,顶部有k个起点,底部有k个相对应的终点
每次只能向下或向右走
求有多少种从各个起点出发到达对应终点且路径不相交的路径?(对109+7取模)
题解:
n*m空间从左上角走到右下角只走右或者下的方案数位C(n,n+m)
首先考虑两个棋子的情况,即一个棋子从a1到b1,另一个棋子从a2到b2,不考虑交叉方案数显然是C(b1-a1+n-1,n-1) * C(b2-a2+n-1,n-1)
对于路径交叉的方案,对于一个a1->b2,a2->b1的方案,这两条路径必然会相交,
把最后一个交叉点之后的两条路径交换就又对应一个a1->b1,a2->b2的路径交叉的方案
故我们建立了a1->b1,a2->b2交叉路径与a1->b2,a2->b1的方案的一一对应,那么不合法方案数就是C(b2-a1+n-1,n-1) * C(b1-a2+n-1,n-1)
多个棋子的情况,考虑容斥,如果两个路径相交,相当于交换了终点
枚举每个起点的终点,乘上一个容斥系数(−1)t,t表示相交的路径个数,(也可以看做选择的终点序列的逆序对个数)
按照每个起点选择每一个终点构造出一个组合数的矩阵,按照矩阵的的定义答案就是这个矩阵的行列式
以上内容转载自 Intersection is not allowed!——行列式 - qinuna - 博客园 (cnblogs.com)
懒得写了,贴一个高斯消元求行列式的板子吧(OI-WIKI)
const double EPS = 1E-9;
int n;
vector<vector<double> > a(n, vector<double>(n));
double det = 1;
for (int i = 0; i < n; ++i) {
int k = i;
for (int j = i + 1; j < n; ++j)
if (abs(a[j][i]) > abs(a[k][i])) k = j;
if (abs(a[k][i]) < EPS) {
det = 0;
break;
}
swap(a[i], a[k]);
if (i != k) det = -det;
det *= a[i][i];
for (int j = i + 1; j < n; ++j) a[i][j] /= a[i][i];
for (int j = 0; j < n; ++j)
if (j != i && abs(a[j][i]) > EPS)
for (int k = i + 1; k < n; ++k) a[j][k] -= a[i][k] * a[j][i];
}
cout << det;
day21
Subtree Minimum Query (可持久化线段树)
题意:
q次询问以x为根的子树中,与x深度之差不大于k的点的点权最小值,强制在线。
题解:
考虑将“深度”作为主席树的版本(时间)轴,以dfs序建立线段树。
我们在版本中查询区间的最小值即可。不需要考虑版本,因为它不存在区间。
//
// Created by vv123 on 2022/8/17.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 10;
const int INF = INT64_MAX / 10;
struct hjtTree {
int l, r, mn;
} tr[N << 5];
struct node {
int id, val, dep, dfn, siz;
} a[N];
int n, q, root, rt[N];
vector<int> g[N];
int idx = 0;
void dfs(int u, int fa) {
a[u].dep = a[fa].dep + 1;
a[u].dfn = ++idx;
a[u].siz = 1;
for (auto v : g[u]) {
if (v == fa) continue;
dfs(v, u);
a[u].siz += a[v].siz;
}
}
#define mid (l+r>>1)
#define ain (l>=s && r<=t)
#define lin (s<=mid)
#define rin (t>=mid+1)
void build(int &u, int pre, int l, int r, int pos, int val) {
u = ++idx;
tr[u] = tr[pre];
if (l == pos && r == pos) {
tr[u].mn = val;
return;
}
if (pos <= mid) build(tr[u].l, tr[pre].l, l, mid, pos, val);
else build(tr[u].r, tr[pre].r, mid + 1, r, pos, val);
tr[u].mn = min(tr[tr[u].l].mn, tr[tr[u].r].mn);
}
int query(int u, int l, int r, int s, int t) {
if (ain) return tr[u].mn;
int res = INF;
if (lin) res = min(res, query(tr[u].l, l, mid, s, t));
if (rin) res = min(res, query(tr[u].r, mid + 1, r, s, t));
return res;
}
signed main() {
ios::sync_with_stdio(false); cin.tie(0);
tr[0].mn = INF;
cin >> n >> root;
for (int i = 1; i <= n; i++) {
cin >> a[i].val;
a[i].id = i;
}
for (int i = 1; i <= n - 1; i++) {
int u, v; cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs(root, 0);
sort(a + 1, a + 1 + n, [](node x, node y) { return x.dep < y.dep; });
idx = 0;
for (int i = 1; i <= n; i++) {
build(rt[a[i].dep], rt[a[i-1].dep], 1, n, a[i].dfn, a[i].val);
}
int maxdep = a[n].dep;
sort(a + 1, a + 1 + n, [](node x, node y) { return x.id < y.id; });
cin >> q;
int ans = 0;
while (q--) {
int x, k; cin >> x >> k;
x = (x + ans) % n + 1, k = (k + ans) % n;
ans = query(rt[min(a[x].dep + k, maxdep)], 1, n, a[x].dfn, a[x].dfn + a[x].siz - 1);
cout << ans << "\n";
}
return 0;
}
day22
String (Z Algorithm(exkmp))
题意:
题解:
求出每一个位置的z函数(以i开头的后缀与s的前缀的最长公共前缀)
考虑每一个位置为重叠部分的起点,然后需要一个"区间加,但是每k个数加一个"的操作。事实上需要区间加的数都是模k同余的,可以把它们放在一个vector里,然后差分就可以了。
求完exkmp最好改成下标从1开始的,不然写起来非常难受。。
#pragma GCC optimize("Ofast")
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("inline")
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e6 + 10;
const int inf = 0x3f3f3f3f;
const int mod = 998244353;
vector<int> v[N];
vector<int> z_function(string s) {
int n = (int)s.length();
vector<int> z(n);
for (int i = 1, l = 0, r = 0; i < n; ++i) {
if (i <= r && z[i - l] < r - i + 1) {
z[i] = z[i - l];
} else {
z[i] = max(0ll, r - i + 1);
while (i + z[i] < n && s[z[i]] == s[i + z[i]]) ++z[i];
}
if (i + z[i] - 1 > r) l = i, r = i + z[i] - 1;
}
return z;
}
signed main() {
ios::sync_with_stdio(false); cin.tie(0);
int T; cin >> T;
while (T--) {
string s;
int n, k;
cin >> s >> k; n = s.length();
vector<int> z = z_function(s);
//for (int i = 0; i <= n - 1; i++) cout << z[i] << " "; cout << "\n";
for (int i = 0; i < k; i++) {
vector<int>(0).swap(v[i]);
v[i].resize(n / k + 5);
for (int j = 0; j < v[i].size(); j++) v[i][j] = 0;
v[i][0] = 1;
}
for (int i = 0; i <= n - 1; i++) {
if ((i + 1) % k == 0) {
v[i % k][i / k]++; v[i % k][i / k + 1]--;
}
}
for (int i = 1; i <= n - 1; i++) {
if (z[i] < i + 1 || z[i] - i < k) continue;
int pos = (i * 2 + k - 1) % k;
int num = (z[i] - i) / k;
int l = (i * 2 + k - 1) / k, r = l + num - 1;
v[pos][l]++; v[pos][r + 1]--;
}
int ans = 1;
for (int i = 0; i < k; i++) {
//cout << i << ":";
int sum = 0, val = 0;
for (int j = 0; ; j++) {
if (i + j * k > n - 1) break;
sum = (sum + v[i][j]) % mod;
ans = ans * sum % mod;
//cout << sum << " ";
}
//cout << "\n";
}
cout << ans << "\n";
}
return 0;
}
Once in a casino (贪心)
题意:
给出数字串s,t,每次操作可以将s中相邻两个数同时加一或减一,必须保持操作后每个数字在0-9内(且第1个数不为0)。问最少几次可以把s变成t并输出过程(前10^5次),或判定无解。
题解:
一开始的想法从左到右是随便贪心一下。
后来发现这样可能会使得后面的数变成负数。
于是很自然地想先把后一个数加够再减,又担心不是最优解。
赛后发现上述贪心竟然是对的...
假设s1比t1大delta,且s2小于delta、大于t2,我们就需要先将s2 += delta - s2,再进行s2 -= delta += t2(期间顺便处理完s1),总的操作次数为2delta + t2 - s2
如果直接贪心的话,答案是delta + t2 - (s2 - delta) = 2delta + t2 - s2....
同理如果你想避免>9,最后发现其实也不会增加操作次数。所以可以将所有操作视为“没有限制”,如果有解则限制下也有解,而且是最优的。
所以先无脑贪心求出答案,然后正经贪心dfs一下就好了
#pragma GCC optimize("Ofast")
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("inline")
#include <bits/stdc++.h>
#define int long long
#define sgn(x) (x>0?1:-1)
using namespace std;
const int N = 2e6 + 10;
const int inf = 0x3f3f3f3f;
const int mod = 998244353;
int n, ans, a[N], b[N], ta[N];
void dfs(int x, int f) {
if (!ans) return;
if (a[x + 1] + f >= 0 && a[x + 1] + f <= 9) {
cout << x << " " << f << "\n";
a[x] += f; a[x + 1] += f; ans--;
return;
}
dfs(x + 1, -f);
if (!ans) return;
cout << x << " " << f << "\n";
a[x] += f; a[x + 1] += f; ans--;
}
signed main() {
string s, t;
cin >> n >> s >> t;
for (int i = 0; i < n; i++) ta[i + 1] = a[i + 1] = s[i] - '0', b[i + 1] = t[i] - '0';
for (int i = 1; i < n; i++) {
int delta = b[i] - ta[i];
ta[i] += delta; ta[i + 1] += delta; ans += abs(delta);
}
if (ta[n] != b[n]) {
puts("-1");
return 0;
}
cout << ans << "\n";
ans = min(ans, 100000ll);
for (int i = 1; i < n && ans > 0; i++) {
while (a[i] != b[i] && ans > 0) {
dfs(i, sgn(b[i] - a[i]));
}
}
return 0;
}
Copy (块状链表/bitset/vector)
题意:
给定一个序列,初始长度为n,每次操作选择一个区间,拷贝一份插入到这个区间后边,或者询问序列中第x数的值,x<=n.
n,q<=1e5,第一种操作的次数<=20000
题解:
这是网上的一份vector题解,吓尿了。。。
还好现在已经tle了。。。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
const int N=1e5+10;
int n,q;
vector<int>a;
int main()
{
std::ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin>>t;
while(t--)
{
cin>>n>>q;
a.clear();
a.resize(n+1);
for(int i=1;i<=n;i++)
cin>>a[i];
int ans=0;
for(int i=1;i<=q;i++)
{
int op;
cin>>op;
if(op==1)
{
int l,r;
cin>>l>>r;
auto p=a.begin()+r+1;//指定插入点
auto select_l=a.begin()+l;
auto select_r=a.begin()+r+1;
//选取复制区间的左右端点,因为是Insert函数要求的区间是左闭右开,所以[l,r]=[l,r+1)
/*
之所以是左闭右开是因为只有少数迭代器支持<运算符,而
几乎所有迭代器都支持!=运算符,并且使用!=和左闭右开定义
可以更方便地判断是否到达end以终止处理
*/
//超过1~n范围的的数已经没用,可以直接删掉
auto del=a.begin()+n+1;
if(del!=a.end())
a.erase(del,a.end());//删除指定区间内的数字
//在指定的位置插入指定区间的数
vector<int> tmp(select_l,select_r);
a.insert(p,tmp.begin(),tmp.end());
// a.insert(p,select_l,select_r);
}
else
{
int pos;
cin>>pos;
ans^=a[pos];
}
}
cout<<ans<<endl;
}
return 0;
}
————————————————
版权声明:本文为CSDN博主「࿐ཉི༗࿆一鲸落,万物生 ༗࿆ཉི࿐」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_54562114/article/details/125921191
正解是块状链表
#include <ext/rope>
#include <iostream>
using namespace std;
using namespace __gnu_cxx;
rope<int> S;
void solve() {
int n, q;
cin >> n >> q;
S.clear();
for (int i = 1; i <= n; i++) {
int t;
cin >> t;
S.push_back(t);
}
int ans = 0;
while (q--) {
int op, l, r, x;
cin >> op;
if (op == 1) {
cin >> l >> r;
S = S.substr(0, r) + S.substr(l - 1, n - r);
} else {
cin >> x;
ans ^= S[x - 1];
}
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int T;
cin >> T;
while (T--)
solve();
return 0;
}
或者bitset
#include <bits/stdc++.h>
// #pragma GCC optimize(2)
// #define int long long
using namespace std;
const int maxn = 1e5 + 10;
int n, q;
bitset<maxn> b, c, d;
int a[maxn];
int op[maxn], l[maxn], r[maxn];
signed main()
{
// freopen("1003.in", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(0);
int t;
cin >> t;
while (t--) {
b.reset();
cin >> n >> q;
for (int i = 1; i <= n; ++i) cin >> a[i];
for (int i = 1; i <= q; ++i) {
cin >> op[i] >> l[i];
if (op[i] == 1) {
cin >> r[i];
}
}
for (int i = q; i >= 1; --i) {
if (op[i] == 2) {
b[l[i]] = b[l[i]] - 1;
}
else {
c = b & (~bitset<maxn>(0) >> (maxn - r[i] - 1));
d = b & (~bitset<maxn>(0) << (r[i] + 1));
b = c ^ (d >> (r[i] - l[i] + 1));
}
}
int ans = 0;
for (int i = 1; i <= n; ++i) {
if (b[i]) ans ^= a[i];
}
cout << ans << endl;
}
return 0;
}
day23
Burenka and Traditions (hard version)(贪心)
题意:
给出 n 个数,每次可以选择一个区间和一个数,将该区间的所有数异或上该数,代价为区间长度除以2上取整,求将所有数变为 0 的最小代价
题解:
可以发现,上取整的结果都可以分为长度为 2 或 1 的组合,而对于一段区间,例如 a,b,c如果有 a⨁b=c,则其贡献可以减一,故可以发现一个贪心策略:维护前缀异或和,找不相交的两个相同的数,用 set 维护每段,for一遍即可
时间复杂度:O(nlogn)
//
// Created by vv123 on 2022/8/19.
//
#include <bits/stdc++.h>
#define pii pair<int,int>
#define int long long
using namespace std;
const int N = 1e6 + 10;
int n, ans, sum;
void solve() {
cin >> n; ans = n; sum = 0;
set<int> s; int x;
s.insert(0);
for (int i = 1; i <= n; i++) {
cin >> x;
sum ^= x;
if (s.count(sum)) ans--, s.clear();
s.insert(sum);
}
cout << ans << "\n";
}
signed main() {
int T; cin >> T;
while (T--) {
solve();
}
return 0;
}
Robots(贪心)
题意
有 n 个机器人和 n 个天线,分布在直线上,坐标各不相同。如果我们激活某一条天线,那么离它最近的机器人会移动到天线处爆炸,而其他机器人不动。如果两个机器人和天线距离相同,那么左侧的机器人移动。需要构造一个激活顺序,让机器人移动的距离之和尽可能小,输出距离之和与激活顺序。保证输入机器人和天线坐标分别递增
题解
显然,从左到右,让机器人和天线一一对应,机器人的移动总距离是最小的。
那么,如何构造顺序让机器人和天线正好可以一一对应呢?
首先可以考虑最左侧的天线。如果最左侧的天线,左侧没有机器人,那么激活它之后一定是最左侧的机器人会移动,激活它没有任何问题。
如果最左侧的天线对应的机器人在它的左边,那么就把它暂存入栈,然后继续判断下一个。
如果遇到一个天线,对应的机器人在它右边,那么结合栈顶元素,构成一种情况:中间两个天线,两侧是它们对应的机器人。那么至少有一个天线,激活后是可以吸引正确的机器人的。
循环直至末尾,如果栈中还有剩余元素,那么一定可以从右向左依次输出(因为栈中保存的元素,都是机器人在对应天线的左侧)
————————————————
版权声明:本文为CSDN博主「铬钼合金」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/CrMo2001/article/details/115038105
//
// Created by vv123 on 2022/8/19.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 10;
int n, ans, a[N], b[N];
signed main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) {
cin >> b[i];
ans += abs(a[i] - b[i]);
}
cout << ans << "\n";
vector<int> st;
for (int i = 1; i <= n; i++) {
if (b[i] < a[i]) {
while (!st.empty() && b[st.back()] - a[st.back()] <= a[i] - b[st.back()]) {
cout << st.back() << " "; st.pop_back();
}
cout << i << " ";
} else {
st.push_back(i);
}
}
while (!st.empty()) {
cout << st.back() << " ";
st.pop_back();
}
return 0;
}
day24
C. Cipher count
题意:
题解:
转载自 2020 ICPC Universidad Nacional de Colombia Programming Contest - _kiko - 博客园 (cnblogs.com)
埃氏筛
一个串为需要被计数的串,当且仅当它的最小循环节为其本身
枚举每个长度,f(i)表示长度为i的且最小循环节为为自身的串的数量,那么可以得到f(i)=ai−∑d∣if(d)
直接类似埃氏筛一样处理即可
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MOD = (int)1e9+7;
const int MAXN = 5e6+7;
int f[MAXN], a, k, ret;
void solve(){
scanf("%d %d",&a,&k);
f[0] = 1;
for(int i = 1; i <= k; i++) f[i] = 1ll * f[i-1] * a % MOD;
for(int i = 1; i <= k; i++) {
ret = (ret + f[i]) % MOD;
for(int j = i + i; j <= k; j += i) f[j] = (f[j] - f[i] + MOD) % MOD;
}
cout << ret << endl;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("Local.in","r",stdin);
// freopen("ans.out","w",stdout);
#endif
solve();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律