20260728考试总结

P4427 [BJOI2018] 求和

简化题意

给定一颗 \(n\) 个节点的树,每个节点有个权值 \(a_i\),给定树上路径 \(l-r\)(这里记为 \(s\)),请你求出 \(\textstyle \sum_{i \in s}^{a_i^k}\)(\(k\)已知)

做法

注意到 \(k\) 比较小,所以我们可以考虑去与处理所有的 \(k\) 的树上前缀和,使用 \(LCA\) 查询即可,这里需要统计的是点的和,所以运用公式 sum[k][l]+sum[k][r]-sum[k][lca]-sum[k][dp[lca][0]]

代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=300005,mod=998244353;
int qpow(int x,int y){
    int ans=1;
    while(y){
        if(y&1)ans=ans*x%mod;
        x=x*x%mod;y>>=1;
    }
    return ans;
}
int n,m;
vector<int>g[N];
int dp[N][25];
int dep[N];
int sum[55][N];
void dfs(int u,int fa){
    dp[u][0]=fa;
    for(int i=1;i<=20;i++)dp[u][i]=dp[dp[u][i-1]][i-1];
    for(auto i:g[u]){
        if(i!=fa){
            dep[i]=dep[u]+1;
            dfs(i,u);
        }
    }
    return;
}
int LCA(int x,int y){
    if(dep[x]<dep[y])swap(x,y);
    for(int i=20;i>=0;i--){
        if(dep[dp[x][i]]>=dep[y])x=dp[x][i];
    }
    if(x==y)return x;
    for(int i=20;i>=0;i--){
        if(dp[x][i]!=dp[y][i])x=dp[x][i],y=dp[y][i];
    }
    return dp[x][0];
}
void init(int u,int fa){
    int jc=1;
    for(int i=1;i<=50;i++){
        jc*=dep[u];jc%=mod;
        sum[i][u]=(sum[i][fa]+jc)%mod;
    }
    for(auto i:g[u]){
        if(i!=fa)init(i,u);
    }
    return;
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    // freopen("sum.in","r",stdin);
    // freopen("sum.out","w",stdout);
    cin>>n;
    for(int i=1;i<n;i++){
        int u,v;cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs(1,0);init(1,0);
    cin>>m;
    for(int i=1;i<=m;i++){
        int l,r,k;cin>>l>>r>>k;
        int lca=LCA(l,r);
        cout<<((sum[k][l]+sum[k][r]-sum[k][lca]-sum[k][dp[lca][0]])%mod+mod)%mod<<"\n";
    }
    return 0;
}

P1514 [NOIP 2010 提高组] 引水入城

简化题意

有一个矩形,矩形上的格子有不同高度。你可以在第一行的任意多个格子放水,水会沿低的格子一直落下来。

求最少需要放多少水才能使得最后一行都有水,如果不能,则有多少格子没有水

题解

可以发现有一个性质,就是如果有解,则一个点覆盖的应定是一个区间。


证明

这里考虑使用反证法。

假设这是一条水流

没有线条则表示无法抵达,因为一定有解,所以将会有旁边的一条河流到达这个点

而这条红色的河,经过了蓝色的河之后才到达点,但是蓝色河流是无法到达的,因此矛盾。

(这里借用了这篇文章)的图片


所以我们可以考虑对于第一行的每个点,运用记忆化搜索求出可以到达的最后一行的左右点。最后通过一个最小线段覆盖求解答案即可。

代码
#include<bits/stdc++.h>
using namespace std;
const int N=505;
struct no{
    int l,r;
}t[N][N];
int h[N][N];
int sum[N];
bool vis[N][N];
int n,m;
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
void dfs(int x,int y){
    vis[x][y]=1;
    for(int i=0;i<4;i++){
        int zx=x+dx[i],zy=y+dy[i];
        if(zx>=1 && zx<=n && zy>=1 && zy<=m && h[x][y]>h[zx][zy]){
            if(!vis[zx][zy])dfs(zx,zy);
            t[x][y].l=min(t[x][y].l,t[zx][zy].l);
            t[x][y].r=max(t[x][y].r,t[zx][zy].r);
        }
    }
    return;
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++)cin>>h[i][j];
    }
    for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)t[i][j].l=1e9;
    for(int i=1;i<=m;i++)t[n][i]={i,i};
    for(int i=1;i<=m;i++){
        if(!vis[1][i])dfs(1,i);
    }
    int ans=0;
    for(int i=1;i<=m;i++)ans+=!vis[n][i];
    if(ans){
        cout<<0<<"\n"<<ans;
        return 0;
    }
    int x=0,top=1;
    while(x<m){
        ans++;
        int lasr=x+1;
        for(int i=1;i<=m;i++){
            if(t[1][i].l<=lasr)x=max(x,t[1][i].r);
        }
    }
    cout<<1<<"\n"<<ans;
    return 0;
}
posted @ 2026-07-28 16:09  凉茶映屏红  阅读(9)  评论(0)    收藏  举报