Logo S08577 的博客

博客

标签
暂无

E - K-th Largest Connected Components 题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-09-24 12:59:42

思路

首先,该题有求联通块,不妨使用并查集求两点是否在同一联通块并且进行合并操作。 $2$ 操作是求联通块内第 $k$ 大,我们可以使用set来操作,合并时进行启发式合并,求第 $k$ 大可以直接暴力求解。

代码

int find(int x){
    return fa[x]==x?x:fa[x]=find(fa[x]);
}

void merge(int u,int v){
    u=find(u),v=find(v);
    if(u!=v){
        if(s[u].size()<s[v].size()){
            for(auto i:s[u]) s[v].insert(i);
        }
        else{
            for(auto i:s[v]) s[u].insert(i);
            swap(s[u],s[v]);
        }
        fa[u]=v;
    }
}


int query(int u,int k){
    u=find(u);
    if(s[u].size()<k) return -1;
    else{
        set<int> :: iterator it;
        for(it=s[u].begin();it!=s[u].end();it++){
            k--;
            if(k==0) return *it;
        }
    }
}

P2121拆地毯题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-09-27 19:28:57

思路

这道题挺简单的。

首先题目说明,连成的图中不能有环,说明只能是一棵树。而要求整个树上的边权最大,不难想到最大生成树我们只需要求出只有 $k$ 条边时的最大生成树即可。

代码

struct id{
    int x,y,w;
}a[N];
int fa[N];


int find(int x)
{
    return fa[x]==x?x:(fa[x]=find(fa[x]));
}

bool cmp(id a ,id b){
    return a.w>b.w;\/\/与最小生成树的唯一区别
}
int n,m;
int k;
int ans=0,edge=0,sum=0;
void Kruscal(){
    for(int i=1;i<=m;i++){
        int u=find(a[i].x),v=find(a[i].y);
        if(u==v) continue;
        ans+=a[i].w;
        fa[u]=v;
        edge++;
        if(edge==n-1||edge==k) break;
    }
}

错误总结

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-09-28 10:37:53

9.28

  1. int函数必须要有返回值。
  2. sqrt的返回值是 $double$ 类型的,应当前面加上 (int)

9.28 普及模拟赛总结

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-09-28 10:53:27

T1

这道题要求从 $1$ 到 $n$ 里约数是偶数个数的数的个数。不难发现,只有完全平方数的约数个数是奇数个,其余的所有数约数个数都是偶数个。

注意,sqrt 的返回值是 $double$,当使用 sqrt(i)*sqrt(i) 时应当写成 (int) sqrt(i)*(int) sqrt(i) 不然会导致大部分的数都符合条件。

T2

求 $x \times y \mod z$ 的值,$x,y \le 10^{18}$。

要是题目为$x^ y \mod z$ 的值,$x,y \le 10^{18}$ 那么不难想到这道题用快速幂可做,而 $x^y$ 为 $y$ 个 $x$ 相乘,$x \times y$ 为 $y$ 个 $x$ 相加,只需要把快速幂中的乘改成加即可。

inline int ksj(int a,int b,int Mod){
	long long res=0;
	while(b>0){
		if(b&1)
			res=(res+a)%Mod;
		a=(a+a)%Mod;
		b>>=1;
	}
	return res%Mod;
}

T4

只会Kruscal不会Prim

$int$ 函数一定要有返回值!

$int$ 函数一定要有返回值!

$int$ 函数一定要有返回值!

不然会出现全部RE的情况!

10.2xm模拟赛总结

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-10-02 15:45:08

T2

T2是子序列,用的map+暴力,复杂度为O(n^4nlogn)本以为能过40,实则只过了20,正解DP,赛事没有想到DP做法。

T3

T3认为暴力的复杂度会T飞,听完讲解之后发现是能过30的,不应该不写。

T4

T4赛事一点思路没有

总结

总结:多打暴力,做之前及时想好时间复杂度,不要出现类似状况,也可以直接暴搜

10.2xm模拟赛记录

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-10-02 15:45:22

T2

T2是子序列,用的map+暴力,复杂度为O(n^4nlogn)本以为能过40,实则只过了20,正解DP,赛事没有想到DP做法。

T3

T3认为暴力的复杂度会T飞,听完讲解之后发现是能过30的,不应该不写。

T4

T4赛事一点思路没有

总结

总结:多打暴力,做之前及时想好时间复杂度,不要出现类似状况,也可以直接暴搜

10.2 xm模拟赛题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-10-02 16:53:32

T1

赛时A了。

