2020 HDU Multi-University Training Contest 2(部分)

6763 Total Eclipse

一般思考是,对每个连通块里的全部点减去其中最小点的值,去掉最小点分裂出新的连通块,依次进行,所有减去的值的和为答案。困难的是如何使得连通块分裂。
解决办法为逆向构造连通块,在这个过程中最后留下的总是值最大的点,因此可以令点从大到小排序,依次加入图中。
对于每个新加入的点x,遍历其所有边(x,y),如果y在x之前加入且x与y不连通,说明y所在连通块受到x影响,且还未把该影响更新。利用并查集将x,y合并,并且让y所在集合的根的父亲更新为x,集合的根也更新为x。这样对于每个点u,对答案的贡献值就是b[u]-b[fa[u]]。(显然,根总是上一个该连通图中的最小值点,该点直接受到x的影响,若x非最后的一个值则x又受到该连通图中更小值的影响,从而重现了每次减值时最小值点的消失)

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
int T, n, m, fa[maxn], set[maxn], b[maxn], id[maxn], pos[maxn];
int find(int x) {
    return set[x] == x ? x : set[x] = find(set[x]);
}
void combine(int x, int y) {
    y = find(y);
    if (y == x)return;
    fa[y] = set[y] = x;
}
bool cmp(int x, int y) {
    return b[x] > b[y];
}
int main(void) {
    scanf("%d", &T);
    while (T--) {
        scanf("%d %d", &n, &m);
        vector<int>link[maxn];
        for (int i = 1; i <= n; i++) {
            set[i] = id[i] = i;
            fa[i] = 0;
            scanf("%d", &b[i]);
        }
        sort(id + 1, id + 1 + n, cmp);
        for (int i = 1; i <= n; i++)pos[id[i]] = i;
        while (m--) {
            int u, v;
            scanf("%d %d", &u, &v);
            link[u].push_back(v);
            link[v].push_back(u);
        }
        for (int i = 2; i <= n; i++) 
            for (int j = 0; j < link[id[i]].size(); j++) 
                if (pos[link[id[i]][j]] < i)
                    combine(id[i], link[id[i]][j]);
        ll ans = 0;
        for (int i = 1; i <= n; i++) 
            ans += b[i] - b[fa[i]];
        printf("%lld\n", ans);
    }
    return 0;
}

6768 The Oculus

这题就卡在题目没说取模,一般可以用几个常用的如1e9+9,2^64,998244353

#include<cstdio>
#include<cstring>
using namespace std;
typedef unsigned long long ull;
const int maxn = 2e6 + 5;
ull fib[maxn], A, B, C;
int T, i, len, x, vis[maxn];
ull ksc(ull a, ull b) {
    ull res = 0;
    while (b) {
        if (b & 1)res += a;
        a <<= 1;
        b >>= 1;
    }
    return res;
}
int main(void) {
    fib[1] = 1; fib[2] = 2;
    for (int i = 3; i < maxn; i++)
        fib[i] = fib[i - 1] + fib[i - 2];
    scanf("%d", &T);
    while (T--){
        A = B = C = 0;
        scanf("%d", &len);
        for (int i = 1; i <= len; i++) {
            scanf("%d", &x);
            if (x)A += fib[i];
        }
        scanf("%d", &len);
        for (int i = 1; i <= len; i++) {
            scanf("%d", &x);
            if (x)B += fib[i];
        }
        scanf("%d", &len);
        for (int i = 1; i <= len; i++) {
            scanf("%d", &x);
            vis[i] = x;
            if (x)C += fib[i];
        }
        A = ksc(A, B) - C;
        for (int i = 1;; i++) {
            if (vis[i])continue;
            if (fib[i] == A){
                printf("%d\n", i);
                break;
            }
        }
    }
    return 0;
}

6771 It’s All Squares

主要是判断点是否在多边形内

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 400 + 5;
const int maxs = 4000010;
int T, sx, sy, x, y, n, m, q, w[maxn][maxn];
int tag[maxn][maxn], inIt[maxn * maxn], POS;
char s[maxs];
inline void umin(int& a, int b) { a > b ? (a = b) : 0; }
inline void umax(int& a, int b) { a < b ? (a = b) : 0; }
int main(void) {
    scanf("%d", &T);
    while(T--){
        scanf("%d%d%d", &n, &m, &q);
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
                scanf("%d", &w[i][j]);
        while (q--) {
            scanf("%d%d%s", &sx, &sy, s);
            int len = strlen(s);
            int xl = n, xr = 0, yl = m, yr = 0;
            x = sx, y = sy;
            for (register int i = 0; i < len; i++) {
                if (s[i] == 'L')x--;
                if (s[i] == 'R')x++;
                if (s[i] == 'D')y--;
                if (s[i] == 'U')y++;
                umin(xl, x);
                umax(xr, x);
                umin(yl, y);
                umax(yr, y);
            }
            for (register int i = xl; i <= xr; i++)
                for (register int j = yl; j <= yr; j++)
                    tag[i][j] = 0;
            x = sx, y = sy;
            for (register int i = 0; i < len; i++) {
                if (s[i] == 'L')tag[x][y + 1] = 1, x--;
                if (s[i] == 'R')x++, tag[x][y + 1] = 1;
                if (s[i] == 'D')y--;
                if (s[i] == 'U')y++;
            }
            POS++;
            int ans = 0;
            for (register int i = xl + 1; i <= xr; i++) 
                for (register int j = yl + 1, o = 0; j <= yr; j++) {
                    o ^= tag[i][j];
                    if (!o)continue;
                    if (inIt[w[i][j]] != POS)inIt[w[i][j]] = POS, ans++;
                }
            /*
            向右走,到达目标点,下一步有三种情况:上下右
            目标点如果继续向上或者向下垂直延申.则这条路径从下向上的第二个点开始,其值一直都在多边形内 
            向左走,到达目标点,下一步有三种情况:上下左
            则在上一步的点,其情况和向右走的目标点一样.
      
            称类似向右走的目标点为x点,每次在x点的上方一点做个标记
            (考虑最左的边是不包含在范围内的,因此一定要保证标记的左方的垂直方向上有点)
            从下向上,若路经过了第一个标记,即开始遇到了在范围内的点,遇到第二次则走出了范围

            因此如果每次右走在目标点上方作个标记,每次左走在走前点的上方做个标记
            则在范围内从下向上遍历,如果遇到了标记的次数为奇数,说明目前的点在范围内,若为偶数,则不在范围内
            */
            printf("%d\n", ans);
        }
    }
    return 0;
}

