2020HDU多校第二场

A.Total Eclipse

题目描述
n n n个点, m m m条边的无向图,每一个点上有一个权值。接下来你每次要选择最大 k k k个联通的点,使他们的权值 − 1 -1 1,问最少需要几次能让所有点变为 0 0 0
思路
对于每一次减小肯定是从小的值减小到大的。其实将整个过程反过来看,先把大的值减小到和他相连的值一样大,此时的结果肯定是大的减去小的差值,这个过程可以就可以看成是把这两个点并成一个点,可以用并查集来维护。而那些最小值就一定是起点,加上这些本身就是自己集合的点的值。
具体操作就是记录每一条边的两个端点的值,然后从根据端点较小的值从小到大排序一遍,从后往前进行操作,合并集合即可。最后跑一遍循环,将本身就是自己的集合的点的值全部加上。
代码

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 1e5 + 10;
struct node {
    int u, v, val1, val2;
    friend bool operator < (node a, node b) {
        if(a.val1 == b.val1) {
            return a.val2 < b.val2;
        }
        return a.val1 < b.val1;
    }
}e[N << 1];
int fa[N], val[N];

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

void solve() {
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &val[i]);
        fa[i] = i;
    }
    for(int i = 1; i <= m; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        if(val[u] > val[v]) {
            swap(u, v);
        }
        e[i] = {u, v, val[u], val[v]};
    }
    sort(e + 1, e + 1 + m);
    LL res = 0;
    for(int i = m; i; i--) {
        int x = e[i].u, y = e[i].v;
        int fx = find(x), fy = find(y);
        if(val[fx] > val[fy]) {
            res += (LL)val[fx] - val[fy];
            fa[fx] = fy;
        }
        else {
            res += (LL)val[fy] - val[fx];
            fa[fy] = fx;
        }
    }
    for(int i = 1; i <= n; i++) {
        if(fa[i] == i) res += (LL)val[i];
    }
    printf("%lld\n", res);
}

int main() {
//    freopen("in.txt", "r", stdin);
    int t; cin >> t; while(t--)
    solve();
    return 0;
}

F.The Oculus

题目描述
给定长度为lena,lenb,lenc的 0 , 1 0,1 0,1数组,每一位表示对应的斐波那契数 F 1 = 1 , F 2 = 2 , F 3 = 3 , F 4 = 5... F_1=1,F_2=2,F_3=3,F_4=5... F1=1,F2=2,F3=3,F4=5...,1表示加上所对应的这位数,0不加上。第一行总和代表数字 A A A,第二行总和代表数字 B B B,第三行总和代表数字 C C C。满足 A ∗ B = C A*B=C AB=C。但C中有一位 1 1 1错写成了 0 0 0,需要找出是哪一位的 0 0 0错写成 1 1 1。也就是求第 k k k位斐波那契数列满足 A ∗ B = C ∗ F k A*B=C*F_k AB=CFk
思路
A ∗ B m o d P A*B mod P ABmodP = C ∗ F k m o d P C*F_kmodP CFkmodP 等价于 F k m o d P = ( A ∗ B − C ) m o d P F_kmodP=(A*B-C)modP FkmodP=(ABC)modP
数据范围并不大,所以按照暴力跑完全是可以模拟跑完的,唯一就是需要对每一位斐波那契数取模,保证所有数对应的模数都是唯一的,根据官方题解把模数当成 2 64 2^{64} 264完全就可以了,注意一下取反即可。看见有的队伍取双模数也是过了。
代码

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 2e6 + 10;
LL f[N];

void init() {
    f[1] = 1, f[2] = 2;
    for(int i = 3; i < N; i++) {
        f[i] = f[i - 1] + f[i - 2];
    }
}

void solve() {
    int lena, lenb, lenc;
    LL a = 0, b = 0, c = 0;
    scanf("%d", &lena);
    for(int i = 1; i <= lena; i++) {
        int x; scanf("%d", &x);
        if(x == 1) a += f[i];
    }
    scanf("%d", &lenb);
    for(int i = 1; i <= lenb; i++) {
        int x; scanf("%d", &x);
        if(x == 1) b += f[i];
    }
    scanf("%d", &lenc);
    for(int i = 1; i <= lenc; i++) {
        int x; scanf("%d", &x);
        if(x == 1) c += f[i];
    }
    LL num = a * b - c;
    for(int i = 1; i <= lena + lenb + 1; i++) {
        if(f[i] == num || f[i] == ~num) {
            printf("%d\n", i);
            return;
        }
    }
}

int main() {
    init();
//    freopen("in.txt", "r", stdin);
    int t; cin >> t; while(t--)
    solve();
    return 0;
}

G.In Search of Gold

