能做的都没做啊。。。。。 跟着榜走。。。。。 心情复杂。。
A - Beauty of Trees
题意:
题意:
有
n
n
个数,每次给你一个信息,代表
al xor al+1... xor ar=k
a
l
x
o
r
a
l
+
1
.
.
.
x
o
r
a
r
=
k
,问你哪些信息是错误的,如果
x
x
信息和信息只可以
x
x
对错或者
x
x
错对,那么认为先给出的信息是对的。
思路:
并查集路径压缩。 记
a
a
数组的前缀异或和是,那么信息
l,r,k
l
,
r
,
k
实际上就是
sumr xor suml−1=k
s
u
m
r
x
o
r
s
u
m
l
−
1
=
k
,如果已知
sumr xor sumx=k1,suml−1 xor sumx=k2
s
u
m
r
x
o
r
s
u
m
x
=
k
1
,
s
u
m
l
−
1
x
o
r
s
u
m
x
=
k
2
,那么只要判断
k1 xor k2
k
1
x
o
r
k
2
是否等于
k
k
即可,否则设,那么有
sumx xor sumy=k1 xor k2 xor k,
s
u
m
x
x
o
r
s
u
m
y
=
k
1
x
o
r
k
2
x
o
r
k
,
可以合并
x,y,
x
,
y
,
并设
sum[x] xor sum[y]=k1 xor k2 xor k
s
u
m
[
x
]
x
o
r
s
u
m
[
y
]
=
k
1
x
o
r
k
2
x
o
r
k
, 然后直接用路径压缩就好了。
#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 1e5 + 10;
using namespace std;
int n, m, T, kase = 1;
int pre[maxn], sum[maxn];
int findset(int x) {
if(x == pre[x]) return x;
int px = pre[x];
pre[x] = findset(pre[x]);
sum[x] ^= sum[px];
return pre[x];
}
int main() {
while(scanf("%d %d", &n, &m) != EOF) {
vector<int> vec;
for(int i = 0; i <= n; i++) {
sum[i] = 0;
pre[i] = i;
}
for(int i = 1; i <= m; i++) {
int l, r, k;
scanf("%d %d %d", &l, &r, &k);
l--;
int fl = findset(l);
int fr = findset(r);
if(fl == fr) {
if((sum[l] ^ sum[r]) != k) vec.push_back(i);
} else {
pre[fl] = fr;
sum[fl] = sum[l] ^ sum[r] ^ k;
}
}
if(!vec.size()) vec.push_back(-1);
for(int i = 0; i < vec.size(); i++) printf("%d\n", vec[i]);
}
return 0;
}
D - Mr. Qiang and His Motorcycles
题意:
给你一棵
n(⩽3000)
n
(
⩽
3000
)
个节点每个节点带权值的树,
q(q⩽10)
q
(
q
⩽
10
)
个查询,每次给你一个
w
w
,问这棵树中有多少个连通块权值和刚好是。
思路:
先跑出
dfs
d
f
s
树,设:
dp[x][w]:
d
p
[
x
]
[
w
]
:
以
x
x
为根且经过节点大小为
w
w
的连通块的数量,一次考虑每个孩子,处理完之后合并到父节点上去,当考虑到孩子节点的时候,以
x
x
为根大小为的连通块总数
Sd
S
d
为:
这里求快速卷积, 给了取模的数是 998244353 998244353 ,直接快速 NTT N T T 求解。
#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 3e3 + 10;
const int INF = 1e9;
const ll mod = 998244353;
using namespace std;
typedef pair<int, int> pr;
int del[maxn], sz[maxn], wt[maxn];
vector<int> G[maxn];
ll dp[3010][2010], rec[maxn * 20];
int n, m, k;
ll rev[maxn * 20], a[maxn * 20], b[maxn * 20];
ll qmod(ll x, ll n, ll mod) {
ll ans = 1;
while(n) {
if(n & 1) ans = ans * x % mod;
x = x * x % mod;
n >>= 1;
}
return ans;
}
void NTT(ll *a, ll len, int op) {
for(int i = 0; i < len; i++) if(i < rev[i]) swap(a[i], a[rev[i]]);
for(int i = 1; i < len; i <<= 1) {
ll wn = qmod(3, ((mod - 1) / i / 2 * op + mod - 1) % (mod - 1), mod);
int step = i << 1;
for(int j = 0; j < len; j += step) {
ll w = 1, x, y;
for(int k = 0; k < i; k++, w = (w * wn) % mod) {
x = a[j + k]; y = (w * a[j + k + i]) % mod;
a[j + k] = (x + y) % mod; a[j + k + i] = ((x - y) % mod + mod) % mod;
}
}
}
}
void solve_NTT(int x, int y, int wi) {
int len = 1, l = 0;
while((len <= 2 * wi)) { len <<= 1; l++; }
for(int i = 0; i < len; i++) {
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (l - 1));
if(i <= wi) { a[i] = dp[x][i]; b[i] = dp[y][i]; }
else a[i] = b[i] = 0;
}
NTT(a, len, 1); NTT(b, len, 1);
for(int i = 0; i < len; i++) a[i] = a[i] * b[i] % mod;
NTT(a, len, -1);
ll inv = qmod(len, mod - 2, mod);
for(int i = 0; i <= wi; i++) rec[i] = a[i] * inv % mod;
}
ll ans;
void DFS(int x, int fa, int wi) {
for(int i = 0; i <= wi; i++) dp[x][i] = 0;
dp[x][wt[x]] = 1;
for(int i = 0; i < G[x].size(); i++) {
int to = G[x][i];
if(to == fa || del[to]) continue;
DFS(to, x, wi);
solve_NTT(x, to, wi);
for(int j = 0; j <= wi; j++) dp[x][j] = (dp[x][j] + rec[j]) % mod;
}
ans += dp[x][wi];
}
int main() {
while(scanf("%d", &n) != EOF) {
for(int i = 1; i <= n; i++) {
scanf("%d", &wt[i]);
G[i].clear();
}
for(int i = 1; i < n; i++) {
int u, v;
scanf("%d %d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
}
scanf("%d", &m);
DFS(1, 0, 2000);
while(m--) {
ll wi; scanf("%lld", &wi);
ans = 0;
for(int i = 1; i <= n; i++) ans += dp[i][wi];
printf("%lld\n", ans % mod);
}
}
return 0;
}
E - A Simple Problem
题意:
一个
n
n
个数的数组(每个数的值
<11
<
11
<script type="math/tex" id="MathJax-Element-220"><11</script>),然后给一个
m
m
个数的数组,
b
b
数组中的数可以任意换成其他数,但是不能几个不同的数换成同一个数,同一个数也只能唯一换成另一个数,就是说如果第一个换成了
2
2
,那么后面所有的都要换成
2
2
,问数组中能有多少个可以和
b
b
匹配的子串。
思路:
第二个数组b可以从右往左考虑,每一个不同的数出现的位置,比如样例
1 2 1 2
1
2
1
2
,那么第
0
0
个数第一次出现在这个位置(序列中的
2
2
),第种数有这些位置出现了:
0
0
:
1
1
同样第种数就是:
1
1
:
0
0
对
0
0
和的下标位置哈希(求和比较方便),然后随便乱搞几下记录
b
b
串的哈希。
那么对于
a
a
数组,从左往右考虑,考虑到的时候,那么对于的判断区间
[l,r]
[
l
,
r
]
就是
[i−m+1,i]
[
i
−
m
+
1
,
i
]
,既然所有不同的数记录下来了,那么可以知道,
a[l+0],
a
[
l
+
0
]
,
a[l+2]
a
[
l
+
2
]
是第1种数,
a[l+1]
a
[
l
+
1
]
,
a[l+3]
a
[
l
+
3
]
是第
0
0
种数,可以简单认为是第
1
1
种数,是第
0
0
种数,那么求出在区间
[l,r]
[
l
,
r
]
的下标相对于
l
l
的位置的和(也就是第种数的哈希值, 所以求和方便),这里求和可以直接前缀,假设在区间
[l,r]
[
l
,
r
]
中所有是
a[l+0]
a
[
l
+
0
]
的下标是是
i1,i2,..,ik,
i
1
,
i
2
,
.
.
,
i
k
,
那么相对位置的哈希值就是
i1−l+i2−l+...+ik−l=(i1+i2+...+ik)−k∗l
i
1
−
l
+
i
2
−
l
+
.
.
.
+
i
k
−
l
=
(
i
1
+
i
2
+
.
.
.
+
i
k
)
−
k
∗
l
(第
1
1
种数的个数 也行, 只是求哈希, 如果最终结果相同的话肯定
k=
k
=
第
1
1
种数的个数 ),这样就是对于新的第种数的下标的哈希值, 后面的照样处理,最后判断哈希值是否和
b
b
串的哈希值相同即可。
#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 2e5 + 10;
const ll mod = 1e11 + 7;
using namespace std;
int n, m, a[maxn], b[maxn], k;
int use[13];
vector<ll> ag[20], sa[20], ha[20];
int tot[maxn], mp[20], show[20];
ll hh[20];
int main() {
while(scanf("%d %d", &n, &k) != EOF) {
for(int i = 0; i < 20; i++) {
ag[i].clear();
ha[i].clear();
sa[i].clear();
}
set<int> st;
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
ag[a[i]].push_back(i);
sa[a[i]].push_back(i);
int sz = sa[a[i]].size();
if(sz <= 1) continue;
sa[a[i]][sz - 1] += sa[a[i]][sz - 2];
}
scanf("%d", &m);
for(int i = 0; i < m; i++) scanf("%d", &b[i]);
int flag = 0;
for(int i = m - 1; i >= 0; i--) {
int t = b[i];
if(!st.count(t)) mp[t] = flag++;
st.insert(t); ha[mp[t]].push_back(i + 1);
}
ll val = 0, c = 1;
for(int i = 0; i < flag; i++) {
ll su = 0; c = c * 3;
for(int j = 0; j < ha[i].size(); j++) {
su += ha[i][j];
}
su = su * c * (i + 1) % mod;
val = (val + su) % mod;
}
int ans = 0;
for(int i = 1; i <= n; i++) {
if(i < m) continue;
memset(use, 0, sizeof use);
int l = i - m + 1, r = i, cc = 1;
ll hs = 0;
for(int j = 0; j < flag; j++) {
int x = l + ha[j][0] - 1; cc = cc * 3;
x = a[x];
if(use[x]) { hs = -mod - 5; break; }
use[x] = 1;
int idl = lower_bound(ag[x].begin(), ag[x].end(), l) - ag[x].begin();
int idr = upper_bound(ag[x].begin(), ag[x].end(), r) - ag[x].begin() - 1;
ll su = sa[x][idr];
if(idl) su -= sa[x][idl - 1];
ll t = ha[j].size();
su -= t * (l - 1);
su = su * cc * (j + 1) % mod;
hs = (hs + su) % mod;
}
if(hs == val) ans++;
}
printf("%d\n", ans);
}
return 0;
}
K - Maximum average Sequence
题意:
给你
n
n
个数组的序列,现在要你选出一个子序列,使得子序列满足整除关系的对数除以子序列长度最大。一对整除关系是指:如果子序列中的整除
aj
a
j
或者
aj
a
j
整除
ai
a
i
,那么
(i,j)
(
i
,
j
)
就是一对整除关系。
思路:
原序列中有整除关系的元素之间连一条边权值为
1
1
,那么就是选出一个子图使得边的权值和除以顶点数最大, 然后二分答案, 这是裸的最大密度子图。。。。 最大密度子图可以参考胡伯涛的论文《最小割模型在信息学竞赛中的应用》。
#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 2e4 + 10;
const int INF = 1e9 + 10;
const double eps = 1e-6;
using namespace std;
struct st {
int to, re; double cap;
st(int t = 0, double c = 0, int r = 0) : to(t), cap(c), re(r) {}
};
int n, m, s, t;
vector<st> G[maxn];
int it[maxn], lv[maxn];
void add(int f, int t, double c) {
G[f].push_back(st(t, c, G[t].size()));
G[t].push_back(st(f, 0, G[f].size() - 1));
}
void bfs() {
memset(lv, -1, sizeof(lv));
queue<int> q;
lv[s] = 0;
q.push(s);
while(!q.empty()) {
int u = q.front(); q.pop();
for(int i = 0; i < G[u].size(); i++) {
st &e = G[u][i];
if(e.cap > eps && lv[e.to] < 0) {
lv[e.to] = lv[u] + 1;
q.push(e.to);
}
}
}
}
double dfs(int v, int t, double f) {
if(v == t) return f;
for(int &i = it[v]; i < G[v].size(); i++) {
st &e = G[v][i];
if(e.cap > eps && lv[v] < lv[e.to]) {
double d = dfs(e.to, t, min(f, e.cap));
if(d > eps) {
e.cap -= d;
G[e.to][e.re].cap += d;
return d;
}
}
}
return 0;
}
double maxflow() {
double f = 0;
while(1) {
bfs();
if(lv[t] < 0) return f;
memset(it, 0, sizeof(it));
double fl;
while((fl = dfs(s, t, INF)) > eps) f += fl;
}
}
int from[maxn], to[maxn], vis[maxn];
double build_graph(double mid) {
s = 0; t = n + m + 1;
double tot = m;
for(int i = 0; i <= n + m + 1; i++) G[i].clear();
for(int i = 1; i <= n; i++) add(i, t, mid);
for(int i = 1; i <= m; i++) {
add(s, i + n, 1);
add(i + n, from[i], INF);
add(i + n, to[i], INF);
}
double res = maxflow();
tot = tot - res;
return tot;
}
int a[300];
int main() {
while(scanf("%d", &n) != EOF) {
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
m = 1;
for(int i = 1; i <= n; i++) {
for(int j = i + 1; j <= n; j++) {
if(a[i] % a[j] == 0 || a[j] % a[i] == 0) {
from[m] = i; to[m++] = j;
}
}
}
m--;
double l = 0, r = 1010;
while(l + 1e-9 < r) {
double mid = (l + r) / 2;
double sol = build_graph(mid);
if(sol > 0) l = mid;
else r = mid;
}
printf("%.8f\n", l);
}
return 0;
}
I - Magic Forest
题意:
给出
n
n
个数的数组还有
m
m
个查询,每次给一个,求一个
k
k
数组使得。
思路:
基本相当于裸的扩展欧几里得了…求个前缀
gcd
g
c
d
,如果
gcdn∤X
g
c
d
n
∤
X
那么无解,否则我们知道:
GCD(gcdn−1,an)=gcdn
G
C
D
(
g
c
d
n
−
1
,
a
n
)
=
g
c
d
n
,可以通过扩展欧几里得求得
gcdn−1∗x+an∗y=gcdn
g
c
d
n
−
1
∗
x
+
a
n
∗
y
=
g
c
d
n
,求出
x
x
和,
y
y
就是的系数,然后类似处理
gcdn−1
g
c
d
n
−
1
,之后的所有系数都乘以
x
x
<script type="math/tex" id="MathJax-Element-303">x</script>即可。
#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 1e5 + 10;
using namespace std;
typedef pair<ll, ll> pa;
ll n, m, T, a[maxn];
ll gg[maxn], ans[maxn];
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
void exgcd(ll a, ll b, ll &d, ll &x, ll &y) {
if(!b) { d = a; x = 1; y = 0; }
else { exgcd(b, a % b, d, y, x); y -= x * (a / b); }
}
int main() {
while(scanf("%lld %lld", &n, &m) != EOF) {
for(int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
if(i == 1) gg[i] = a[i];
else gg[i] = gcd(gg[i - 1], a[i]);
}
while(m--) {
ll x; scanf("%lld", &x);
if(x % gg[n]) { printf("NO\n"); continue; }
if(n == 1) { printf("%lld\n", x / a[1]); continue; }
ll aa, bb = a[n], xx, yy, d, t = x / gg[n];
for(int i = n - 1; i >= 1; i--) {
aa = gg[i];
exgcd(aa, bb, d, xx, yy);
ans[i + 1] = yy * t;
ans[i] = xx * t;
bb = a[i]; t *= xx;
}
for(int i = 1; i <= n; i++) printf("%lld%c", ans[i], i < n ? ' ' : '\n');
}
}
return 0;
}