【10.30校内测试】【玄学数论?】【点分治】【费用流动态开点】
Solution
这种题怎么推??当然是打表啊!
打表发现规律,满足上述条件的数对一定满足大数减小数等于它们的gcd??
然而考试的时候知道了这个规律也没有写出来....
知道了以上结论后,就枚举两数的差d,使大数为$kd$,小数为$kd-d$,它们的gcd一定就是d了,那么只用判断两数的异或是否也等于d即可。
主要是复杂度证明了??根据迷之调和级数,这样做复杂度是$O(nln_n)$的
Code
#include<bits/stdc++.h>
using namespace std;
int main() {
freopen("gcd.in", "r", stdin);
freopen("gcd.out", "w", stdout);
int n, ans = 0;
scanf("%d", &n);
for(int i = 1; i <= n; i ++) {
for(int k = 2; k <= n / i; k ++) {
if(((k * i) ^ (k - 1) * i) == i) ans ++;
}
}
printf("%d", ans);
return 0;
}
Solution
“树上路径的题很多都可以用点分治解决”
所以这道题就是点分治叻,对于每个分治出的树的子树,按顺序遍历子树,更新答案后将子树新的贡献加入。这种贡献用set维护单增,用lower_bound查询即可。
Code
#include<bits/stdc++.h>
using namespace std;
int n, s, e, k = 0x3f3f3f3f;
inline int read() {
int x = 0; char ch = getchar(); int t = 0;
while(!isdigit(ch)) t |= (ch == '-'), ch = getchar();
while(isdigit(ch)) x = x * 10 + ch - '0', ch = getchar();
return x *= t ? -1 : 1;
}
struct Node {
int v, nex, w;
} Edge[200005];
int stot, h[100005];
inline void add(int u, int v, int w) {
Edge[++stot] = (Node) {v, h[u], w};
h[u] = stot;
}
int siz[100005], rt, asiz, vis[100005], sum;
inline void find_root(int u, int f) {
siz[u] = 1;
int tmp = 0;
for(int i = h[u]; i; i = Edge[i].nex) {
int v = Edge[i].v;
if(vis[v] || v == f) continue;
find_root(v, u);
siz[u] += siz[v];
if(siz[v] > tmp) tmp = siz[v];
}
if(sum - siz[u] > tmp) tmp = sum - siz[u];
if(tmp < asiz) rt = u, asiz = tmp;
}
int dis[100005], dep[100005];
inline void get_dis(int u, int f) {
dep[++dep[0]] = dis[u];
for(int i = h[u]; i; i = Edge[i].nex) {
int v = Edge[i].v;
if(vis[v] || v == f) continue;
dis[v] = dis[u] + Edge[i].w;
get_dis(v, u);
}
}
set < int > st;
inline void cal(int u) {
for(int i = h[u]; i; i = Edge[i].nex) {
int v = Edge[i].v;
if(vis[v]) continue;
dep[0] = 0; dis[v] = Edge[i].w;
get_dis(v, u);
for(int i = 1; i <= dep[0]; i ++) {
if(dep[i] >= s && dep[i] <= e) {
k = min(k, dep[i]); continue;
}
if(dep[i] > e) continue;
int tmp = s - dep[i];
set < int > :: iterator it;
it = st.lower_bound(tmp);
if(*it + dep[i] > e || *it + dep[i] < s) continue;
k = min(k, *it + dep[i]);
}
for(int i = 1; i <= dep[0]; i ++) {
if(dep[i] < s)
st.insert(dep[i]);
}
}
st.clear();
}
inline void work(int u) {
vis[u] = 1;
cal(u);
for(int i = h[u]; i; i = Edge[i].nex) {
int v = Edge[i].v;
if(vis[v]) continue;
sum = siz[v], asiz = 0x3f3f3f3f;
find_root(v, u);
work(rt);
}
}
int main() {
freopen("path.in", "r", stdin);
freopen("path.out", "w", stdout);
n = read(), s = read(), e = read();
for(int i = 1; i < n; i ++) {
int u = read(), v = read(), w = read();
add(u, v, w); add(v, u, w);
}
sum = n, asiz = 0x3f3f3f3f;
find_root(1, 0);
work(rt);
if(k > e) printf("-1");
else printf("%d", k);
return 0;
}
Solution
乍一看好像之前做过的费用流经典模型修车啊??
再一看数据范围,这么多个点是怎么回事???
所以隐藏在省选题面具下实际上是一道NOI的题:美食节QAQ
这真的是noip模拟赛???
我们发现这道题如果要像修车那样全部建边,明显时间、空间都承受不起。而这两道题唯一的区别也在这里,这道题有很多很多边是无用的,所以考虑怎么把这些边的空间给省去。
所以是动态开点了,先把所有洞拆点,但一开始只把所有n连到拆的第一个点(表示相对这个洞被倒数第一个进入),找到一条增广路后把选用的这个拆的点往后移一位,也就是这个洞被倒数第2,倒数第3....次选用所代表的点,然后此时再将这些点加边到图中去跑,也就是可能有用的点再连边,无用就不管。
一开始把连向汇点的建边放到循环内部去了,建了好多,调了好久QAQ
Code
#include<bits/stdc++.h> #define oo 0x3f3f3f3f using namespace std; int n, m; inline int read() { int x = 0; char ch = getchar(); int t = 0; while(!isdigit(ch)) t |= (ch == '-'), ch = getchar(); while(isdigit(ch)) x = x * 10 + ch - '0', ch = getchar(); return x *= t ? -1 : 1; } struct Node { int v, nex, f, w; } Edge[5000005]; int h[100005], stot = 1; void add(int u, int v, int f, int w) { Edge[++stot] = (Node) {v, h[u], f, w}; h[u] = stot; Edge[++stot] = (Node) {u, h[v], 0, -w}; h[v] = stot; } int vis[100005], dis[100005], S, T, pree[100005], preu[100005]; bool Spfa() { memset(vis, 0, sizeof(vis)); memset(dis, 0x3f3f3f3f, sizeof(dis)); dis[S] = 0; vis[S] = 1; queue < int > q; q.push(S); while(!q.empty()) { int u = q.front(); q.pop(); vis[u] = 0; for(int i = h[u]; i; i = Edge[i].nex) { int v = Edge[i].v; if(Edge[i].f && dis[v] > dis[u] + Edge[i].w) { dis[v] = dis[u] + Edge[i].w; pree[v] = i, preu[v] = u; if(!vis[v]) vis[v] = 1, q.push(v); } } } return dis[T] < dis[T + 1]; } long long mincost; void Doge() { int u = T, delta = oo; while(u != S) { delta = min(delta, Edge[pree[u]].f); u = preu[u]; } u = T; while(u != S) { Edge[pree[u]].f -= delta; Edge[pree[u] ^ 1].f += delta; u = preu[u]; } mincost += delta * dis[T]; } int p[50], t[50][105], tot; int main() { freopen("bird.in", "r", stdin); freopen("bird.out", "w", stdout); scanf("%d%d", &n, &m); for(int i = 1; i <= n; i ++) { p[i] = read(), tot += p[i]; add(S, i, p[i], 0); } S = 0, T = n + m * tot + 1; for(int i = 1; i <= n; i ++) for(int j = 1; j <= m; j ++) { t[i][j] = read(); add(i, n + (j - 1) * tot + 1, 1, t[i][j]); } for(int i = 1; i <= m; i ++) add(n + (i - 1) * tot + 1, T, 1, 0); while(Spfa()) { Doge(); int x = preu[T] + 1; //printf("%d\n", x); for(int i = 1; i <= n; i ++) { int pos; int tmp = (x - n) % tot; if(!tmp) pos = (x - n) / tot, tmp = tot; else pos = (x - n) / tot + 1; add(i, x, 1, t[i][pos] * tmp); } add(x, T, 1, 0); } printf("%lld", mincost); return 0; }