2022NOIPA层联测7
\(accoder\) 用数据告诉我们,找女朋友是个假命题
找(a)
简单推一下柿子,维护总和和平方和
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 200005;
const int mod = 998244353;
int x[maxn], y[maxn];
int n;
int main(){
n = read();
for(int i = 1; i <= n; ++i)x[i] = read(), y[i] = read();
int sx = 0, sy = 0, sxx = 0, syy = 0;
for(int i = 1; i <= n; ++i)sx = (sx + x[i]) % mod;
for(int i = 1; i <= n; ++i)sy = (sy + y[i]) % mod;
for(int i = 1; i <= n; ++i)sxx = (sxx + 1ll * x[i] * x[i] % mod) % mod;
for(int i = 1; i <= n; ++i)syy = (syy + 1ll * y[i] * y[i] % mod) % mod;
for(int i = 1; i <= n; ++i){
int ax = (1ll * x[i] * x[i] % mod * n % mod + sxx - 2ll * x[i] * sx % mod + mod) % mod;
int ay = (1ll * y[i] * y[i] % mod * n % mod + syy - 2ll * y[i] * sy % mod + mod) % mod;
printf("%d\n",(ax + ay) % mod);
}
return 0;
}
女(b)
赛时暴跳父亲切了。。。。。。
比较无语
最后 \(20min\) 想出来了 \(nlog^3n\) 的做法,但是调不出来。。。。
给 \(chino\) 大佬简单讲了一下思路。他切了,\(chino\) 太巨了
顺便帮我调出来了,,,
简单来说,在每个点维护其到其子树内黑点的最短距离 \(dist\)
那么每次查询在他的祖先链上查询 \(min(dist + dep_x - dep_f)\) 即可
本质上是在 \(lca\) 处统计贡献
考虑如何维护,
链上信息- >树剖
区间修改查询- > 线段树
但是直接取 \(min\) 无法快速消除一个点的影响,于是考虑维护所有点
具体的,在线段树每个节点维护一个 \(multiset\) 表示在当前整个区间都能贡献的黑点深度,取区间最大深度向上贡献 \(val\)
查询时,整区间直接返回 \(val\), 非整区间维护的最小深度,与查询深度最大的进行计算
每次操作到根,轻重链有 \(log\) 个, 每次线段树区间修改会最多 \(log\) 个区间,每个区间对 \(multiset\) 操作一个 \(log\)
所以总复杂度 \(qlong^3 n\)
题解做法是建出来点分树,然后暴力跳父亲
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 100005;
const int inf = 0x3f3f3f3f;
int head[maxn], tot;
struct edge{int to, net;}e[maxn << 1 | 1];
void add(int u, int v){
e[++tot].net = head[u];
head[u] = tot;
e[tot].to = v;
}
int n, q;
int f[maxn], dep[maxn], cnt;
bool col[maxn];
int son[maxn], top[maxn], dfn[maxn], size[maxn];
void dfs1(int x){
size[x] = 1;
for(int i = head[x]; i; i = e[i].net){
int v = e[i].to;
if(v == f[x])continue;
f[v] = x; dep[v] = dep[x] + 1;
dfs1(v);
size[x] += size[v];
son[x] = size[son[x]] > size[v] ? son[x] : v;
}
}
int tim, id[maxn];
void dfs2(int x, int tp){
dfn[x] = ++tim; id[tim] = x;
top[x] = tp;
if(son[x])dfs2(son[x], tp);
for(int i = head[x]; i; i = e[i].net){
int v = e[i].to;
if(v == f[x] || v == son[x])continue;
dfs2(v, v);
}
}
struct seg{
struct node{
multiset<int>s;
int fd, val;
}t[maxn << 2 | 1];
void push_up(int x){
t[x].fd = min(t[x << 1].fd, t[x << 1 | 1].fd);
t[x].val = min(t[x << 1].val, t[x << 1 | 1].val);
if(t[x].s.size())t[x].val = min(t[x].val, *(t[x].s.begin()) + t[x].fd + t[x].fd);
}
void built(int x, int l, int r){
if(l == r){
t[x].val = inf;
t[x].fd = -dep[id[l]];
return;
}
int mid = (l + r) >> 1;
built(x << 1, l, mid);
built(x << 1 | 1, mid + 1, r);
push_up(x);
}
void modify(int x, int l, int r, int L, int R, int val){
if(L <= l && r <= R){
t[x].s.insert(val);
t[x].val = min(t[x].val, *(t[x].s.begin()) + t[x].fd + t[x].fd);
return;
}
int mid = (l + r) >> 1;
if(L <= mid)modify(x << 1, l, mid, L, R, val);
if(R > mid)modify(x << 1 | 1, mid + 1, r, L, R, val);
push_up(x);
}
void del(int x, int l, int r, int L, int R, int val){
if(L <= l && r <= R){
t[x].s.erase(t[x].s.lower_bound(val));
t[x].val = inf;
if(l != r)push_up(x);
else if(t[x].s.size())t[x].val = min(t[x].val, *(t[x].s.begin()) + t[x].fd + t[x].fd);
return;
}
int mid = (l + r) >> 1;
if(L <= mid)del(x << 1, l, mid, L, R, val);
if(R > mid)del(x << 1 | 1, mid + 1, r, L, R, val);
push_up(x);
}
int query(int x, int l, int r, int L, int R, int mx){
if(L <= l && r <= R){
return t[x].val;
}
int mid = (l + r) >> 1, ans = inf;
if(t[x].s.size()) ans = *(t[x].s.begin()) - mx - mx;
if(L <= mid)ans = min(ans, query(x << 1, l, mid, L, R, mx));
if(R > mid)ans = min(ans, query(x << 1 | 1, mid + 1, r, L, R, mx));
return ans;
}
}t;
int num;
void add(int x){
++num;
int u = x;
while(u){
t.modify(1, 1, n, dfn[top[u]], dfn[u], dep[x]);
u = f[top[u]];
}
}
void del(int x){
--num;
int u = x;
while(u){
t.del(1, 1, n, dfn[top[u]], dfn[u], dep[x]);
u = f[top[u]];
}
}
int query(int x){
if(num == 0)return -1;
int u = x, ans = inf;
while(u){
ans = min(ans, t.query(1, 1, n, dfn[top[u]], dfn[u], dep[u]) + dep[x]);
u = f[top[u]];
}
return ans;
}
void rev(int x){
if(col[x]){del(x); col[x] = 0;}
else{ add(x);col[x] = 1;}
}
int main(){
n = read(), q = read();
for(int i = 1; i < n; ++i){
int u = read(), v = read();
add(u, v); add(v, u);
}
dep[1] = 1; dfs1(1); dfs2(1, 1);
t.built(1, 1, n);
rev(1);
for(int i = 1; i <= q; ++i){
int opt = read(), x = read();
if(opt & 1)rev(x);
else printf("%d\n",query(x));
}
return 0;
}
朋(c)
不会,弃了,数据水,判断必要条件能过
题解: 给每条边一个随机边权,然后FMT求行列式即可
假做法
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 50005;
const int inf = 0x3f3f3f3f;
int n, m;
struct EDG{int u, v, w;}l[maxn];
struct wwl{
int head[maxn], tot = 1;
struct edge{int to, net, val;}e[maxn << 1 | 1];
void add(int u, int v, int w){
e[++tot].net = head[u];
head[u] = tot;
e[tot].to = v;
e[tot].val = w;
}
void link(int u, int v, int w){
add(u, v, w); add(v, u, 0);
}
int dep[maxn], now[maxn];
int s, t;
bool bfs(){
for(int i = 1; i <= n + n + 5; ++i)dep[i] = 0;
queue<int>q; now[s] = head[s]; dep[s] = 1;
q.push(s);
while(!q.empty()){
int x = q.front(); q.pop();
if(x == t)return true;
for(int i = head[x]; i; i = e[i].net){
int v = e[i].to;
if(e[i].val > 0 && !dep[v]){
dep[v] = dep[x] + 1;
now[v] = head[v];
q.push(v);
}
}
}
return false;
}
int dfs(int x, int from){
if(x == t || from <= 0)return from;
int res = from, i;
for(i = now[x]; i; i = e[i].net){
int v = e[i].to;
if(dep[v] == dep[x] + 1 && e[i].val > 0){
int k = dfs(v, min(res, e[i].val));
if(k <= 0)dep[v] = 0;
res -= k;
e[i].val -= k;
e[i xor 1].val += k;
if(res <= 0)break;
}
}
now[x] = i;
return from - res;
}
int dinic(){
int mx = 0;
while(bfs())mx += dfs(s, inf);
return mx;
}
bool init(int k){
tot = 1; for(int i = 1; i <= n + n + 5; ++i)head[i] = 0;
s = n + n + 1, t = n + n + 2;
for(int i = 1; i <= m; ++i)if((l[i].w & k) == k)link(l[i].u, l[i].v + n, 1);
for(int i = 1; i <= n; ++i)link(s, i, 1);
for(int i = 1; i <= n; ++i)link(i + n, t, 1);
if(dinic() == n)return true;
else return false;
}
}w;
int ans[129];
int num[129];
bool vis[129];
int main(){
n = read(), m = read();
for(int i = 1; i <= m; ++i)l[i].u = read(), l[i].v = read(), l[i].w = read();
for(int i = 1; i <= m; ++i)++num[l[i].w], vis[l[i].w] = 1;
for(int i = 126; i >= 0; --i){
for(int j = 1; j < 128; j += j)
if((i | j) > i)num[i] += num[i | j];
}
for(int k = 127; k >= 0; --k){
if(num[k] < n)continue;
int f = 127;
for(int j = 1; j < 128; j += j)if((k | j) > k && num[k | j])f &= (k | j);
if(f > k && !vis[k])continue;
ans[k] = w.init(k);
}
for(int i = 0; i <= 127; ++i)printf("%d",ans[i]);
return 0;
}
友(d)
学习了点分治。。。这东西我现在才学。。。
直接树形背包是 \(nm^2\) 的,虽然数据水能卡过。。
树形背包的问题是转移太多了,需要拼接贡献
考虑如何减少转移,
假如我们只考虑包含根节点的联通块,那么就可以直接转移而非拼接
具体来讲,处理出来 \(dfs\)序,按照其进行转移
考虑是否选择当前点,如果选择,那么可以考虑他的第一个儿子是否选择,不选择,就只能跳出当前子树
具体一点,如果当前点有儿子,那么选择当前点贡献到儿子
选择或者不选择当前点贡献到跳出该子树的第一个位置,即子树内最大的\(dfs\) 序 \(+1\) 的位置
这样转移方向只有两个,而且只需考虑当前点选或不选,所以处理一个大小为 \(n\) 的树只需要 \(nm\)
于是每次考虑了过定点的联通块,那么后面再考虑就没有选择当前根的必要,也就是说可以删除当前点
那么套上点分治就可以 \(nmlogn\) 解决问题
code
#pragma GCC optimize(3)
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<cassert>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 1005;
const int maxm = 10005;
const int inf = 0x3f3f3f3f;
int head[maxn], tot;
struct edge{int to, net;}e[maxn << 1 | 1];
void add(int u, int v){
e[++tot].net = head[u];
head[u] = tot;
e[tot].to = v;
}
int a[maxn], b[maxn], n, m;
int size[maxn], mx[maxn], sum, root;
bool vis[maxn];
void findroot(int x, int fa){
size[x] = 1; mx[x] = 0;
for(int i = head[x]; i; i = e[i].net){
int v = e[i].to;
if(v == fa || vis[v])continue;
findroot(v, x);
size[x] += size[v];
mx[x] = max(mx[x], size[v]);
}
mx[x] = max(mx[x], sum - size[x]);
if(mx[x] < mx[root])root = x;
}
ll f[maxn][maxm], ans;
int dfn[maxn], dfnr[maxn], id[maxn], tim;
void dfs(int x, int fa){
dfn[x] = ++tim; id[tim] = x;
for(int i = head[x]; i; i = e[i].net){
int v = e[i].to;
if(v == fa || vis[v])continue;
dfs(v, x);
}
dfnr[dfn[x]] = tim;
}
void calc(int x){
tim = 0;
dfs(x, 0);
for(int i = 1; i <= tim + 1; ++i)
for(int j = 0; j <= m; ++j)
f[i][j] = -inf;
f[1][0] = 0;
for(int i = 1; i <= tim; ++i){
if(i + 1 <= dfnr[i]){
int now = id[i];
for(int j = 0; j <= m - a[now]; ++j){
f[i + 1][j + a[now]] = max(f[i + 1][j + a[now]], f[i][j] + b[now]);
}
}
for(int j = 0; j <= m; ++j){
int now = id[i];
if(j + a[now] <= m)f[dfnr[i] + 1][j + a[now]] = max(f[dfnr[i] + 1][j + a[now]], f[i][j] + b[now]);
f[dfnr[i] + 1][j] = max(f[dfnr[i] + 1][j], f[i][j]);
}
}
for(int i = 0; i <= m; ++i)ans = max(ans, f[tim + 1][i]);
}
void solve(int x){
calc(x); vis[x] = true;
for(int i = head[x]; i; i = e[i].net){
int v = e[i].to;
if(vis[v])continue;
sum = size[v]; root = 0;
findroot(v, 0); solve(root);
}
}
int main(){
n = read(), m = read();
for(int i = 1; i <= n; ++i)a[i] = read(), b[i] = read();
for(int i = 1; i < n; ++i){
int u = read(), v = read();
add(u, v); add(v, u);
}
mx[0] = sum = n;
findroot(1, 0);
solve(root);
printf("%lld\n",ans);
return 0;
}