2019.10.12解题报告

真是令人智熄
我这辈子也没想到自己打表还能打错了
手残加眼瞎
我真是个小菜鸡



T1:最近公共祖先

最近公共祖先(Lowest Common Ancestor,LCA)是指在一个树中同时拥有给定的两个点作为后
代的最深的节点。
为了学习最近公共祖先,你得到了一个层数为 n + 1 的满二叉树,其中根节点的深度为 0,其他
节点的深度为父节点的深度 +1 。你需要求出二叉树上所有点对 (i,j),(i,j 可以相等,也可以 i > j)
的最近公共祖先的深度之和对 10^9 + 7 取模后的结果。

Input:一行一个整数 n 。

Output:一行一个整数表示所有点对 (i,j),(i,j 可以相等,也可以 i > j)的最近公共祖先的深度之和对(10^9 + 7) 取模后的结果。

Examples:

commonants.in commonants.out
2 22
19260817 108973412

Notes:


样例一解释:
树一共有 7 个节点(一个根节点和两个子节点) ,其中 (4,4),(5,5),(6,6),(7,7)4对的最近公共祖先深度为2,(4,2),(2,4),(5,2),(2,5),(5,4),(4,5),(2,2),(6,3),(3,6),(3,7),(7,3),(6,7),(7,6),(3,3) 共 14 对最近公共祖先深度是 1 ,其他的点对最近公共祖先深度为 0,所以答案为22。

数据范围:

对于 20% 的数据,n ≤ 10
对于 50% 的数据, n ≤10^6
对于 100% 的数据,1 ≤ n ≤ 10^9

solution:

考虑一个节点, 如果他作为最近公共祖先的话有三种情况
1.他和他的左子树或者他和他的右子树
2.他和他自己
3.他的左子树和他的右子树
那么对于深度为(i)的节点他的左子树的对他的贡献即为他和他的左子树的组合方式有2^{n-i}-1种, 同理可得他的右子树对他的贡献为2^{n-i}-1,再加上他自己的减去重复的那么这个节点总贡献就是(2^{2 ast n-2 ast i + 1}-1),又因为这一层(i)(2 ast i)个节点一共有i层,那么这棵树的总贡献即为$$sum_{i=0}^{n}(2 ast i ast i ast 2^{2 ast n-2 ast i +1}-1)$$
算这里O(n)来实现, 但是n的范围是到了10^9, 然后我们再利用高中知识(数学必修五)把他化简成O(1)的具体做法是先把他拆开然后错位相减。 太麻烦不想写了

code:

