ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

2022.8.4 颓废记录

2022-08-05 01:00:24  阅读:110  来源: 互联网

标签:sz le 颓废 记录 int maxn 2022.8 return dp


Preface

太蒻了QAQ

Content

[CF76A]Gift

\(N\) 个点,\(M\) 条边的无向图,第 \(i\) 条边有两个权值 \(g_i,s_i\),给定两数 \(G,S\)。

求一棵生成树 \(T\),使得 \(ans=G\times \max\limits_{i\in T}(g_i)+S\times \max\limits_{i\in T} (s_i)\) 最小,无解输出 \(-1\)。

\(1\le N \le 200,1\le M \le 5\times 10^4,1\le g_i,s_i,G,S \le 10^9\)。

(注:并查集的时间复杂度当作常数,不计入时间复杂度)

这个东西初看找不到什么思路可以直接求解,毕竟有两个权值。

遇到这种情况,一种比较通用的想法是固定某个权值,找另一个权值的最优解。

在这题中,将所有边按 \(g_i\) 升序排序。依次求出前 \(i(1\le i \le M)\) 条边以 \(s\) 为权值的最小生成树,更新答案即可。

直接这样做是 \(O(M^2)\) 的,但显然这个思路很可做,考虑优化。

发现对于一条边 \(i\),如果前 \(k\) 条边的最小生成树不包括 \(i\),则前 \(k+1\) 条边的最小生成树也不可能包含 \(i\)。

正确性显然,不作证明。

那么有效的边的数量是 \(O(N)\) 级别的,此时这个算法就已经有通过此题的希望了。

考虑如何删边。使用 std::multiset 单次操作 \(O(\log N)\),则时间复杂度 \(O(MN\log N)\)。

但这个算法我时间复杂度实现错了 还能优化。

发现每次只会加进来一条边,那完全可以使用冒泡排序 \(O(N)\) 地进行排序。

时间复杂度可以优化到 \(O(MN)\)。

Code

// Problem: CF76A Gift
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF76A
// Memory Limit: 250 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
using namespace std;
const int maxn = 205;
const int maxm = 50005;
typedef long long ll;
struct edge {
    int u,v;
    ll g,s;
    edge() {
        u = v = g = s = 0;
    }
    edge(int u,int v,ll g,ll s):u(u),v(v),g(g),s(s){}
    bool operator < (const edge& p)const {
        return s ^ p.s ? s < p.s : (u ^ p.u ? u < p.u : v < p.v);
    }
}E[maxm];
int n,m,pre[maxn],cnt;
edge a[maxm];
ll G,S;
int find(int x) {
    return x == pre[x] ? x : pre[x] = find(pre[x]);
}
int main() {
    scanf("%d %d %lld %lld",&n,&m,&G,&S);
    for(int i = 1;i <= m;++ i) {
        scanf("%d %d %lld %lld",&E[i].u,&E[i].v,&E[i].g,&E[i].s);
    }
    sort(E + 1 , E + 1 + m , [&](edge p,edge q) {
        return p.g < q.g;
    });
    ll ans = 5e18;
    ll curS;
    int sumEdge,newcnt;
    for(int i = 1;i <= m;++ i) {
        a[++ cnt] = E[i];//bubble-sort
        for(int k = cnt;k > 1;-- k)
            if(a[k - 1].s > a[k].s)swap(a[k - 1] , a[k]);
        if(cnt < n - 1)continue ;
        curS = sumEdge = newcnt = 0;
        for(int k = 1;k <= n;++ k)pre[k] = k;//init
        for(int k = 1;k <= cnt;++ k) {
            if(find(a[k].u) == find(a[k].v)) {
                continue ;
            }
            ++ sumEdge;
            pre[find(a[k].u)] = find(a[k].v);
            curS = max(curS , a[k].s);
            a[++ newcnt] = a[k];
            if(sumEdge >= n - 1)break ;
        }
        cnt = newcnt;
        if(sumEdge < n - 1)continue ;
        ans = min(ans , G * E[i].g + S * curS);
    }
    printf("%lld",ans == 5e18 ? -1 : ans);
    return 0;
}

给大家找点乐子:

