目录
C - And and Pair(二项式定理)
思路
考虑枚举每一个
i
i
i,求满足条件的
j
j
j,容易发现,当
i
i
i确定时,令
i
i
i的二进制位从高位到低位第一个1往后0的数量为
x
x
x,则满足条件的
j
j
j为
2
x
2^x
2x。
由于和
i
i
i最高位的1有关,那么考虑枚举最高位为
b
b
b的所有
i
i
i。
假设原串在
b
b
b位之后1的数量为
y
y
y,0的数量为
x
x
x,那么最高位为
b
b
b的
i
i
i的数量为
2
y
2^y
2y,那么除了最高位之外还有
z
z
z个1的
i
i
i的数量为
C
y
z
C_y^z
Cyz,这些
i
i
i对应的
j
j
j的数量都为
2
x
+
(
y
−
z
)
2^{x+(y-z)}
2x+(y−z)(
0
≤
z
≤
y
0 \le z \le y
0≤z≤y)
于是,以第
b
b
b位为最高位的
i
i
i对应的方案总数为
2
x
(
C
y
0
2
y
+
C
y
1
2
y
−
1
+
.
.
.
+
C
y
y
2
0
)
=
2
x
(
1
+
2
)
y
=
2
x
3
y
2^x(C_y^02^y+C_y^12^{y-1}+...+C_y^y2^0)=2^x(1+2)^y=2^x3^y
2x(Cy02y+Cy12y−1+...+Cyy20)=2x(1+2)y=2x3y
所以从后往前枚举一遍就可以啦
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
const int mod = 1000000007;
int n;
char s[N];
int qpow(int a, int b)
{
int res = 1;
while(b)
{
if (b & 1) res = 1LL * res * a % mod;
a = 1LL * a * a % mod;
b >>= 1;
}
return res;
}
int main()
{
int _;
scanf("%d", &_);
while (_ --)
{
scanf("%s", s + 1);
n = strlen(s + 1);
int c1 = 0, c0 = 0;
int ans = 0;
for (int i = n; i >= 1; i -- )
{
if (s[i] == '1')
ans = (ans + 1LL * qpow(2, c0) * qpow(3, c1) % mod) % mod;
if (s[i] == '1') c1 ++;
else c0 ++;
}
printf("%d\n", (ans + 1) % mod);
}
return 0;
}
E - Bob’s Problem(简单图论)
思路
首先把黑边选满
如果此时图已经联通,则选出k条权值最大的白边
否则对白边做最大生成树
如果最终图仍然不连通,则-1
否则如果白边还可以继续选,选出没被选到的权值最大的几条白边即可
用并查集维护即可
代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 50005, M = 500005;
int n, m, k;
struct edge
{
int u, v, w;
bool operator < (const edge& e) const
{
return w > e.w;
}
}E[M], unwhite[M], white[M];
int cnt, wcnt, uncnt;
int fa[N];
bool vis[N];
int getf(int x)
{
return x == fa[x] ? x : fa[x] = getf(fa[x]);
}
int main()
{
int _;
scanf("%d", &_);
while (_ --)
{
uncnt = wcnt = cnt = 0;
scanf("%d%d%d", &n, &m, &k);
for (int i = 1; i <= n; i ++ ) fa[i] = i;
LL ans = 0;
for (int i = 1; i <= m; i ++ )
{
int u, v, w, c;
scanf("%d%d%d%d", &u, &v, &w, &c);
if (c == 1) white[++ wcnt] = {u, v, w};
else
{
ans += w;
u = getf(u), v = getf(v);
if (u != v)
fa[u] = v;
}
}
for (int i = 1; i <= wcnt; i ++ )
{
int u = getf(white[i].u), v = getf(white[i].v);
if (u != v)
E[++ cnt] = white[i];
else
unwhite[++ uncnt] = white[i];
}
sort(E + 1, E + 1 + cnt);
for (int i = 1; i <= cnt; i ++ ) vis[i] = false;
for (int i = 1; i <= cnt; i ++ )
{
int u = getf(E[i].u), v = getf(E[i].v);
if (u != v && k)
{
vis[i] = true;
ans += E[i].w;
fa[u] = v;
k --;
}
}
bool flag = true;
for (int i = 1; i <= n; i ++ )
if (getf(i) != getf(1))
{
flag = false;
break;
}
if (!flag)
{
puts("-1");
continue;
}
if (k)
{
for (int i = 1; i <= cnt; i ++ )
if (!vis[i])
unwhite[++ uncnt] = E[i];
sort(unwhite + 1, unwhite + 1 + uncnt);
for (int i = 1; i <= k; i ++ )
ans += unwhite[i].w;
}
printf("%lld\n", ans);
}
return 0;
}
G - Eating Plan(思维、暴力)
思路
首先需要观察到
998857459
998857459
998857459并不是一个质数,质因数分解之后发现它由三个质数组成,最大的指数为
2803
2803
2803,也就是说,
a
i
≥
2803
a_i \ge 2803
ai≥2803时,
a
i
!
m
o
d
t
=
0
a_i!\ mod\ t=0
ai! mod t=0。
故给出的序列中最多只有
2802
2802
2802个不为0的项,那么我们枚举任意两项求出每个长度最多能吃多少质量,然后二分一下就可以了
代码
#include <iostream>
#include <cmath>
using namespace std;
const int N = 100005, M = 3000, mod = 998857459;
int n, m;
int jc[N];
struct Node
{
int p, val;
}a[N];
int cnt;
int mx[N];
int sum[N];
int b[N], c[N];
int b_cnt;
int main()
{
scanf("%d%d", &n, &m);
jc[0] = 1;
for (int i = 1; i <= n; i ++ )
jc[i] = 1LL * jc[i - 1] * i % mod;
for (int i = 1; i <= n; i ++ )
{
int x;
scanf("%d", &x);
if (jc[x])
a[++ cnt] = {i, jc[x]};
}
for (int i = 1; i <= cnt; i ++ )
sum[i] = (sum[i - 1] + a[i].val) % mod;
for (int i = 1; i <= cnt; i ++ )
for (int j = i; j <= cnt; j ++ )
{
int l = a[i].p, r = a[j].p;
int len = r - l + 1;
mx[len] = max(mx[len], (sum[j] - sum[i - 1] + mod) % mod);
}
int res = -1;
for (int i = 1; i <= n; i ++ )
if (mx[i] > res)
res = mx[i], b[++ b_cnt] = mx[i], c[b_cnt] = i;
for (int i = 1; i <= m; i ++ )
{
int k;
scanf("%d", &k);
int p = lower_bound(b + 1, b + 1 + b_cnt, k) - b;
if (p > b_cnt) puts("-1");
else
printf("%d\n", c[p]);
}
return 0;
}
K - Tree(dsu on tree)
思路
启发式合并(dsu on tree)裸题
求满足要求的点对有多少,相当于枚举每一个点
u
u
u作为
l
c
a
lca
lca,求它的任意两颗子树中的任意满足条件的点对数量。
条件有两个,对于两颗不同子树中的点
v
1
v1
v1和
v
2
v2
v2而言,
首先要满足
v
a
l
[
v
1
]
+
v
a
l
[
v
2
]
=
2
∗
v
a
l
[
u
]
val[v1] + val[v2] = 2 * val[u]
val[v1]+val[v2]=2∗val[u]
然后要满足
d
e
p
[
v
1
]
+
d
e
p
[
v
2
]
−
2
∗
d
e
p
[
u
]
<
=
k
dep[v1] + dep[v2] - 2 * dep[u] <= k
dep[v1]+dep[v2]−2∗dep[u]<=k
按照dsu on tree的思路,在枚举
v
1
v1
v1时,
v
2
v2
v2已经记录起来了,也就是说枚举
v
1
v1
v1然后求满足
v
a
l
[
v
2
]
=
2
∗
v
a
l
[
u
]
−
v
a
l
[
v
1
]
val[v2] = 2 * val[u] - val[v1]
val[v2]=2∗val[u]−val[v1]与
d
e
p
[
v
2
]
<
=
2
∗
d
e
p
[
u
]
+
k
−
d
e
p
[
v
1
]
dep[v2] <= 2 * dep[u] + k - dep[v1]
dep[v2]<=2∗dep[u]+k−dep[v1]的
v
2
v2
v2数量
那么就可以对每一个值开一颗权值线段树(需要动态开点),每一颗权值线段树以深度作为下标,这样维护就可以啦
代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 100005;
int n, k;
int val[N];
struct edge
{
int to, next;
}e[N];
int head[N], cnt;
int siz[N], dep[N], son[N];
struct Tree
{
int l, r;
int sum;
}t[N * 50];
int t_cnt;
int rt[N];
LL ans;
void add(int u, int v)
{
e[++ cnt] = {v, head[u]};
head[u] = cnt;
}
void push_up(int i)
{
t[i].sum = t[t[i].l].sum + t[t[i].r].sum;
}
void update(int& i, int l, int r, int pos, int k)
{
if (!i) i = ++ t_cnt;
if (l == r)
{
t[i].sum += k;
return;
}
int mid = (l + r) >> 1;
if (pos <= mid) update(t[i].l, l, mid, pos, k);
else update(t[i].r, mid + 1, r, pos, k);
push_up(i);
}
int query(int i, int l, int r, int ql, int qr)
{
if (!i) return 0;
if (ql <= l && r <= qr)
return t[i].sum;
int mid = (l + r) >> 1;
int s = 0;
if (ql <= mid) s += query(t[i].l, l, mid, ql, qr);
if (qr > mid) s += query(t[i].r, mid + 1, r, ql, qr);
return s;
}
void dfs(int u, int ff)
{
dep[u] = dep[ff] + 1;
siz[u] = 1;
for (int i = head[u]; i; i = e[i].next)
{
int v = e[i].to;
dfs(v, u);
siz[u] += siz[v];
if (!son[u] || siz[v] > siz[son[u]]) son[u] = v;
}
}
void updateAns(int u, int lca)
{
int Dep = 2 * dep[lca] + k - dep[u];
int Val = 2 * val[lca] - val[u];
if (Val >= 0 && Val <= n && Dep >= dep[lca] + 1)
ans += query(rt[Val], 1, n, dep[lca] + 1, min(Dep, n));
for (int i = head[u]; i; i = e[i].next)
{
int v = e[i].to;
updateAns(v, lca);
}
}
void modify(int u, int k)
{
update(rt[val[u]], 1, n, dep[u], k);
for (int i = head[u]; i; i = e[i].next)
modify(e[i].to, k);
}
void dsu(int u, int k)
{
for (int i = head[u]; i; i = e[i].next)
{
int v = e[i].to;
if (v == son[u]) continue;
dsu(v, 0);
}
if (son[u]) dsu(son[u], 1);
for (int i = head[u]; i; i = e[i].next)
{
int v = e[i].to;
if (v == son[u]) continue;
updateAns(v, u);
modify(v, 1);
}
update(rt[val[u]], 1, n, dep[u], 1);
if (!k) modify(u, -1);
}
int main()
{
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i ++ ) scanf("%d", &val[i]);
for (int i = 2; i <= n; i ++ )
{
int p;scanf("%d", &p);
add(p, i);
}
dfs(1, 0);
dsu(1, 0);
printf("%lld\n", ans * 2);
return 0;
}
L - Who is the Champion(签到)
思路
按照题意计算每只队伍的得分还有得球数与失球数的差之后排序即可。
注意特判
n
=
=
1
n==1
n==1的情况!!!!(wa了两发)
代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 105;
int n;
LL b[N][N];
struct Team
{
LL score, goal, conceded;
LL diff;
int id;
bool operator < (const Team& b) const
{
if (score == b.score)
return diff > b.diff;
return score > b.score;
}
}a[N];
int getscore(LL sa, LL sb)
{
if (sa > sb) return 3;
else if (sa == sb) return 1;
else return 0;
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
scanf("%lld", &b[i][j]);
if (n == 1)
{
puts("1");
return 0;
}
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
{
if (i != j)
{
a[i].goal += b[i][j];
a[i].conceded += b[j][i];
a[i].score += getscore(b[i][j], b[j][i]);
}
}
for (int i = 1; i <= n; i ++ )
a[i].id = i, a[i].diff = a[i].goal - a[i].conceded;
sort(a + 1, a + 1 + n);
if (a[1].score == a[2].score && a[1].diff == a[2].diff)
puts("play-offs");
else
printf("%d\n", a[1].id);
return 0;
}