#include <cstdio>
#include <iostream>
#define int long long
using namespace std;
const int mod = 1000000007;
int n;
int read() {
	int s = 0, w = 1;
	char ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') w = -1;ch = getchar();}
	while(isdigit(ch)) {s = s * 10 + ch - '0';ch = getchar();}
	return s * w;
}
int powe(int a, int b) {
	int sum = 1;
	while(b) {
		if(b & 1) sum = sum * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return sum;
}
signed main() {
//	freopen("commonants.in", "r", stdin);
//  freopen("commonants.out", "w", stdout);
	n = read();
	cout << ((powe(2, 2 * n + 2) % mod - (4 * n + 2) % mod * powe(2, n)) % mod + mod - 2) % mod << endl;
//	fclose(stdin);
//	fclose(stdout);
	return 0;
}

T2:即时战略

HLY 在玩一个即时战略 (Real Time Strategy) 游戏。与大多数同类游戏类似,这个游戏的地图是
平面的,并且玩家都有一个基地。
HLY 的对手杰哥的基地是一个 w × h 的矩形。其中矩形的每个格子都有一个建筑,每个建筑都
有一个重要度。其中第 i 行第 j 列的格子中的建筑的重要度为 w ij 。
HLY 决定轰炸杰哥的基地。他可以选择杰哥基地的任何一个格子释放一个能量为 p 的炸弹。释
放以后,这个格子的建筑会受到 p 的摧毁度。炸弹产生的冲击波可以向四个方向扩散,每扩散一格能
量值会减少 1 。即释放位置相邻的 4 个格子会受到 p − 1 的摧毁度,再向外的 8 个格子会受到 p − 2
的摧毁度 ... 直到能量值减为 0 ,形式化的讲,如果在第 x 行第 y 列释放炸弹,那么第 i 行第 j 列
的格子受到的摧毁度 d[i][j] = max(0,p − (| x − i | + | y − j |)) 。
对于每个的格子,杰哥受到的损失即为这个格子的重要度与受到的摧毁度的乘积,即 w[i][j] × d[i][j] 。
HLY 想知道,对于每一种投放炸弹的方案,杰哥受到的最小总损失和最大总损失各为多少,形式
化的讲,即为$$sum_{i=1}{w}sum_{j=1}{h}(w[i][j]ast d[i][j])$$
的最小值与最大值。

Input:第 1 行三个整数 w,h,p 接下来 w 行,每行 h 个整数。从第 2 行开始第 i 行第 j 个整数表示 w[i][j]

Output:一行两个数,表示杰哥受到的最小总损失和最大总损失,用空格隔开。

rts.in
3 4 3
9 9 9 1
9 9 1 1
9 1 1 1
rts.out :
10 96

Notes:

样例解释:
HLY 在第 2 行第 2 列释放炸弹杰哥所受损失最大,为 9 × 1 + 9 × 2 + 9 × 1 + 9 × 2 + 9 × 3 + 1 ×
2 + 1 × 1 + 9 × 1 + 1 × 2 + 1 × 1 = 96 。
HLY 在第 3 行第 4 列释放炸弹杰哥所受损失最小, 为 1×1+1×1+1×2+1×1+1×2+1×3 = 10

数据范围:

对于 100% 的数据,1 ≤ n,m ≤ 400,1 ≤ p ≤ 200,0 ≤ w[i][j] ≤ 10 5 。
子任务 1 (10 分) :满足 p = 1 。
子任务 2 (30 分) :满足 1 ≤ n,m ≤ 40 。
子任务 3 (60 分) :没有特殊限制。

solution:

法一:来自cdx大佬的讲解:“你看这个n m q的范围很明显是要一个O(nmq)的做法,你发现暴力的时间复杂度正好是这个然后你就做完了。”

%%%

法二(下发题解方法):
注意到我们没有必要对于每一个点暴力统计一遍答案。
当炸弹往右移动一格的时候,每个格子的摧毁度是这样变化的

红色部分 -1 ,绿色部分+1。

红色部分与绿色部分相应的往右移动

我们只需要维护红色部分与绿色部分的和,在往右移动一格的时候,由于红色部分与绿色部分只有边上会变化,那么总的改变个数为 O(n) 级别,于是我们就能 O((n^2)) 枚举, O(n) 维护,O(n^3) 的时间复杂度解决了。
期望得分:100分。
还可以更优吗?
事实上,我们只要记录对角线方向的前缀和就能做到 O(1) 维护,总复杂度 O(n^2)

孙土蛋code

#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <vector>
#include <map>
#include <complex>

#define lc k << 1
#define rc k << 1 | 1

#define inf 0x3f3f3f3f
 
using namespace std;
typedef long long ll;

int a[805][805];
int n, m, p;

ll mn = 1e13, mx = 0;

inline int dis(int x1, int y1, int x2, int y2){
	return min(abs(x2 - x1) + abs(y2 - y1), p);
}

void solve(int x){
	int y = 1;
	ll lres = 0, rres = 0;
	ll lsum = 0, rsum = 0;
	for(int i = x - p; i <= x + p; i ++){
		for(int j = y - p; j <= y; j ++){
			if(dis(i, j, x, y) != p) lsum += a[i + 200][j + 200]; lres += a[i + 200][j + 200] * (p - dis(i, j, x, y));
		}
	}
	for(int i = x - p; i <= x + p; i ++){
		for(int j = y + 1; j <= y + p; j ++){
			if(dis(i, j, x, y) != p) rsum += a[i + 200][j + 200]; rres += a[i + 200][j + 200] * (p - dis(i, j, x, y));
		}
	}
	//printf("%d %d %lld %lld %lld %lld
", x, y, lres, rres, lsum, rsum);
	mn = min(lres + rres, mn);
	mx = max(mx, lres + rres);
	while(y < m){
		lres -= lsum;
		for(int i = y - p + 1; i <= y; i ++){
			lsum -= a[x + (p - 1 - y + i) + 200][i + 200];
			if(p - 1 - y + i != 0) lsum -= a[x - (p - 1 - y + i) + 200][i + 200];
		}
		y ++;
		for(int i = y; i <= y + p - 1; i ++){
			rsum += a[x + (p - 1 - i + y) + 200][i + 200];
			if(p - 1 - i + y != 0) rsum += a[x - (p - 1 - i + y) + 200][i + 200];
		}
		rres += rsum;
		for(int i = x - p + 1; i <= x + p - 1; i ++){
			rsum -= a[i + 200][y + 200]; rres -= a[i + 200][y + 200] * (p - dis(i, y, x, y));
			lsum += a[i + 200][y + 200]; lres += a[i + 200][y + 200] * (p - dis(i, y, x, y));
		}
	//	printf("%d %d %lld %lld %lld %lld
", x, y, lres, rres, lsum, rsum);
		mn = min(lres + rres, mn);
		mx = max(mx, lres + rres);
	}
}

int main(){
//	freopen("rts.in", "r", stdin);
//	freopen("rts.out", "w", stdout);
	 
	scanf("%d%d%d", &n, &m, &p);
	memset(a, 0, sizeof(a));
	for(int i = 1; i <= n; i ++){
		for(int j = 1; j <= m; j ++){
			scanf("%d", &a[i + 200][j + 200]);
			//a[i + 200][j + 200] = 1;
		}
	}
	for(int i = 1; i <= n; i ++){
		solve(i);
	}
	printf("%lld %lld
", mn, mx);
	return 0;
}

问:博主为什么不自己写要粘土蛋的代码?
答:干卿吊事。
我懒不行嘛

T3欧皇:

欧皇 HLY 自从拼点失败以后,认为带有随机性的游戏不适合他,开始专注于下棋。有一天他太无聊了,在棋盘上摆起棋子来。
HLY 有一个 w × h 的棋盘,有 c 种颜色的棋子,第 i 种棋子有 a[i] 个。他想把棋子全部摆到棋盘上,使得每个格子上至多有 1 个棋子,并且同一行同一列不能有两个不同的棋子。
HLY 想知道有多少种放棋子的方案,两种方案不同当且仅当存在一个格子在两种方案中一种放棋子一种不放棋子或放的棋子不一样。由于HLY还需要陪妹子玩耍,所以他想让你帮他解决这个问题。你只需要告诉他答案对 10^9 + 7 取模后的结果即可。

Input:第 1 行三个整数 w,h,c 。第 2 行 c 个整数 a 1 ...a n ,含义如题面所示。

Output:一行一个整数,即放棋子的方案数对 10 9 + 7 取模后的结果。

Examples:

europe.in :
4 2 2
3 1
europe.out:
8

Notes:

对于 100% 的数据,1 ≤ w,h ≤ 30,1 ≤ c ≤ 10,
sum(a[i]=a[1]+a[2]+a[3]+...+a[n])
子任务 1 (10 分) :满足 w × h ≤ 2 。
子任务 2 (15 分) :满足 w × h ≤ 8 。
子任务 3 (15 分) :满足 c = 1 。
子任务 4 (15 分) :满足 a[i] = 1 。
子任务 5 (45 分) :没有特殊限制。

solution:

目前还没人讲这道题
考试下发题解思路:
题目里有一个重要的性质:同一行和同一列不能有两个颜色不同的棋子
所以我们可以对于每种棋子单独考虑,为每种棋子选出若干行和列去放
我们记h[k][i][j]表示第k种棋子,占据第i行第j列,且每一列都有棋子的方案数
我们可以用第k种棋子,占据第i行第j列的总方案数-不合法的方案数计算,这里考虑容斥原理
合法方案数=总方案数-至少一列空的方案数+至少两列空的方案数-至少三列空的方案数-......
x列为空相当于先从j列选出x列,再乘以i行j-x列的总方案数
我们再记g[k][i][j]表示第k种棋子,占据第i行j列,且每一行每一列都有棋子的方案数
和h相同,我们也可以用总方案数-不合法的方案数计算
合法方案数=总的列的合法方案数-至少一行空列合法的方案数+至少两行空的列的合法方案数-至少三行空的列的合法方案数......
我们再记f[k][i][j]表示前k种棋子,共占据i行j列,且每一行每一列都有棋子的方案数
我们枚举当前的棋子占据了几行几列,用组合数计算即可
最后我们枚举有多少行/列为空,统计答案
复杂度O(n^4c)

孙土蛋code:

#include <bits/stdc++.h>
 
using namespace std;
 
typedef long long ll;
const ll mod = 1e9 + 7;
ll C[1005][1005];
 
void pre(){
    C[0][0] = 1;
    for(int i = 1; i <= 1000; i ++){
        C[i][0] = 1;
        for(int j = 1; j <= i; j ++){
            C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
        }
    }
}
int n, m, c;
int a[105];
ll f[11][31][31];
ll g[11][31][31], h[11][31][31];
int main(){
//	freopen("europe.in", "r", stdin);
//	freopen("europe.out", "w", stdout);
	
    scanf("%d%d%d", &n, &m, &c);
    for(int i = 1; i <= c; i ++){
        scanf("%d", &a[i]);
    }   
    pre();
    for(int k = 1; k <= c; k ++){
        for(int i = 1; i <= n; i ++){
            for(int j = 1; j <= m; j ++){
                h[k][i][j] = C[i * j][a[k]];
                ll flag = -1;
                for(int jj = j - 1; jj >= 1; jj --){
                    h[k][i][j] = (h[k][i][j] + C[j][jj] * C[i * jj][a[k]] % mod * flag + mod) % mod;
                    flag = -flag;
                }
            }
        }
    }
    for(int k = 1; k <= c; k ++){
        for(int j = 1; j <= m; j ++){
            for(int i = 1; i <= n; i ++){
                g[k][i][j] = h[k][i][j];
            //  printf("%d %d %d %lld
", k, i, j, g[k][i][j]);
                ll flag = -1;
                for(int ii = i - 1; ii >= 1; ii --){
                    g[k][i][j] = (g[k][i][j] + C[i][ii] * h[k][ii][j] % mod * flag + mod) % mod;
                    flag = -flag;
                }
            //  printf("%d %d %d %lld
", k, i, j, g[k][i][j]);
            }
        }
    }
    f[0][0][0] = 1;
    for(int k = 1; k <= c; k ++){
        for(int i = 1; i <= n; i ++){
            for(int j = 1; j <= m; j ++){
                for(int ii = 0; ii <= i - 1; ii ++){
                    for(int jj = 0; jj <= j - 1; jj ++){
                        if((i - ii) * (j - jj) < a[k]) continue;
                        f[k][i][j] = (f[k][i][j] + C[i][i - ii] * C[j][j - jj] % mod * f[k - 1][ii][jj] % mod * g[k][i - ii][j - jj]) % mod;
                    }
                }
            }
        }
    }
    ll ans = 0;
    for(int i = 1; i <= n; i ++){
        for(int j = 1; j <= m; j ++){
            ans = (ans + C[n][i] * C[m][j] % mod * f[c][i][j]) % mod;
        }
    }
    //printf("%d %d %d
", n, m, c); 
    printf("%lld
", ans);
    return 0;
}

谢谢收看, 祝身体健康!

原文地址:https://www.cnblogs.com/yanxiujie/p/11662826.html