每天的固定节目了属于是 /fn

[CF1292C]Xenon‘s Attack on the Gangs

给定一棵 \(N\) 个结点的树,将 \([0,n-2]\) 中的每个数分配为每条边的权值。

使得 \(\sum\limits_{1\le s\lt t\le n} \operatorname{MEX}_{(u,v)\in path(s,t) } w(u,v)\) 最大。

\(2 \le n \le 3000\)。

差点做出来 *2300 的题QAQ。

这个数据范围明显是要 \(O(N^2)\) 的算法。

首先要注意到 \(0\),因为没有 \(0\) 的话 \(\operatorname{MEX}\) 值始终为 \(0\)。

将 \(0\) 随意放在某条边上,接下来考虑 \(1\) 的位置,因为没有 \(1\) 其它数的贡献必然更劣。

发现 \(1\) 必然和 \(0\) 接在一起,否则对答案不起作用。

再考虑 \(2\),显然 \(2\) 要放在 \(0,1\) 两条边形成的链旁边。

(说一下我的思路是怎么跑歪的:我只注意到 \(2\) 不能在 \(1\) 之前放置,但没注意到链有两端,我把注意力全放在了其中一端,另一端完全没注意 QAQ,由于思路不连贯,即使看到样例 \(2\) 注意到这点了也想不出来 qwq,后悔死了)

依次类推,可以发现,这些数所在的边组成了一条链,并且链上的权值呈单谷状。

具体而言,假设链的两端点固定在 \((s,t)\),将 \(s\to t\) 路径上的权值存入序列 \(a_{1\sim k}\),则 \(\exist j,\forall i \in [1,j),a_i \gt a_{i+1}\land \forall i \in [j,k),a_i\lt a_i+1\)。

(当然,这个玩意其实作用并没有那么大。。)

考虑怎样计算这条链的贡献。

设 \(sz(u,v)\) 表示原树中以 \(u\) 为根节点时 \(v\) 的子树大小,\(f(u,v)\) 表示以 \(u\) 为根时 \(v\) 的父亲。

若权值为 \(0\) 的边两端点为 \(u,v\),则 \((u,v)\) 对答案的贡献为 \(sz(v,u)\times sz(u,v)\)。

这并不难理解,其实就是经过 \((u,v)\) 这条边的路径数量,显然这些路径 \(\operatorname{MEX}\) 值至少为 \(1\)。

接下来,考虑权值为 \(1\) 的边,设其两端点为 \(v,t\)。

则 \((v,t)\) 对答案的贡献为 \(sz(u,t)\times sz(t,u)\)。

这其实代表着经过 \(u\to v \to t\) 这条链的路径数量,这些路径 \(\operatorname{MEX}\) 值至少为 \(2\)。

手玩几步就能发现统计答案的方法,开三个数组:

  • \(dp(u,v)\):表示链的两段为 \(u,v\) 时答案的最大值。

  • \(sz(u,v),f(u,v)\),含义见上文。

初始状态:\(\forall i \in [1,n],dp(i,i)=0\)

状态转移方程:\(dp(u,v)=\max\{dp(f(u,v),u),dp(f(v,u),v)\}+sz(u,v)\times sz(v,u)\)

\(sz\) 数组和 \(f\) 数组可以 \(O(N^2)\) 预处理,DP 的顺序不太好搞,用记忆化搜索会轻松不少。

时间复杂度 \(O(N^2)\)。

Code

答案的最大值没算对,结果不开 long long 见祖宗了 QAQ。