用折半搜索,如果整个序列用一个搜索,那么复杂度将会为 $O(2^n)$,显然会T飞,那么不妨考虑折半搜索,先搜索前半段,再搜索后半段,复杂度为 $O(2\times 2^{\frac{n}{2}})$,可以AC。

我们将前半段和后半段搜索的结果分别放入 $sum1$ 和 $sum2$ 中,双指针求最大值即可。

注意,这里两个数组的长度在 $1e5$ 左右,$O(n^2)$ 过不了,需要稍微剪剪枝。(本人自己测之前写的 $n^2$,当 $n=35$ 时,T飞了)

T2

20pts

我们发现对于 $\forall i,j,s_i\neq s_j$,不难发现个数即为 $C_n^4$。

40pts

$n^4$ 暴力即可。

100pts

DP

设 $f_{i,j}$ 为在前 $i$ 个选 $j$ 个的方案数,$lst_i$ 为 $i$ 上一次出现的位置,不难推出状态转移方程:

$$f_{i,j}=f_{i-1,j}+f_{i-1,j-1}$$ $$f_{i,j}=f_{i,j}-f_{lst_{s_i}-1,j-1}[lst_{s_i} \neq 0] $$ (第一句的意思是杨辉三角,即组合数)

($f_{lst_{s_i}-1,j-1}$ 是减去重复的。很好理解,$lst_{s_i}-1$ 为该字母上一次出现的位置)

初始化: $$f_{i,0}=1$$ 在前 $i$ 个里面啥都不选,显然只有一种方式。

最后输出 $f_{n,4}$ 即可。

key code

    for(int i=0;i<=n;i++) f[i][0]=1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=4;j++){
            f[i][j]=f[i-1][j]+f[i-1][j-1];
            if(lst[s[i]]) f[i][j]-=f[lst[s[i]]-1][j-1];
        }
        lst[s[i]]=i;
    }
    cout<<f[n][4];

鸣谢 @Dtw

T3

20-60pts

dfs暴力即可

看剪枝剪了多少。

100pts

分类讨论。

这道题已知小W杀了不超过3只,这是很方便进行分讨的。

题目已知小Z不超过小W,可以分讨各杀了几只。

$3-1,3-2,3-3,2-1,2-2$

以小W杀了3只为例,暴力枚举杀了3只的所有情况,判断有无路径,并记录怪兽的最大值最小值以及价值和。

将所有路径按照最小值降序和最大值降序两种方式排列,分别给小W和小Z,用双指针类似T1的方式合并即可。

最后将所有讨论所得的最大值记录一下输出即可。

复杂度为 $O(n^6 \log n)$。

T4

30pts

暴力贪心,对于每次询问的 $l$ 和 $r$,我们选一条能覆盖到 $l$ , 且右端点最大的线段(不需要考虑左端点,对于左端点只需覆盖到 $l$ 即可。),重复操作,知道覆盖到 $r$ ,操作的次数即为所求答案。

[CSP-J2019] 纪念品 题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-10-08 18:49:19

思路

不难发现这是一道DP,而且看起来是背包问题。

这道题有一个难点,小伟今天买的东西不知道在第几天卖出。

如果这道题是背包的话,那么买的物品必须当天买,明天卖。

那么,思考到这里,就不难想到了:

我们可以当天买,明天卖。而且明天我们还可以原价买回,就等于明天没有卖。

不妨想到背包的三要素:

1.容量:今天手里有的钱。

2.价值:这个货物明天和今天的价值差。

3.重量:今天的价值。

设 $dp_i$ 为当手里有的钱为 $i$ 的时候,最多能赚多少钱。

状态转移方程: $$dp_j=\max(dp_j,dp_{j-p_{k,i}}+(p_{k+1,i}-p_{k,i}))$$

其中,$k$ 是枚举第几天, $i$ 是枚举 第 $k$ 天 每个货物的价值,$p_{i,j}$ 是第 $i$ 天,第 $j$ 个货物的价值。

循环的最后,将手头的钱加上能得到的最优的钱数$\max(dp_i)$ 即可。

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<set>

using namespace std;
#define int long long
#define ll long long
#define Endl cout<<endl;
#define ENdl Endl
#define xy cout<<"xy";
#define yx cout<<"yx";
#define pii pair<int,int>
#define lowbit(x) x&(-x)
#define fi first
#define se second
#define fst ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);

const int N=1e3+10;\/\/注意修改
const int mod=1e9+7;
const int Max=0x3f3f3f3f3f;

int n;
int a[N],dp[N*10];
int p[N][N];