题目描述
给一棵树,求每条边有两种权值 a , b a,b a,b,有 k k k条边权值为 a a a n − k − 1 n-k-1 nk1条边权值为 b b b,求树的直径的最小值。
思路
学了这位巨巨的题解
谈一下自己的理解。
求一个值最大值的最小值,二分答案判断是否可行。正常求树的直径以 f [ i ] f[i] f[i]表示以 i i i为根节点的其子树中的最长链,这里设 f [ i ] [ j ] f[i][j] f[i][j]表示以 i i i为根节点,其子树中有 j j j个值选择 a a a的最大值。在每次更新的过程中,需要判断当前的直径是否超过给定的 m i d mid mid,若超过了就没有必要更新。
代码
代码变量解释:
在每个结点的层,开一个临时数组 d i s [ 25 ] dis[25] dis[25]表示在结点 u u u时,选择 i i i条边为 a a a的最大值,最后对该层的每一种选择进行更新。 s i z [ i ] siz[i] siz[i]表示根结点为 i i i的点最多能选择 a a a的边数。 n o w now now表示根结点 u u u最多能选择的 a a a的数量,所以在每一层关于 n o w now now值的更新就是

now = min(siz[u] + siz[v] + 1, K);

最后把该层的 n o w now now值赋给对应层数的 s i z [ u ] siz[u] siz[u]

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 2e4 + 10, M = 25;
struct edge {
	int to, next;
	LL a, b;
}e[N << 1];
int head[N], idx;
LL f[N][M];
int siz[N];
int n, K;

void add(int u, int v, LL a, LL b) {
	e[++idx] = {v, head[u], a, b};
	head[u] = idx;
}

void init() {
	for(int i = 1; i <= n; i++) {
		head[i] = 0;
	}
	idx = 0;
}

void dfs(int u, int fa, LL x) {
	siz[u] = 0;
	for(int i = 0; i <= K; i++) f[u][i] = 0;
	for(int i = head[u]; i; i = e[i].next) {
        int v = e[i].to;
        if(v == fa) continue;
        dfs(v, u, x);
        int now = min(K, siz[u] + siz[v] + 1);
        LL dis[M];
        for(int j = 0; j <= now; j++) dis[j] = x + 10;
        for(int j = 0; j <= siz[u]; j++) {
            for(int k = 0; k <= siz[v] && k + j <= K; k++) {
                if(f[u][j] + f[v][k] + e[i].a <= x) {
                	dis[j + k + 1] = min(dis[j + k + 1], max(f[u][j], f[v][k] + e[i].a));
                }
                if(f[u][j] + f[v][k] + e[i].b <= x) {
                	dis[j + k] = min(dis[j + k], max(f[u][j], f[v][k] + e[i].b));
                }
            }
        }
        for(int j = 0; j <= now; j++) f[u][j] = dis[j];
        siz[u] = now;
	}
}

void solve() {
	scanf("%d%d", &n, &K);
	init();
	LL l = 1, r = 0;
	for(int i = 1; i < n; i++) {
		int u, v;
		LL a, b;
		scanf("%d%d%lld%lld", &u, &v, &a, &b);
		add(u, v, a, b);
		add(v, u, a, b);
		r += max(a, b);
	}
	LL res = r;
	while(l <= r) {
		LL mid = l + r >> 1;
		dfs(1, 0, mid);
		if(f[1][K] <= mid) {
			res = mid;
			r = mid - 1;
		} else l = mid + 1;
	}
	printf("%lld\n", res);
}

int main() {
//	freopen("in.txt", "r", stdin);
	int t; cin >> t; while(t--)
	solve();
	return 0;
}

J.Lead of Wisdom

题目描述
n n n件物品, k k k种类型,有的类型的物品可能没有。每件物品四个值 a , b , c , d a,b,c,d a,b,c,d,求选择 k k k类每类物品只能选择一个,求 D M G = ( 100 + ∑ i ∈ S a i ) ( 100 + ∑ i ∈ S b i ) ( 100 + ∑ i ∈ S c i ) ( 100 + ∑ i ∈ S d i ) DMG=(100+∑_{i∈S}ai)(100+∑_{i∈S}bi)(100+∑_{i∈S}ci)(100+∑_{i∈S}di) DMG=(100+iSai)(100+iSbi)(100+iSci)(100+iSdi)的最大值
思路
爆搜,把中间为0的点扔掉。个人认为是这样的,对于有的空结点,假如说有 50 50 50件物品,共有 50 50 50种类型,但是只有前 15 15 15类型的物品才存在,假设 1 − 5 1-5 15类物品有 4 4 4件, 6 − 15 6-15 615类物品有3件,那么最后的35类物品就相当于0,如果全部跑一遍的时间复杂度就变成了 O ( 4 5 ∗ 3 10 ∗ 35 ) O(4^5*3^{10}*35) O(4531035)就存在TLE的情况。有的倒着搜跑过去了感觉是数据没出全吧emmm
代码

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N = 55;
int n, k;
struct node {
    LL a, b, c, d;
};
vector<int> num;
vector<node> cnt[N];
LL res;

