暑假集训五[星际旅行,砍树,超级树,求和]

暑假集训五

题面

别问为啥先更六再更五。

A.星际旅行

image

转化题意,因为无向边相当于两条有向边,让一条无向边只走一次,相当于删去它两条边中的一条。则题意可转化为删除两条边之后,原图是否存在欧拉路,以及能形成欧拉路的方案数。

存在欧拉路的充要条件是:图是边联通的
边联通和点联通的关系就像割点与割边。

删去两条边能构成欧拉路的,总共只有三种情况:

  1. 删除两个自环。
  2. 删除一个自环和一个正常边。
  3. 删除两条有且仅有一个公共顶点的边。

正确性的证明详见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.砍树

image

数论分块,等价于求出一个最大的 d,满足:

i=1n(aid×dai)k

C=k+i=1nai

那么 i=1naid×dC

然后就可以数论分块了。

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,不会,贺的。

image

还是看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<=50n<=3e5,并不大。
一棵树的平均深度是 logn,退化成链的最坏深度是 n,所以我们可以预处理出 1~n1~k 次幂,同时维护一个 1 ~ k 次幂的树上前缀和。

最后的答案就是

sum[u][k]+sum[v][k]2sum[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;
}
posted @   TSTYFST  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示