signed main(){
	\/\/ freopen(".in","r",stdin);
	\/\/ freopen(".out","w",stdout);
    int t,n,m;
    cin>>t>>n>>m;
    for(int i=1;i<=t;i++){
        for(int j=1;j<=n;j++) cin>>p[i][j];
    }
    for(int k=1;k<t;k++){
        memset(dp,0,sizeof dp);
        for(int i=1;i<=n;i++){
            for(int j=p[k][i];j<=m;j++) dp[j]=max(dp[j],dp[j-p[k][i]]+(p[k+1][i]-p[k][i]));
        }
        m+=dp[m];
    }
    cout<<m;
	return 0;
}

摆渡车(常规+斜率优化)

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-02-08 17:16:22

题意

有 $n$ 个人在等车,第 $i$ 个人从 $t_i$ 时刻开始等车。摆渡车来回一趟需要 $m$ 分钟。求所有人最小等车时间。

思路

该题显然用动态规划。

不妨设 $f_i$ 为从开始到第 $i$ 时刻每个人最小等车时间。

不难发现:

$$f_i=\min_{j \le i-m}(f_j+\sum_{j < t_k \le i} i-t_k)$$

但是,这个方程中的 $\sum_{j < t_k \le i} i-t_k$ 很难处理。我们不妨将其拆开:

$$\sum_{j < t_k \le i} i-t_k \\ =\sum_{j < t_k \le i} i -\sum_{j < t_k \le i} t_k$$

看见求和,我们不难想到使用前缀和。不妨设 $cnt_i$ 为从开始到 $i$ 时刻有多少人等车,$sum_i$ 为从开始到 $i$ 时刻的位置之和。

不难发现:

$$\sum_{j < t_k \le i} i-t_k \\ =\sum_{j < t_k \le i} i -\sum_{j < t_k \le i} t_k \\ = i \times (cnt_i-cnt_j)-(sum_i-sum_j)$$

所以,状态转移方程即为:

$$f_i = \min_{j \le i-m}(i \times (cnt_i-cnt_j)-(sum_i-sum_j)) $$

最后,由于最后一个人的到达时间为 $t$,我们只需要查询 $\min_{1 \le i \le t+k} f_i$

但是,时间复杂度为 $O(t^2)$ 只能拿到$50$ $pts$。

不妨进行优化,不难发现,当时间小于 $i-2m$ 时,乘客肯定已经坐上车了,所以无需考虑 $i-2m$ 之前的情况。

可将式子优化为:

$$f_i = \min_{i-2m \le j \le i-m}(i \times (cnt_i-cnt_j)-(sum_i-sum_j)) $$

可是,复杂度仍然爆炸,只能去的 $75$ $pts$。

我们不难发现,摆渡车以 $m$ 为一个周期。如果从 $i-m$ 到 $i$ 没有人上车,则这段区间为无用区间,令 $f_i=f_{i-m}$。

这样,我们就可以AC了。

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<set>


using namespace std;

#define int long long
#define ll long long
#define Endl cout<<endl;
#define ENdl Endl
#define xy cout<<"xy";
#define yx cout<<"yx";
#define pii pair<int,int>
#define lowbit(x) x&(-x)
#define fi first
#define se second
#define fst ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);

const int N=4e6+10;\/\/注意修改
const int mod=1e9+7;
const int Max=0x3f3f3f3f3f;

int n,m;
int a[N];
int f[N],cnt[N],sum[N];