void dfs(int x, LL a, LL b, LL c, LL d) {
    if(x == num.size()) {
        res = max(res, a * b * c * d);
        return;
    }
    int y = num[x];
    for(int i = 0; i < cnt[y].size(); i++) {
        dfs(x + 1, a + cnt[y][i].a, b + cnt[y][i].b, c + cnt[y][i].c, d + cnt[y][i].d);
    }
}

void solve() {
    scanf("%d%d", &n, &k);
    num.clear();
    res = 0;
    for(int i = 1; i <= k; i++) {
        cnt[i].clear();
    }
    for(int i = 1; i <= n; i++) {
        int op, a, b, c, d;
        scanf("%d%d%d%d%d", &op, &a, &b, &c, &d);
        cnt[op].push_back({a, b, c, d});
    }
    for(int i = 1; i <= k; i++) {
        if(cnt[i].size() != 0) num.push_back(i);
    }
    dfs(0, 100, 100, 100, 100);
    printf("%lld\n", res);
}

int main() {
//    freopen("out.txt", "w", stdout);
//    freopen("in.txt", "r", stdin);
    int t; cin >> t; while(t--)
    solve();
    return 0;
}

L.String Distance

题目描述
给定两个字符串 S 1 , S 2 S_1,S_2 S1,S2 q q q次询问,每次给一个 l , r l,r l,r表示取 S 1 S_1 S1串的第 l l l位到第 r r r位。对于字符串 S S S可以删除一个字符或插入一个字符,问最少操作几次可以使选择后的串 S 1 S_1 S1 S 2 S_2 S2相等。
思路
删除一个字符或者插入一个字符其实等价,可以看成删除 S 1 S_1 S1 S 2 S_2 S2的最少字符使两个字符串相等。其实就是求两个字符串的最长公共子序列,答案就是 l e n S 1 len_{S_1} lenS1+ l e n S 2 − 2 ∗ L C S len_{S_2}-2*LCS lenS22LCS。但是对于每次询问再算一遍直接T。所以需要DP预处理。
d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示字符串 S 1 S_1 S1匹配到第 i i i位, S 2 S_2 S2字符串匹配到第 j j j位,此时最长公共子序列长度为 k k k时,对于这个匹配在 S 1 S_1 S1中出现的最晚位置。
如果 S 1 [ i ] ! = S 2 [ j ] S_1[i]!=S_2[j] S1[i]!=S2[j] d p [ i ] [ j ] [ k ] = m a x ( d p [ i − 1 ] [ j ] [ k ] , d p [ i ] [ j − 1 ] [ k ] ) dp[i][j][k]=max(dp[i-1][j][k],dp[i][j-1][k]) dp[i][j][k]=max(dp[i1][j][k],dp[i][j1][k])
如果 S 1 [ i ] = = S 2 [ j ] S_1[i]==S_2[j] S1[i]==S2[j],当 k = = 1 k==1 k==1 d p [ i ] [ j ] [ k ] = i dp[i][j][k]=i dp[i][j][k]=i, k ! = 1 k!=1 k!=1时, d p [ i ] [ j ] [ k ] = m a x ( d p [ i ] [ j ] [ k ] , d p [ i − 1 ] [ j − 1 ] [ k − 1 ] ) ; dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-1][k-1]); dp[i][j][k]=max(dp[i][j][k],dp[i1][j1][k1]);
最后对于每个询问,判断是否满足 d p [ r ] [ j ] [ k ] > = l dp[r][j][k]>=l dp[r][j][k]>=l的条件即可。
代码

#include<bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;
char s1[N], s2[N];
int dp[N][22][22];

void solve() {
	scanf("%s%s", s1 + 1, s2 + 1);
	int n = strlen(s1 + 1), m = strlen(s2 + 1);
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= m; j++) {
			for(int k = 1; k <= j; k++) {
				dp[i][j][k] = max(dp[i - 1][j][k], dp[i][j - 1][k]);
				if(s1[i] == s2[j]) {
					if(k == 1) dp[i][j][k] = i;
					else dp[i][j][k] = max(dp[i][j][k], dp[i - 1][j - 1][k - 1]);
				}
			}
		}
	}
	int q; scanf("%d", &q);
	while(q--) {
		int l, r;
		scanf("%d%d", &l, &r);
		int num = 0;
		for(int j = 1; j <= m; j++) {
			for(int k = 1; k <= j; k++) {
				if(dp[r][j][k] >= l) {
					num = max(num, k);
				}
			}
		}
		printf("%d\n", r - l + 1 + m - 2 * num);
	}
}

int main() {
	// freopen("in.txt", "r", stdin);
	int t; cin >> t; while(t--)
	solve();
	return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值