6772 Lead of Wisdom

稍微剪下枝,把item数目为0的点尽量跳过,否则每次遇到会增加正好一倍的dfs,更好的是先根据item数目排序,剪枝效果最好。

#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
int T, n, k, cnt[55], nxt[55];
ll ans;
struct node{
	int a, b, c, d;
	node(int _a = 0, int _b = 0, int _c = 0, int _d = 0) :a(_a), b(_b), c(_c), d(_d) {}
}p[55][55];
void dfs(int depth, int a, int b, int c, int d) {
	if (depth > k) {
		ll tmp = 1LL * a * b * c * d;
		if (tmp > ans)ans = tmp;
		return;
	}
	if (!cnt[depth]) {
		dfs(nxt[depth], a, b, c, d);
		return;
	}
	for (int i = 0; i < cnt[depth]; i++) 
		dfs(depth + 1, a + p[depth][i].a, b + p[depth][i].b, c + p[depth][i].c, d + p[depth][i].d);
}
int main(void) {
	scanf("%d", &T);
	while (T--) {
		scanf("%d%d", &n, &k);
		memset(cnt, 0, sizeof(cnt));
		while (n--) {
			int t, a, b, c, d;
			scanf("%d %d %d %d %d", &t, &a, &b, &c, &d);
			p[t][cnt[t]++] = node(a, b, c, d);
		}
		int x = k + 1;
		for (int i = k; i >= 1; i--) {
			nxt[i] = x;
			if (cnt[i])x = i;
		}//保证每个为0的t,下一个接着就是不为0的一层
		ans = 0;
		dfs(1, 100, 100, 100, 100);
		printf("%lld\n", ans);
	}
	return 0;
}

6774 String Distance

很容易知道要求A的区间串与B串的最长公共子序列长度为len,答案就是R-L+1+m-len

题解是初始化一个g[i][j]数组表示在串A[i…n]中字符j第一次出现的下标
利用该数组DP求出区间与B串的最长公共子序列长度

设数组f[i][j]表示B[1…i]与A[L…R]有长度为j的公共子序列的A[L…R]中最小前缀的下标,显然可以初始化f[0][0]=L-1,将i从0开始遍历,确定已知f[i][j]的正确值,利用f[i][j]推出f[i+1][j]和f[i+1][j+1]。

如果f[i][j]<f[i+1][j],则令f[i+1][j]=f[i][j],如果f[i][j]<R,当i+1时,求j+1长度的最短前缀下标,要看B[i+1]和A[f[i][j]+1…n]中哪个字符最先匹配,g[f[i][j]+1][B[i+1]]即其下标。

#include<cstdio>
#include<cstring>
using namespace std;
const int N = 1e5 + 5;
const int M = 20 + 5;
int T, n, m, q, L, R, g[N][26], f[M][M];
char A[N], B[M];
int main(void) {
	scanf("%d", &T);
	while (T--) {
		scanf("%s %s", A + 1, B + 1);
		n = strlen(A + 1);
		m = strlen(B + 1);
		for (int i = 1; i <= n; i++)A[i] -= 'a';
		for (int i = 1; i <= m; i++)B[i] -= 'a';
		for (int i = 0; i < 26; i++)g[n + 1][i] = n + 1;
		for (int i = n; i; i--) {
			for (int j = 0; j < 26; j++)g[i][j] = g[i + 1][j];
			g[i][A[i]] = i;
		}
		scanf("%d", &q);
		while (q--) {
			scanf("%d %d", &L, &R);
			for (int i = 0; i <= m; i++)
				for (int j = 0; j <= i; j++)
					f[i][j] = n + 1;
			f[0][0] = L - 1;
			for (int i = 0; i < m; i++)
				for (int j = 0; j <= i; j++) {
					if (f[i + 1][j] > f[i][j])f[i + 1][j] = f[i][j];
					if (f[i][j] < R)
						if (f[i + 1][j + 1] > g[f[i][j] + 1][B[i + 1]])
							f[i + 1][j + 1] = g[f[i][j] + 1][B[i + 1]];
				}
			int j, flag = 0;
			for (j = m; j; j--) {
				for (int i = m; i >= j; i--) {
					if (f[i][j] <= R) {
						flag = 1;
						break;
					}
				}
				if (flag)break;
			}
			printf("%d\n", R - L + 1 + m - 2 * j);
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JILIN.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值