暑假集训五[星际旅行,砍树,超级树,求和]
暑假集训五
题面
别问为啥先更六再更五。
A.星际旅行
转化题意,因为无向边相当于两条有向边,让一条无向边只走一次,相当于删去它两条边中的一条。则题意可转化为删除两条边之后,原图是否存在欧拉路,以及能形成欧拉路的方案数。
存在欧拉路的充要条件是:图是边联通的。
边联通和点联通的关系就像割点与割边。
删去两条边能构成欧拉路的,总共只有三种情况:
- 删除两个自环。
- 删除一个自环和一个正常边。
- 删除两条有且仅有一个公共顶点的边。
正确性的证明详见kiritokazuto大佬的博客。
Code
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 1e5 + 10;
int n, m, cnt, ring;
long long ans;
int head[MAXN], deg[MAXN];
bool used[MAXN];
struct Edge{
int to, next;
}e[MAXN << 1];
inline void Add(int u, int v){
e[++cnt].to = v;
e[cnt].next = head[u];
head[u] = cnt;
}
void dfs(int rt){
used[rt] = true;
for(register int i = head[rt]; i; i = e[i].next){
int v = e[i].to;
if(!used[v]) dfs(v);
}
}
inline int read(){
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
int main(){
n = read(), m = read();
for(register int i = 1; i <= m; i++){
int u, v;
u = read(), v = read();
Add(u, v);
Add(v, u);
if(u == v) ring++;
else{
deg[u]++;
deg[v]++;
}
}
if(ring == m){
putchar('0');
return 0;
}
for(register int i = 1; i <= n; i++){
if(deg[i]){
dfs(i);
break;
}
}
for(register int i = 1; i <= n; i++){
if(!used[i] && deg[i]){
putchar('0');
return 0;
}
}
ans += 1LL * (m - ring) * ring;
ans += 1LL * ring * (ring - 1) / 2;
for(register int i = 1; i <= n; i++)
ans += 1LL * deg[i] * (deg[i] - 1) / 2;
printf("%lld", ans);
return 0;
}
B.砍树
数论分块,等价于求出一个最大的 \(d\),满足:
\[\large \sum\limits_{i = 1}^{n}(\left \lceil \frac{a_i}{d} \right \rceil \times d - a_i)\leq k
\]
设 \(C = k + \sum\limits_{i = 1}^{n}a_i\)。
那么 \(\sum\limits_{i = 1}^{n} \lceil \frac{a_i}{d}\rceil \times d \leq C\)。
然后就可以数论分块了。
Code
#include<cmath>
#include<cstdio>
#define LL long long
using namespace std;
const int MAXN = 110;
LL n, k;
LL sum, ans;
LL aim[MAXN];
inline LL read(){
LL x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
int main(){
//数论分块啊
n = read(), k = read();
for(register int i = 1; i <= n; i++){
aim[i] = read();
sum += aim[i];
}
sum += k;
for(register LL l = 1, r; l <= sum; l = r + 1){
r = sum / (sum / l);
LL tot = 0;
for(register int i = 1; i <= n; i++)
tot += ceil((1.0 * aim[i]) / (1.0 * r));
if(tot <= sum / r) ans = r;
}
printf("%lld", ans);
return 0;
}
C.超级树
神仙DP,不会,贺的。
还是看kiritokazuto大佬的博客吧。
Code
#include<cstdio>
#include<algorithm>
#define LL long long
using namespace std;
const int MAXK = 310;
int k, p;
LL dp[MAXK][MAXK]; //这状态定义能想到就TM见鬼了
// dp[i][j]表示在一棵 i 级超级树中,有 j 条路径同时存在且这 j 条路径没有公共点时,可能的情况数
int main(){
scanf("%d%d", &k, &p);
dp[1][1] = dp[1][0] = 1;
for(register int i = 0; i <= k; i++){
for(register int j = 0; j <= k; j++){
for(register int l = 0; l <= j; l++){
int r = j - l;
LL num = dp[i][l] * dp[i][r] % p;
dp[i + 1][j + 1] = (1LL * dp[i + 1][j + 1] + num) % p;
dp[i + 1][j] = (1LL * dp[i + 1][j] + num + 2 * num * j) % p;
dp[i + 1][j - 1] = (1LL * dp[i + 1][j - 1] + 2 * num * l * r + num * (l * (l - 1) + r * (r - 1))) % p;
}
}
}
printf("%lld", dp[k][1]);
return 0;
}
D.求和
P4427 [BJOI2018]求和
大水题,LCA树剖维护 + 树上差分。
可以观察到,\(k <= 50\),\(n <= 3e5\),并不大。
一棵树的平均深度是 \(\log n\),退化成链的最坏深度是 \(n\),所以我们可以预处理出 \(1\)~\(n\) 的 \(1\)~\(k\) 次幂,同时维护一个 \(1\) ~ \(k\) 次幂的树上前缀和。
最后的答案就是
\[sum[u][k] + sum[v][k] - 2 * sum[lca][k] + val[lca][k]
\]
记得取模后被减数可能小于减数,所以减完要先加上模数再取模,考试的时候因为这个 100 pts →
0 pts。
Code
//没大样例,手搓的倒是没啥问题
//linux怎么架对拍来着,囸
//卡个常,毕竟大常数选手
#include<cstdio>
#include<algorithm>
#define LL long long
using namespace std;
const int MAXN = 3e5 + 10, MAXK = 55;
const int Mod = 998244353;
int n, m, cnt, num;
int head[MAXN];
int fa[MAXN], son[MAXN], size[MAXN], deep[MAXN];
int dfn[MAXN], top[MAXN];
LL p[MAXN][MAXK], sum[MAXN][MAXK];
struct Edge{
int to, next;
}e[MAXN << 1];
inline void Add(int u, int v){
e[++cnt].to = v;
e[cnt].next = head[u];
head[u] = cnt;
}
void dfs_deep(int rt, int father, int depth){
size[rt] = 1;
fa[rt] = father;
deep[rt] = depth;
for(register int i = 1; i <= 50; i++)
sum[rt][i] = 1LL * (sum[fa[rt]][i] + p[deep[rt]][i]) % Mod;
int max_son = -1;
for(register int i = head[rt]; i; i = e[i].next){
int v = e[i].to;
if(v == father) continue;
dfs_deep(v, rt, depth + 1);
size[rt] += size[v];
if(max_son < size[v]){
son[rt] = v;
max_son = size[v];
}
}
}
void dfs_top(int rt, int top_fa){
dfn[rt] = ++num;
top[rt] = top_fa;
if(!son[rt]) return;
dfs_top(son[rt], top_fa);
for(register int i = head[rt]; i; i = e[i].next){
int v = e[i].to;
if(!dfn[v]) dfs_top(v, v);
}
}
int Get_Lca(int x, int y){
while(top[x] != top[y]){
if(deep[top[x]] < deep[top[y]]) swap(x, y);
x = fa[top[x]];
}
if(deep[x] > deep[y]) swap(x, y);
return x;
}
LL Get_Sum(int x, int y, int k){
int lca = Get_Lca(x, y);
return (1LL * (sum[x][k] + sum[y][k]) % Mod - (2 * sum[lca][k] % Mod) + Mod + p[deep[lca]][k]) % Mod;
}
void init(){
for(register int i = 1; i <= n; i++){
p[i][0] = 1;
for(register int j = 1; j <= 50; j++)
p[i][j] = 1LL * p[i][j - 1] * i % Mod;
}
}
inline int read(){
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c ^ 48);
c = getchar();
}
return x * f;
}
int main(){
n = read();
init();
for(register int i = 1; i <= n - 1; i++){
int u, v;
u = read(), v = read();
Add(u, v);
Add(v, u);
}
dfs_deep(1, 0, 0);
dfs_top(1, 1);
m = read();
for(register int i = 1; i <= m; i++){
int x, y, k;
x = read(), y = read(), k = read();
printf("%lld\n", 1LL * Get_Sum(x, y, k));
}
return 0;
}
以下为博客签名,与博文无关。
只要你们不停下来,那前面就一定有我。所以啊,不要停下来~
本文来自博客园,作者:TSTYFST,转载请注明原文链接:https://www.cnblogs.com/TSTYFST/p/16600332.html