\/*
f_i=min j<=i-m(f_j+sigma i-t_k)
 *\/
signed main(){
    
\/\/   freopen("a.in","r",stdin);
\/\/   freopen("a.out","w",stdout);
    cin>>n>>m;
    int t=0;
    for(int i=1;i<=n;i++) {
        int x;
        cin>>x;
        cnt[x]++;
        sum[x]+=x;
        t=max(t,x);
    }
    for(int i=1;i<t+m;i++){
        cnt[i]+=cnt[i-1];
        sum[i]+=sum[i-1];
    }
    for(int i=0;i<t+m;i++){
        if(i>=m&&cnt[i]==cnt[i-m]){
            f[i]=f[i-m];
            continue;
        }
        f[i]=cnt[i]*i-sum[i];
        for(int j=max(i-m-m+1,0ll);j<=i-m;j++){
            f[i]=min(f[i],f[j]+i*(cnt[i]-cnt[j])-(sum[i]-sum[j]));
        }
        
    }
    int res=Max;
    for(int i=t;i<t+m;i++){
        res=min(res,f[i]);
    }
    cout<<res;

    return 0;
}



\/*
 1
 5 3
 2 4 5

 *\/

斜率优化版

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<set>


using namespace std;

#define int long long
#define ll long long
#define Endl cout<<endl;
#define ENdl Endl
#define xy cout<<"xy";
#define yx cout<<"yx";
#define pii pair<int,int>
#define lowbit(x) x&(-x)
#define fi first
#define se second
#define fst ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);

const int N=4e6+10;\/\/注意修改
const int mod=1e9+7;
const int Max=0x3f3f3f3f3f;

int n,m;
int a[N];
int f[N],cnt[N],sum[N],q[N];

double get_k(int x,int y){
    return double(f[y]+sum[y]-f[x]-sum[x])\/(cnt[x]==cnt[y]?1e-9:  cnt[y]-cnt[x]);
}
\/*
f_i=min j<=i-m(f_j+sigma i-t_k)
 *\/
signed main(){
    
\/\/   freopen("a.in","r",stdin);
\/\/   freopen("a.out","w",stdout);
    cin>>n>>m;
    int t=0;
    for(int i=1;i<=n;i++) {
        int x;
        cin>>x;
        cnt[x]++;
        sum[x]+=x;
        t=max(t,x);
    }
    for(int i=1;i<t+m;i++){
        cnt[i]+=cnt[i-1];
        sum[i]+=sum[i-1];
    }
    int l=1,r=0;
    for(int i=0;i<t+m;i++){
        if(i-m>=0){
            while(l<r&&get_k(q[r-1],q[r])>=get_k(q[r],i-m)){
                r--;
            }
            q[++r]=i-m;
        }
        while(l<r&&get_k(q[l],q[l+1])<=i) l++;
        f[i]=i*cnt[i]-sum[i];
        if(l<=r){
            f[i]=min(f[i],f[q[l]]+i*(cnt[i]-cnt[q[l]])-(sum[i]-sum[q[l]]));
        }
    }
    int res=Max;
    for(int i=t;i<t+m;i++){
        res=min(res,f[i]);
    }
    cout<<res;

    return 0;
}



\/*
 1
 5 3
 2 4 5

 *\/

CF1187E 7.20

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2025-07-20 17:05:41

鸣谢

@Firacode

题意

https://codeforces.com/problemset/problem/1187/E

思路

这道题显然发现可以用树形dp。

首先,一般的树形DP都是自下而上进行的,不妨我们也先自下而上DP。设 $f_i$ 为以 $i$ 为根把 $i$ 染成黑色答案。根据题意,我们发现 你获得的分数等于包含该顶点的、仅由白色顶点组成的连通块的大小,所以 $f_u+=size_u$,然后接着遍历下面的子树,所以 $f_u=size_u+ \sum_{v ∈ son \ u} f_v$。但是发现,这种时间复杂度为 $O(N^2)$,直接爆炸。

但是,我们不难想到换根DP。换根时,除了换根的两个点,其他子树的 $size$ 不变。 比如,原根为 $u$,换的根是 $v$,那么 $size_u$ 减少了 $size_v$,$size_v$ 增加了 $n-size_v$。设 $g_i$ 为以 $i$ 为根的答案,根据上面的推断,$g_v=g_u+n-2*size_v$。

两遍dfs求出答案。

#include<iostream>
using namespace std;
const int N=2e5+10;\/\/注意修改
const int mod=1e9+7;
const int Max=0x3f3f3f3f3f;
int n;
int f[N],g[N],sz[N];
vector<int> ve[N];
void dfs1(int u,int fa){
    sz[u]=1;
    for(int i=0;i<ve[u].size();i++){
        int v=ve[u][i];
        if(v==fa) continue;
        dfs1(v,u);
        sz[u]+=sz[v];
        f[u]+=f[v];
    }
    f[u]+=sz[u];
}

void dfs2(int u,int fa){
    for(int i=0;i<ve[u].size();i++){
        int v=ve[u][i];
        if(v==fa) continue;
        g[v]=g[u]+n-2*sz[v];
        dfs2(v,u);
    }
}

\/*
 *\/
signed main(){
    
\/\/   freopen("a.in","r",stdin);
\/\/   freopen("a.out","w",stdout);
    cin>>n;
    for(int i=1;i<n;i++){
        int u,v;
        cin>>u>>v;
        ve[u].push_back(v);
        ve[v].push_back(u);
    }
    dfs1(1,0);
    g[1]=f[1];
    dfs2(1,0);
    int maxx=0;
    for(int i=1;i<=n;i++){
        maxx=max(maxx,g[i]);
    }
    cout<<maxx;
    return 0;
}


zph说似乎子树深度之和就是子树大小?

共 53 篇博客