// Problem: CF1292C Xenon's Attack on the Gangs
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF1292C
// Memory Limit: 500 MB
// Time Limit: 3000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
#define pb emplace_back
using namespace std;
typedef long long ll;
const int maxn = 3005;
vector<int> G[maxn];
int n,ans;
int sz[maxn][maxn],f[maxn][maxn];
ll dp[maxn][maxn];
void dfs(int u,int s) {
    sz[s][u] = 1;
    for(auto& v : G[u]) {
        if(v == f[s][u])continue ;
        f[s][v] = u;
        dfs(v , s);
        sz[s][u] += sz[s][v];
    }
    return ;
}
ll DP(int u,int v) {
    if(u == v)return 0;
    if(dp[u][v])return dp[u][v];
    if(dp[v][u])return dp[v][u];
    return dp[u][v] = dp[v][u] = max(DP(f[u][v] , u) , DP(f[v][u] , v)) + 1ll * sz[u][v] * sz[v][u];
}
int main() {
    scanf("%d",&n);
    for(int i = 2;i <= n;++ i) {
        int u,v;
        scanf("%d%d",&u,&v);
        G[u].pb(v);
        G[v].pb(u);
    }
    ll ans = 0;
    for(int i = 1;i <= n;++ i) {
        dfs(i , i);
    }
    for(int i = 1;i <= n;++ i)
        for(int j = 1;j <= n;++ j)ans = max(ans , DP(i , j));
    printf("%lld\n",ans);
    return 0;
}

[luogu P3987]我永远喜欢珂朵莉~

当初立的 flag,现在来填坑。

首先要注意到:\(x=1\) 时,操作可以直接跳过。

那么 \(a_i\) 最多只会被操作 \(\log a_i\) 次。

那么怎样操作不关键,怎样快速找到操作的数才是关键。

由于 \(a_i \in [0,5\times 10^5]\),约数至多约 \(200\) 个,我们可以对每个 \(x\) 开一个 std::vector 存储其倍数出现的位置。

每次操作的时候二分出操作的区间,直接暴力操作,暴力删除。

要注意的是,由于 std::vector.erase 操作的特殊性,删除要从后往前。

Code

// Problem: P3987 我永远喜欢珂朵莉~
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3987
// Memory Limit: 1250 MB
// Time Limit: 4000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
#define VEC vector<int>::iterator
#define pb emplace_back
using namespace std;
const int maxn = 1e5 + 5;
const int maxm = 5e5 + 5;
typedef long long ll;
int n,m,a[maxn];
ll c[maxn];
vector<int> s[maxm];
inline int read() {
	int s = 0;
	char c = getchar();
	for(;!isdigit(c);c = getchar());
	for(;isdigit(c);c = getchar())s = (s << 1) + (s << 3) + (c ^ '0');
	return s;
}
void write(ll x) {
	if(x > 9)write(x / 10);
	putchar(x % 10 + '0');
	return ;
}
int lowbit(int x) {
	return x & -x;
}
void add(int x,int y) {
	for(;x <= n;x += lowbit(x))c[x] += y;
	return ;
}
ll query(int x) {
	ll ans = 0;
	for(;x;x -= lowbit(x))ans += c[x];
	return ans;
}
int main() {
	n = read();
	m = read();
	for(int i = 1;i <= n;++ i) {
		a[i] = read();
		add(i , a[i]);
		for(int j = 1;j * j <= a[i];++ j) {
			if(a[i] % j)continue ;
			s[j].pb(i);
			if(j * j != a[i])s[a[i] / j].pb(i);
		}
	}
	while(m --) {
		int op = read(),l = read(),r = read(),x;
		if(op & 1) {
			x = read();
			if(x == 1||s[x].empty())continue ;
			VEC itl = lower_bound(s[x].begin() , s[x].end() , l);
			VEC itr = upper_bound(s[x].begin() , s[x].end() , r);
			if(itl == s[x].end())continue ;
			vector<VEC> G;
			for(VEC it = itl;it != itr;++ it) {
				if(a[*it] % x)continue ;
				add(*it , -a[*it] + a[*it] / x);
				a[*it] /= x;
				if(a[*it] % x)G.pb(it);
			}
			for(int i = G.size() - 1;~ i;-- i)s[x].erase(G[i]);
		}
		else {
			write(query(r) - query(l - 1));
			puts("");
		}
	}
	return 0;
}

晚上用小号打了场 CF Edu Round,太坑了 QAQ,手速场,C 题毒瘤吃了两发罚时,DF 不会,E 压根没看 /kel

为什么和大佬们的差距这么遥不可及 qwq。

标签:sz,le,颓废,记录,int,maxn,2022.8,return,dp
来源: https://www.cnblogs.com/Royaka/p/16552770.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有